Skip to content

Commit 8be432e

Browse files
authored
Merge pull request #11 from pyecore/feature/userclass
--user-module option added
2 parents 4ed64f2 + b655717 commit 8be432e

File tree

13 files changed

+341
-170
lines changed

13 files changed

+341
-170
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project
5+
adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6+
7+
## Unreleased
8+
### Added
9+
10+
- Added option to auto-register generated package in global registry, see the
11+
`--auto-register-package` option.
12+
- Added option to specify a user module, from where mixin classes. These mixins must then implement
13+
all end-user specific code like operations and derived attributes. See the `--user-module`
14+
command line option.
15+
16+
### Fixed
17+
18+
- EDatatype registration.
19+
- Various derived attribute fixes.

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
include README.rst LICENSE
1+
include README.rst CHANGELOG.md LICENSE

README.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ pyecoregen - Python code generation from pyecore models
44
|pypi-version| |master-build| |coverage| |license|
55

66
.. |master-build| image:: https://travis-ci.org/pyecore/pyecoregen.svg?branch=master
7-
:target: https://travis-ci.org/pyecore/pyecoregen
7+
:target: https://travis-ci.org/pyecore/pyecoregen
88

99
.. |pypi-version| image:: https://badge.fury.io/py/pyecoregen.svg
10-
:target: https://badge.fury.io/py/pyecoregen
10+
:target: https://badge.fury.io/py/pyecoregen
1111

1212
.. |coverage| image:: https://coveralls.io/repos/github/pyecore/pyecoregen/badge.svg?branch=master
1313
:target: https://coveralls.io/github/pyecore/pyecoregen?branch=master
@@ -83,3 +83,23 @@ hold it's root package in ``library_pkg``, you would generate with:
8383
8484
generator = EcoreGenerator()
8585
generator.generate(library_pkg, 'some/folder')
86+
87+
Generator options
88+
~~~~~~~~~~~~~~~~~
89+
90+
The end user can control some of the features how the metamodel code is generated. This can be done
91+
at the command line as well as via programmatic invocation. A command line parameter ``--my-param``
92+
is then turning into a keyword argument ``my_param``.
93+
94+
``--auto-register-package`` (Default: ``False``)
95+
If enabled, the generated packages are automatically added to pyecore's global namespace
96+
registry, which makes them available during XMI deserialization.
97+
98+
``--user-module`` (Default: ``None``)
99+
If specified, the given string is interpreted as a dotted Python module path. E.g.
100+
``--user-module my.custom_mod`` will make the generated code import mixin classes from a module
101+
``my.custom_mod``. A generated class with name ``<name>`` then derives from a mixin
102+
``<name>Mixin``, which is expected to be part of the user module. If this option is used, the
103+
generator also produces a skeleton file which contains all required mixin classes and methods.
104+
Usually you copy parts of this template to your own module, which is then checked into version
105+
control all your other code.

pyecoregen/cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import pyecore.resources
99
from pyecoregen.ecore import EcoreGenerator
1010

11-
1211
URL_PATTERN = re.compile('^http(s)?://.*')
1312

1413

@@ -36,6 +35,10 @@ def generate_from_cli(args):
3635
help="Generate package auto-registration for the PyEcore 'global_registry'.",
3736
action='store_true'
3837
)
38+
parser.add_argument(
39+
'--user-module',
40+
help="Dotted name of module with user-provided mixins to import from generated classes.",
41+
)
3942
parser.add_argument(
4043
'--verbose',
4144
'-v',
@@ -47,7 +50,10 @@ def generate_from_cli(args):
4750

4851
configure_logging(parsed_args)
4952
model = load_model(parsed_args.ecore_model)
50-
EcoreGenerator(parsed_args.auto_register_package).generate(model, parsed_args.out_folder)
53+
EcoreGenerator(
54+
auto_register_package=parsed_args.auto_register_package,
55+
user_module=parsed_args.user_module
56+
).generate(model, parsed_args.out_folder)
5157

5258

5359
def configure_logging(parsed_args):

pyecoregen/ecore.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,43 @@ def create_template_context(self, element, **kwargs):
9595
)
9696

9797

98+
class EcorePackageMixinTask(EcorePackageModuleTask):
99+
"""Generation of optional mixins for user implementations."""
100+
template_name = 'mixins.py.skeleton.tpl'
101+
102+
@staticmethod
103+
def filename_for_element(package: ecore.EPackage):
104+
return '{}_mixins.py.skeleton'.format(package.name)
105+
106+
98107
class EcoreGenerator(multigen.jinja.JinjaGenerator):
99-
"""Generation of static ecore model classes."""
108+
"""
109+
Generation of static Pyecore model classes.
100110
101-
tasks = [
102-
EcorePackageInitTask(formatter=multigen.formatter.format_autopep8),
103-
EcorePackageModuleTask(formatter=multigen.formatter.format_autopep8),
104-
]
111+
Attributes:
112+
user_module (str): Dotted module name with user-defined implementations for operations and
113+
derived attributes.
114+
115+
auto_register_package (bool): Flag, whether all generated packages are automatically added
116+
to Pyecore's package registry.
117+
"""
105118

106119
templates_path = os.path.join(
107120
os.path.abspath(os.path.dirname(__file__)),
108121
'templates'
109122
)
110123

111-
def __init__(self, auto_register_package=False, **kwargs):
124+
def __init__(self, *, user_module=None, auto_register_package=False, **kwargs):
125+
self.user_module = user_module
112126
self.auto_register_package = auto_register_package
127+
128+
self.tasks = [
129+
EcorePackageInitTask(formatter=multigen.formatter.format_autopep8),
130+
EcorePackageModuleTask(formatter=multigen.formatter.format_autopep8),
131+
]
132+
if self.user_module:
133+
self.tasks.append(EcorePackageMixinTask(formatter=multigen.formatter.format_autopep8))
134+
113135
super().__init__(**kwargs)
114136

115137
@staticmethod
@@ -212,7 +234,10 @@ def filter_set(value):
212234
return set(value)
213235

214236
def create_global_context(self, **kwargs):
215-
return super().create_global_context(auto_register_package=self.auto_register_package)
237+
return super().create_global_context(
238+
user_module=self.user_module,
239+
auto_register_package=self.auto_register_package
240+
)
216241

217242
def create_environment(self, **kwargs):
218243
"""
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% import 'module_utilities.tpl' as modutil with context -%}
2+
"""Mixins to be implemented by user."""
3+
4+
{%- for c in classes -%}
5+
{{ modutil.generate_mixin(c) }}
6+
{%- endfor %}

pyecoregen/templates/module.py.tpl

Lines changed: 7 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
{% import 'module_utilities.tpl' as modutil with context -%}
12
"""Definition of meta model '{{ element.name }}'."""
23
from functools import partial
34
import pyecore.ecore as Ecore
45
from pyecore.ecore import *
56
{% for c in imported_classifiers -%}
67
from {{ c.ePackage | pyfqn }} import {{ c.name }}
78
{% endfor %}
9+
{% if user_module -%}
10+
import {{ user_module }} as _user_module
11+
{% endif %}
812

913
name = '{{ element.name }}'
1014
nsURI = '{{ element.nsURI | default(boolean=True) }}'
@@ -15,137 +19,13 @@ eClass = EPackage(name=name, nsURI=nsURI, nsPrefix=nsPrefix)
1519
eClassifiers = {}
1620
getEClassifier = partial(Ecore.getEClassifier, searchspace=eClassifiers)
1721

18-
{#- -------------------------------------------------------------------------------------------- -#}
19-
20-
{%- macro generate_enum(e) %}
21-
{{ e.name }} = EEnum('{{ e.name }}', literals=[{{ e.eLiterals | map(attribute='name') | map('pyquotesingle') | join(', ') }}])
22-
{% endmacro %}
23-
24-
{#- -------------------------------------------------------------------------------------------- -#}
25-
26-
{%- macro generate_edatatype(e) %}
27-
{{ e.name }} = EDataType('{{ e.name }}', instanceClassName='{{ e.instanceClassName }}')
28-
{% endmacro %}
29-
30-
{#- -------------------------------------------------------------------------------------------- -#}
31-
32-
{%- macro generate_class_header(c) -%}
33-
class {{ c.name }}({{ c | supertypes }}):
34-
{{ c | docstringline -}}
35-
{% endmacro -%}
36-
37-
{#- -------------------------------------------------------------------------------------------- -#}
38-
39-
{%- macro generate_attribute(a) -%}
40-
{% if a.derived %}_{% endif -%}
41-
{{ a.name }} = EAttribute({{ a | attrqualifiers }})
42-
{%- endmacro %}
43-
44-
{#- -------------------------------------------------------------------------------------------- -#}
45-
46-
{%- macro generate_reference(r) -%}
47-
{{ r.name }} = EReference({{ r | refqualifiers }})
48-
{%- endmacro %}
49-
50-
{#- -------------------------------------------------------------------------------------------- -#}
51-
52-
{%- macro generate_derived_attribute(d) -%}
53-
@property
54-
def {{ d.name }}(self):
55-
return self._{{ d.name }}
56-
57-
{%- if d.changeable %}
58-
59-
@{{ d.name }}.setter
60-
def {{ d.name }}(self, value):
61-
self._{{ d.name }} = value
62-
{% endif %}
63-
{%- endmacro %}
64-
65-
{#- -------------------------------------------------------------------------------------------- -#}
66-
67-
{%- macro generate_class_init_args(c) -%}
68-
{% if c.eStructuralFeatures %}, *, {% endif -%}
69-
{{ c.eStructuralFeatures | map(attribute='name') | map('re_sub', '$', '=None') | join(', ') }}
70-
{%- endmacro %}
71-
72-
{#- -------------------------------------------------------------------------------------------- -#}
73-
74-
{%- macro generate_feature_init(feature) %}
75-
{%- if feature.upperBound == 1 %}
76-
if {{ feature.name }} is not None:
77-
self.{{ feature.name }} = {{ feature.name }}
78-
{%- else %}
79-
if {{ feature.name }}:
80-
self.{{ feature.name }}.extend({{ feature.name }})
81-
{%- endif %}
82-
{%- endmacro %}
83-
84-
{#- -------------------------------------------------------------------------------------------- -#}
85-
86-
{%- macro generate_class_init(c) %}
87-
def __init__(self{{ generate_class_init_args(c) }}, **kwargs):
88-
{%- if not c.eSuperTypes %}
89-
if kwargs:
90-
raise AttributeError('unexpected arguments: {}'.format(kwargs))
91-
{%- endif %}
92-
93-
super().__init__({% if c.eSuperTypes %}**kwargs{% endif %})
94-
{%- for feature in c.eStructuralFeatures | reject('type', ecore.EReference) %}
95-
{{ generate_feature_init(feature) }}
96-
{%- endfor %}
97-
{%- for feature in c.eStructuralFeatures | select('type', ecore.EReference) %}
98-
{{ generate_feature_init(feature) }}
99-
{%- endfor %}
100-
{%- endmacro %}
101-
102-
{#- -------------------------------------------------------------------------------------------- -#}
103-
104-
{%- macro generate_operation_args(o) -%}
105-
{% for p in o.eParameters -%}
106-
, {{ p.name }}{% if not p.required %}=None{% endif -%}
107-
{% endfor -%}
108-
{%- endmacro %}
109-
110-
{#- -------------------------------------------------------------------------------------------- -#}
111-
112-
{%- macro generate_operation(o) %}
113-
def {{ o.name }}(self{{ generate_operation_args(o) }}):
114-
{{ o | docstringline }}
115-
raise NotImplementedError('operation {{ o.name }}(...) not yet implemented')
116-
{%- endmacro %}
117-
118-
{#- -------------------------------------------------------------------------------------------- -#}
119-
120-
{%- macro generate_class(c) %}
121-
122-
{% if c.abstract %}@abstract
123-
{% endif -%}
124-
{{ generate_class_header(c) }}
125-
{%- for a in c.eAttributes %}
126-
{{ generate_attribute(a) -}}
127-
{% endfor %}
128-
{%- for r in c.eReferences %}
129-
{{ generate_reference(r) -}}
130-
{% endfor %}
131-
{% for d in c.eAttributes | selectattr('derived') %}
132-
{{ generate_derived_attribute(d) }}
133-
{% endfor %}
134-
{{- generate_class_init(c) }}
135-
{% for o in c.eOperations %}
136-
{{ generate_operation(o) }}
137-
{% endfor %}
138-
{%- endmacro %}
139-
140-
{#- -------------------------------------------------------------------------------------------- -#}
141-
14222
{%- for c in element.eClassifiers if c is type(ecore.EEnum) -%}
143-
{{ generate_enum(c) }}
23+
{{ modutil.generate_enum(c) }}
14424
{%- endfor %}
14525
{% for c in element.eClassifiers if c is type(ecore.EDataType) -%}
146-
{{ generate_edatatype(c) }}
26+
{{ modutil.generate_edatatype(c) }}
14727
{%- endfor %}
14828

14929
{%- for c in classes -%}
150-
{{ generate_class(c) }}
30+
{{ modutil.generate_class(c) }}
15131
{%- endfor %}

0 commit comments

Comments
 (0)