from pathlib import Path
import fire
from hypergol.name_string import NameString
from hypergol.cli.data_model_renderer import DataModelRenderer
from hypergol.hypergol_project import HypergolProject
TEMPORAL = ['datetime', 'date', 'time']
DEFAULT_INITIALISATIONS = {
'object': '{"sample": "sample"}',
'datetime': 'datetime.now()',
'date': 'date.today()',
'time': 'time.min()',
'int': '0',
'str': "''",
'float': '0.0',
'List[int]': '[0, 0]',
'List[str]': "['', '']",
'List[float]': '[0.0, 0.0]',
'List[datetime]': '[datetime.now(), datetime.now()]',
'List[date]': '[date.today(), date.today()]',
'List[time]': '[time.min(), time.min()]'
}
class Member:
def __init__(self, name=None, type_=None, from_=None, to_=None, isTemporal=False, isList=False, isObject=False):
self.name = name
self.type_ = type_
self.from_ = from_
self.to_ = to_
self.isTemporal = isTemporal
self.isList = isList
self.isObject = isObject
class DataModel:
def __init__(self, className: NameString, project: HypergolProject):
self.className = className
self.project = project
self.arguments = []
self.initialisations = []
self.names = []
self.ids = []
self.conversions = []
self.isListDependent = False
def process_inputs(self, value):
m = Member(name=value.split(':', 1)[0], type_=value.split(':', 1)[1])
self.names.append(m.name)
if m.type_ in ['int:id', 'str:id']:
self.ids.append(f'self.{m.name}')
m.type_ = m.type_[:-3]
self.arguments.append(f'{m.name}: {m.type_}')
self.initialisations.append(f'{m.name}={DEFAULT_INITIALISATIONS.get(m.type_, "None")}')
if m.type_.startswith('List['):
self.isListDependent = True
m.type_ = m.type_[5:-1]
m.isList = True
if m.type_ == 'object':
if m.isList:
raise ValueError('List[object] is an invalid type')
m.isObject = True
self.conversions.append(m)
elif m.type_ in TEMPORAL:
m.to_ = 'isoformat'
m.from_ = 'fromisoformat'
m.isTemporal = True
self.conversions.append(m)
elif self.project.is_data_model_class(NameString(m.type_)):
m.to_ = 'to_data'
m.from_ = 'from_data'
m.type_ = NameString(m.type_)
self.conversions.append(m)
elif m.type_ not in ['int', 'str', 'float']:
raise ValueError(f'Unknown type: {value}')
[docs]def create_data_model(className, *args, projectDirectory='.', dryrun=None, force=None, project=None):
"""Generates domain class from the parameters derived from :class:`.BaseData`
Fails if the target file already exists unless ``force=True`` or ``--force`` in CLI is set.
Parameters
----------
className : string (CamelCase)
Name of the class to be created
projectDirectory : string (default='.')
Location of the project directory, the code will be created in ``projectDirectory/data_models/class_name.py``.
dryrun : bool (default=None)
If set to ``True`` it returns the generated code as a string
force : bool (default=None)
If set to ``True`` it overwrites the target file
*args : List of strings
member variables string
representation of the member variable in "name:type", "name:List[type]" or "name:type:id" format
Returns
-------
content : string
The generated code if ``dryrun`` is specified
"""
if project is None:
project = HypergolProject(projectDirectory=projectDirectory, dryrun=dryrun, force=force)
dataModel = DataModel(className=NameString(className), project=project)
for value in args:
dataModel.process_inputs(value)
temporalDependencies = sorted(list({m.type_ for m in dataModel.conversions if m.isTemporal}))
dataModelDependencies = [{'snake': m.type_.asSnake, 'name': m.type_} for m in dataModel.conversions if not m.isTemporal and not m.isObject]
content = (
DataModelRenderer()
.add('from typing import List ', dataModel.isListDependent)
.add('from datetime import {0} ', temporalDependencies)
.add(' ', dataModel.isListDependent or len(temporalDependencies) > 0)
.add('from hypergol import BaseData ')
.add(' ', len(dataModelDependencies) > 0)
.add('from data_models.{snake} import {name}', dataModelDependencies)
.add(' ')
.add(' ')
.add('class {className}(BaseData): ', className=dataModel.className)
.add(' ')
.add(' def __init__(self, {arguments}): ', arguments=', '.join(dataModel.arguments))
.add(' self.{0} = {0} ', dataModel.names)
.add(' ', len(dataModel.ids) > 0)
.add(' def get_id(self): ', len(dataModel.ids) > 0)
.add(' return ({idString}, ) ', len(dataModel.ids) > 0, idString=', '.join(dataModel.ids))
.add(' ', len(dataModel.conversions) > 0)
.add(' def to_data(self): ', len(dataModel.conversions) > 0)
.add(' data = self.__dict__.copy() ', len(dataModel.conversions) > 0)
.add(" data['{name}'] = BaseData.to_string(data['{name}']) ", [{'name': m.name} for m in dataModel.conversions if m.isObject])
.add(" data['{name}'] = data['{name}'].{conv}() ", [{'name': m.name, 'conv': m.to_} for m in dataModel.conversions if not m.isList and not m.isObject])
.add(" data['{name}'] = [v.{conv}() for v in data['{name}']] ", [{'name': m.name, 'conv': m.to_} for m in dataModel.conversions if m.isList])
.add(' return data ', len(dataModel.conversions) > 0)
.add(' ', len(dataModel.conversions) > 0)
.add(' @classmethod ', len(dataModel.conversions) > 0)
.add(' def from_data(cls, data): ', len(dataModel.conversions) > 0)
.add(" data['{name}'] = BaseData.from_string(data['{name}']) ", [{'name': m.name} for m in dataModel.conversions if m.isObject])
.add(" data['{name}'] = {type_}.{conv}(data['{name}']) ", [{'name': m.name, 'type_': str(m.type_), 'conv': m.from_} for m in dataModel.conversions if not m.isList and not m.isObject])
.add(" data['{name}'] = [{type_}.{conv}(v) for v in data['{name}']] ", [{'name': m.name, 'type_': str(m.type_), 'conv': m.from_} for m in dataModel.conversions if m.isList])
.add(' return cls(**data) ', len(dataModel.conversions) > 0)
).get()
project.create_text_file(content=content, filePath=Path(project.dataModelsPath, dataModel.className.asFileName))
project.render(
templateName='test_data_models.py.j2',
templateData={
'name': dataModel.className,
'initialisations': ', '.join(dataModel.initialisations)
},
filePath=Path(project.testsPath, f'test_{dataModel.className.asFileName}')
)
return project.cli_final_message(creationType='Class', name=dataModel.className, content=(content, ))
if __name__ == "__main__":
fire.Fire(create_data_model)