Skip to content

Commit

Permalink
Merge branch 'release/0.13.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
aranega committed Oct 25, 2022
2 parents df1f230 + b080e23 commit 7a5337b
Show file tree
Hide file tree
Showing 25 changed files with 418 additions and 150 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.12.2
current_version = 0.13.0
commit = True
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
Expand Down
27 changes: 16 additions & 11 deletions .travis.yml
@@ -1,21 +1,26 @@
sudo: false
language: python
dist: focal
python:
- '3.5'
- '3.6'
- '3.7'
- '3.8'
- '3.9'
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11-dev"
- "pypy3"
- "nightly"
install:
- pip install -U pip setuptools
- pip install tox-travis coveralls
- pip install -U importlib-metadata
- pip install -U pip setuptools
- pip install tox-travis coveralls
script: tox
after_success: coveralls
branches:
only:
- develop
- master
- "/^\\d+.\\d+.\\d+$/"
- develop
- master
- "/^\\d+.\\d+.\\d+$/"
deploy:
provider: pypi
user: __token__
Expand All @@ -24,4 +29,4 @@ deploy:
distributions: sdist bdist_wheel
on:
tags: true
python: 3.9
python: "3.10"
25 changes: 25 additions & 0 deletions CHANGELOG.rst
@@ -1,6 +1,31 @@
Changelog
---------

0.13.0
++++++

**Features**

- Add support for ``EGeneric`` super types. The support is only for dynamic metamodels right now, right now, it misses a way of encoding/generating them in the static metamodel (if they are dynamically added to a static ``EClass``, it will work though).
- Add alternative MRO computation in some cases. The inheritance tree in some ecore file cannot be mapped on a normal Python inheritance tree, even after reordering (or it requires a more complex reordering). This patch simply provides a new mro computation if such a case is found. The patch activates itself if needed, in almost all normal cases, it will be disabled.
- Add attribute handling in ``DynamicEPackage``.
- Add support for item deletion in ordered sets.
- Add preservation of order for XMI/JSON serialization and deserialization. This means that successive serialization/deserialization of a resource without modification will always yield the same result. If a modification is performed in the resource, then saved, a simple textual diff between the old XMI/JSON and the new one will only higlight the modification. Thanks `@ubmarco <https://github.com/ubmarco>`_ for the suggestion.

**Bugfixes**

- Fix issue with (de)serialization of references in JSON.
- Fix missing f-string in JSON resources. Thanks `@pablo-campillo https://github.com/pablo-campillo`_ for the pull request.
- Fix ``URI.relative_from_me`` method for resources (de)serialization. Thanks `@jamoralp https://github.com/jamoralp`_ for the pull request.
- Fix missing import of ``chain``. Thanks `@ubmarco <https://github.com/ubmarco>`_ for the pull request.


**Miscellaneous**

- Drop Python 3.5 support.
- Some performance improvement.


0.12.2
++++++

Expand Down
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -238,6 +238,9 @@ Thanks for making PyEcore better!
* Eugene (`@4ekin <https://github.com/4ekin>`_)
* Alexandre Acebedo (`@aacebedo <https://github.com/aacebedo>`_)
* Jinhu (`@jinhu <https://github.com/jinhu>`_)
* Pablo Campillo (`@pablo-campillo <https://github.com/pablo-campillo>`_)
* Jose Antonio Moral (`@jamoralp <https://github.com/jamoralp>`_)
* Marco Heinemann (`@ubmarco <https://github.com/ubmarco>`_)



Expand Down
2 changes: 1 addition & 1 deletion pyecore/__init__.py
Expand Up @@ -2,4 +2,4 @@
"""

__version__ = "0.12.2"
__version__ = "0.13.0"
1 change: 0 additions & 1 deletion pyecore/behavior.py
@@ -1,4 +1,3 @@
# -*- coding: future_fstrings -*-
"""The exec module provides new annotations that helps to extend existing
metamodels with behavior, and enables a full executable model experience
in Python.
Expand Down
1 change: 0 additions & 1 deletion pyecore/commands.py
@@ -1,4 +1,3 @@
# -*- coding: future_fstrings -*-
""" This module introduce the command system which allows to defined various
that can be executed onto a commands stack. Each command can also be 'undo' and
'redo'.
Expand Down
120 changes: 89 additions & 31 deletions pyecore/ecore.py
@@ -1,4 +1,3 @@
# -*- coding: future_fstrings -*-
"""This module is the heart of PyEcore. It defines all the basic concepts that
are common to EMF-Java and PyEcore (EObject/EClass...).
It defines the basic classes and behavior for PyEcore implementation:
Expand All @@ -21,11 +20,12 @@
import inspect
from decimal import Decimal
from datetime import datetime
from itertools import chain
from ordered_set import OrderedSet
from weakref import WeakSet
from RestrictedPython import compile_restricted, safe_builtins
from .notification import ENotifer, Kind
from .innerutils import ignored, javaTransMap, parse_date
from .innerutils import InternalSet, ignored, javaTransMap, parse_date


name = 'ecore'
Expand Down Expand Up @@ -144,6 +144,21 @@ def __subclasscheck__(cls, other):
other = other.python_class
return type.__subclasscheck__(cls, other)

def _mro_alternative(cls):
try:
return super().mro()
except TypeError:
# try:
# new_mro = (e.python_class for e in cls.eClass.eAllSuperTypes())
# return tuple(chain([cls], new_mro, (EObject, ENotifer, object)))
# except Exception:
def _eAllBases_gen(self):
super_types = self.__bases__
yield from super_types
for x in super_types:
yield from _eAllBases_gen(x)
return OrderedSet(chain((cls,), _eAllBases_gen(cls)))


# Meta methods for static EClass
class MetaEClass(Metasubinstance):
Expand All @@ -166,7 +181,7 @@ class EObject(ENotifer, metaclass=Metasubinstance):
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
instance._internal_id = None
instance._isset = set()
instance._isset = InternalSet()
instance._container = None
instance._containment_feature = None
instance._eresource = None
Expand All @@ -180,6 +195,9 @@ def __new__(cls, *args, **kwargs):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def force_resolve(self):
return self

@classmethod
def allInstances(cls, resources=None):
if resources:
Expand Down Expand Up @@ -459,11 +477,17 @@ def __init__(self, eTypeParameter=None, eClassifier=None, **kwargs):
super().__init__(**kwargs)
self.eTypeParameter = eTypeParameter
self.eClassifier = eClassifier
self._eternal_listener.append(self)

@property
def eRawType(self):
return self.eClassifier or self.eTypeParameter

def notifyChanged(self, notif):
if (self.eContainmentFeature() is EClass.eGenericSuperTypes and
notif.feature is EGenericType.eClassifier):
self.eContainer()._update_supertypes()


# class SpecialEClassifier(Metasubinstance):
# def __instancecheck__(cls, instance):
Expand Down Expand Up @@ -686,7 +710,10 @@ def __delete__(self, instance):

def __repr__(self):
eType = getattr(self, 'eType', None)
eGenericType = getattr(self, 'eGenericType', None)
name = getattr(self, 'name', None)
if eGenericType:
return f'<{self.__class__.__name__} {name}: {eGenericType.eRawType.raw_types()}>'
return f'<{self.__class__.__name__} {name}: {eType}>'


Expand Down Expand Up @@ -782,13 +809,17 @@ def new_init(self, *args, **kwargs):
try:
instance.python_class = type(name, super_types, attr_dict)
except Exception:
super_types = sorted(super_types,
key=lambda x: len(x.eClass
.eAllSuperTypes()),
reverse=True)
instance.python_class = type(name,
tuple(super_types),
attr_dict)
try:
super_types = tuple(sorted(super_types,
key=lambda x: len(x.eClass
.eAllSuperTypes())),
reverse=True)
instance.python_class = type(name,
super_types,
attr_dict)
except TypeError:
Metasubinstance.mro = Metasubinstance._mro_alternative
instance.python_class = type(name, super_types, attr_dict)

instance.__name__ = name
return instance
Expand Down Expand Up @@ -821,28 +852,22 @@ def notifyChanged(self, notif):
if getattr(self.python_class, '_staticEClass', False):
return
if notif.feature is EClass.eSuperTypes:
new_supers = self.__compute_supertypes()
try:
self.python_class.__bases__ = new_supers
except TypeError:
new_supers = sorted(new_supers,
key=lambda x: len(x.eClass
.eAllSuperTypes()),
reverse=True)
self.python_class.__bases__ = tuple(new_supers)
self._update_supertypes()
elif notif.kind in (Kind.REMOVE, Kind.REMOVE_MANY):
if notif.kind is Kind.REMOVE:
delattr(self.python_class, notif.old.name)
elif notif.kind is Kind.REMOVE_MANY:
for feature in notif.old:
delattr(self.python_class, feature.name)
elif notif.feature is EClass.eOperations:
if notif.kind is Kind.ADD:
self.__create_fun(notif.new)
elif notif.kind is Kind.REMOVE:
delattr(self.python_class, notif.old.name)
elif notif.feature is EClass.eStructuralFeatures:
if notif.kind is Kind.ADD:
setattr(self.python_class, notif.new.name, notif.new)
elif notif.kind is Kind.ADD_MANY:
for x in notif.new:
setattr(self.python_class, x.name, x)
elif notif.kind is Kind.REMOVE:
delattr(self.python_class, notif.old.name)
elif notif.feature is EClass.name and notif.kind is Kind.SET:
self.python_class.__name__ = notif.new
self.__name__ = notif.new
Expand All @@ -856,11 +881,27 @@ def __create_fun(self, eoperation):
exec(code, safe_builtins, namespace)
setattr(self.python_class, name, namespace[name])

def _update_supertypes(self):
new_supers = self.__compute_supertypes()
try:
self.python_class.__bases__ = new_supers
except TypeError:
try:
new_supers = tuple(sorted(new_supers,
key=lambda x: len(x.eClass
.eAllSuperTypes()),
reverse=True))
self.python_class.__bases__ = new_supers
except TypeError:
Metasubinstance.mro = Metasubinstance._mro_alternative
self.python_class.__bases__ = new_supers

def __compute_supertypes(self):
if not self.eSuperTypes:
if not self.eSuperTypes and not self.eGenericSuperTypes:
return (EObject,)
else:
eSuperTypes = list(self.eSuperTypes)
eSuperTypes.extend(x.eClassifier for x in self.eGenericSuperTypes if x.eClassifier is not None)
if len(eSuperTypes) > 1 and EObject.eClass in eSuperTypes:
eSuperTypes.remove(EObject.eClass)
return tuple(x.python_class for x in eSuperTypes)
Expand All @@ -884,18 +925,32 @@ def findEStructuralFeature(self, name):
None)

def _eAllSuperTypes_gen(self):
super_types = self.eSuperTypes
yield from self.eSuperTypes
for x in super_types:
yield from (x.force_resolve() for x in self.eSuperTypes)
for x in self.eSuperTypes:
yield from x._eAllSuperTypes_gen()

def eAllSuperTypes(self):
return OrderedSet(self._eAllSuperTypes_gen())

def _eAllGenericSuperTypes_gen(self):
super_types = self.eGenericSuperTypes
yield from self.eGenericSuperTypes
for x in super_types:
yield from x.eClassifier._eAllGenericSuperTypes_gen()

def eAllGenericSuperTypes(self):
return OrderedSet(self._eAllGenericSuperTypes_gen())

def eAllGenericSuperTypesClassifiers(self):
return OrderedSet((x.eClassifier for x
in self._eAllGenericSuperTypes_gen()))

def _eAllStructuralFeatures_gen(self):
yield from self.eStructuralFeatures
for parent in self.eSuperTypes:
yield from parent._eAllStructuralFeatures_gen()
for parent in self.eGenericSuperTypes:
yield from parent.eClassifier._eAllStructuralFeatures_gen()

def eAllStructuralFeatures(self):
return OrderedSet(self._eAllStructuralFeatures_gen())
Expand Down Expand Up @@ -964,7 +1019,7 @@ def __init__(self, path=None, resource=None, wrapped=None, **kwargs):

def force_resolve(self):
if self.resolved:
return
return self._wrapped
resource = self._proxy_resource
decoded = resource.resolve_object(self._proxy_path)
if not hasattr(decoded, '_inverse_rels'):
Expand All @@ -974,6 +1029,7 @@ def force_resolve(self):
self._wrapped._inverse_rels.update(self._inverse_rels)
self._inverse_rels = self._wrapped._inverse_rels
self.resolved = True
return self._wrapped

def delete(self, recursive=True):
if recursive and self.resolved:
Expand Down Expand Up @@ -1049,6 +1105,8 @@ def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)

def __hash__(self):
if self.resolved:
return hash(self._wrapped)
return object.__hash__(self)

def __eq__(self, other):
Expand All @@ -1075,8 +1133,8 @@ def abstract(cls):
# meta-meta level
EString = EDataType('EString', str)
ENamedElement.name = EAttribute('name', EString)
ENamedElement.name._isset.add(ENamedElement.name) # special case
EString._isset.add(ENamedElement.name) # special case
ENamedElement.name._isset[ENamedElement.name] = None # special case
EString._isset[ENamedElement.name] = None # special case

EBoolean = EDataType('EBoolean', bool, False,
to_string=lambda x: str(x).lower(),
Expand Down Expand Up @@ -1134,7 +1192,7 @@ def abstract(cls):
ETypedElement.eGenericType = EReference('eGenericType', EGenericType,
containment=True)
ETypedElement.eType = EReference('eType', EClassifier)
ENamedElement.name._isset.add(ETypedElement.eType) # special case
ENamedElement.name._isset[ETypedElement.eType] = None # special case

EStructuralFeature.changeable = EAttribute('changeable', EBoolean,
default_value=True)
Expand Down
5 changes: 4 additions & 1 deletion pyecore/innerutils.py
@@ -1,4 +1,3 @@
# -*- coding: future_fstrings -*-
"""
This module gives decorators, functions and variables that are shared among the
different modules.
Expand All @@ -7,6 +6,10 @@
from datetime import datetime


class InternalSet(dict):
add = dict.setdefault


@contextmanager
def ignored(*exceptions):
"""Gives a convenient way of ignoring exceptions.
Expand Down
1 change: 0 additions & 1 deletion pyecore/notification.py
@@ -1,4 +1,3 @@
# -*- coding: future_fstrings -*-
"""
This module gives the "listener" classes for the PyEcore notification layer.
The main class to create a new listener is "EObserver" which is triggered
Expand Down

0 comments on commit 7a5337b

Please sign in to comment.