Skip to content

Commit

Permalink
tools: create new project
Browse files Browse the repository at this point in the history
  • Loading branch information
SamDanielThangarajan committed Sep 3, 2024
1 parent 1af5c30 commit 24710f0
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
omit =
src/nasdaq_protocols/tools/*


3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include src/nasdaq_protocols/common/message/templates/*.mustache
include src/nasdaq_protocols/tools/templates/*.mustache
include src/nasdaq_protocols/tools/templates/*.xml
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dynamic = ["version"]
[project.scripts]
nasdaq-ouch-codegen="nasdaq_protocols.ouch.codegen:generate"
nasdaq-itch-codegen="nasdaq_protocols.itch.codegen:generate"
nasdaq-protocols-create-new-project="nasdaq_protocols.tools.new_project:create"


[tool.setuptools_scm]
Expand Down
Empty file.
138 changes: 138 additions & 0 deletions src/nasdaq_protocols/tools/new_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from importlib import resources
from pathlib import Path

import attrs
import click
import chevron

from . import templates


__all__ = [
'create'
]
SUPPORTED_PROTOCOLS = ['ouch', 'itch']
TEMPLATES_PATH = resources.files(templates)


@attrs.define(auto_attribs=True)
class AppInfo:
app_name: str
app_dir: Path
app_xml: Path
proto_name: str


@attrs.define(auto_attribs=True)
class Context:
project_name: str
project_src_name: str
target_dir: Path
pyproject_toml: Path
tox_ini: Path
apps: list[AppInfo]


@click.command()
@click.option(
'--name', '-n', required=True,
help='Project name'
)
@click.option(
'--target-dir', '-d', type=click.Path(), required=False, default='.',
help='Target directory to create the project structure'
)
@click.option(
'--application', '-a', multiple=True, required=True,
help=f'''include applications in this project.
format appname:protocol
e.g. -a oe:ouch -a md:itch -a fix_oe:fix
supported protocols: {SUPPORTED_PROTOCOLS}
'''
)
def create(name, target_dir, application):
try:
_validate_applications(application)
except Exception as e:
raise click.ClickException(str(e))

click.echo(f'Creating project {name} in {target_dir}')

target_dir = Path(target_dir) / Path(name)
context = Context(
project_name=name,
project_src_name=name.replace('-', '_'),
target_dir=Path(target_dir),
pyproject_toml=Path(target_dir) / Path('pyproject.toml'),
tox_ini=Path(target_dir) / Path('tox.ini'),
apps=[]
)

for app_proto in application:
app_name, proto_name = app_proto.split(':')
app_dir = Path(target_dir) / Path('src') / Path(context.project_src_name) / Path(app_name)
app_info = AppInfo(
app_name=app_name,
app_dir=app_dir,
app_xml=app_dir / Path(f'{app_name}.xml'),
proto_name=proto_name
)
app_info.app_dir.mkdir(parents=True, exist_ok=True)
_write_app_xml(app_info)
click.echo(f'Created application directory: {app_dir}')
context.apps.append(app_info)

Path(target_dir / Path('__init__.py')).touch()
_write_pyproject(context)
_write_tox(context)

click.echo(f'Created project {name} in {target_dir}')


def _write_app_xml(app_info: AppInfo):
if app_info.app_xml.exists():
return

if app_info.proto_name in ['ouch', 'itch']:
xml_template = TEMPLATES_PATH / Path('ouch_itch_xml.mustache')
else:
raise ValueError(f'Unsupported protocol: {app_info.proto_name}')

with open(app_info.app_xml, 'w', encoding='utf-8') as op, open(xml_template, 'r', encoding='utf-8') as inp:
content = chevron.render(inp.read(), attrs.asdict(app_info), partials_path=str(TEMPLATES_PATH))
op.write(content)


def _write_pyproject(context: Context):
toml_template = TEMPLATES_PATH / Path('toml.mustache')
with (open(context.pyproject_toml, 'a', encoding='utf-8') as op,
open(toml_template, 'r', encoding='utf-8') as inp):
content = chevron.render(inp.read(), attrs.asdict(context), partials_path=str(TEMPLATES_PATH))
op.write(content)


def _write_tox(context: Context):
tox_template = TEMPLATES_PATH / Path('tox.mustache')
with (open(context.tox_ini, 'a', encoding='utf-8') as op,
open(tox_template, 'r', encoding='utf-8') as inp):
content = chevron.render(inp.read(), attrs.asdict(context), partials_path=str(TEMPLATES_PATH))
op.write(content)


def _validate_applications(applications):
apps = {}
for app_proto in applications:
if ':' not in app_proto:
raise ValueError(f'Invalid application protocol: {app_proto}, format = appname:protocol')

app_name, proto_name = app_proto.split(':')
if app_name in apps:
raise ValueError(f'Application {app_name} already exists')

if proto_name not in SUPPORTED_PROTOCOLS:
raise ValueError(f'Unsupported protocol: {proto_name} for application: {app_name},'
f' Supported protocols: {SUPPORTED_PROTOCOLS}')

apps[app_name] = proto_name
Empty file.
82 changes: 82 additions & 0 deletions src/nasdaq_protocols/tools/templates/ouch_itch_xml.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<enums-root>
<!-- Add your enums here -->
</enums-root>
<messages-root>
<!-- Add your messages here -->
</messages-root>
</root>

<!--
ENUM DEFINITION:
<enum id="ENUM_NAME" type="DATATYPE">
<value name="VALUE1" description="DESCRIPTION1">VALUE1</value>
<value name="VALUE2" description="DESCRIPTION2">VALUE2</value>
</enum>
Example:
<enum id="OuchSide" type="char_iso-8859-1">
<value name="Buy" description="Buy">B</value>
<value name="Sell" description="Sell">S</value>
</enum>
MESSAGE DEFINITION:
<message id="MESSAGE_NAME" message-id="MSG_TYPE_IN_DECIMAL" direction="incoming | outgoing">
<fields>
<field name="FIELD_NAME" type="DATATYPE"/>
</fields>
</message>
* The direction indicates the flow of message with respect to the server,
if the message is sent from the server to the client, the direction is 'outgoing' otherwise 'incoming'
* MSG_TYPE_IN_DECIMAL: The message type in decimal,
this is the value that is used to identify the message in the wire.
Use '65' for 'A' and '66' for 'B' and so on.
* If the DATATYPE of the field contains 'n' at the end,
it means the field is a fixed length string and the length is specified in the 'length' attribute
Example:
<message id="EnterOrder" message-id="69" direction="incoming">
<fields>
<field name="token" type="int_8_be"/>
<field name="book" type="int_4_be"/>
<field name="side" type="char_iso-8859-1"/>
<field name="account" type="str_iso-8859-1_n" length="16"/>
<field name="customerInfo" type="str_iso-8859-1_n" length="15"/>
<field name="exchangeInfo" type="str_iso-8859-1_n" length="32"/>
</fields>
</message>
DATATYPES:
These are the possible data types you can use for fields or enums
boolean: boolean type indicated by b'\x00' or b'\x01'
byte: 1 byte integer
int_2: 2 byte integer in little endian
int_2_be: 2 byte integer in big endian
uint_2: 2 byte unsigned integer in little endian
uint_2_be: 2 byte unsigned integer in big endian
int_4: 4 byte integer in little endian
int_4_be: 4 byte integer in big endian
uint_4: 4 byte unsigned integer in little endian
uint_4_be: 4 byte unsigned integer in big endian
int_8: 8 byte integer in little endian
int_8_be: 8 byte integer in big endian
uint_8: 8 byte unsigned integer in little endian
uint_8_be: 8 byte unsigned integer in big endian
char_ascii: 1 byte ascii character
char_iso-8859-1: 1 byte iso-8859-1 character
str_ascii: ascii string
str_iso-8859-1: iso-8859-1 string
str_ascii_n: ascii string with length 'n'
str_iso-8859-1_n: iso-8859-1 string with length 'n'
-->
53 changes: 53 additions & 0 deletions src/nasdaq_protocols/tools/templates/toml.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[build-system]
requires = ["setuptools>=64", "setuptools_scm[toml]>=8"]
build-backend = "setuptools.build_meta"


[project]
name = "{{project_name}}"
description = "Nasdaq protocols python implementation ({{project_name}})"

# LICENSE
# ADD license here
# uncomment one of the following line if you want to specify the license of the package
# license = { text = "" }
# license = { file = "" }


# VERSION
# Comment the following line if you want to use the dynamic versioning based on git tags
version = "0.0.1"

# DEPENDENCIES
# add your dependencies to the below list
dependencies = [
'nasdaq-protocols>=0.0.1',
]

# VERSION
# uncomment the following lines if you want to specify the version of this package to be based on git tag
#dynamic = ["version"]
#[tool.setuptools_scm]


# SCRIPTS
# uncomment the following line if you want to specify the scripts of the package
# [project.scripts]
# Add project scripts here


[tool.setuptools.packages.find]
where = ["src"] # list of folders that contain the packages (["."] by default)
include = ["{{project_src_name}}*"] # package names should match these glob patterns (["*"] by default)
namespaces = false # to disable scanning PEP 420 namespaces (true by default)


# DATA FILES
# uncomment the following line if you want to package the asn1 files.
#[tool.setuptools.package-data]
#{{project_src_name}} = ["*.asn1"]


[options]
zip_safe = true
include_package_data = true
18 changes: 18 additions & 0 deletions src/nasdaq_protocols/tools/templates/tox.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[tox]
requires =
tox>4
virtualenv>20.2
env_list =
build

[testenv:build]
description = Build package
deps =
nasdaq-protocols
build
commands =
{{#apps}}
nasdaq-generate-{{proto_name}}-classes src/{{project_src_name}}/{{app_name}}/{{app_name}}.xml src/{{project_src_name}}/{{app_name}} {{app_name}} {{project_src_name}}.{{app_name}}
{{/apps}}
python -m build

0 comments on commit 24710f0

Please sign in to comment.