Package: pyecore-py2
PyEcore is a Model Driven Engineering (MDE) framework written for Python 2.7
(backport of thePyEcore for Python 3version).
Precisely, it is an implementation ofEMF/Ecorefor Python, and it tries to give an
API which is compatible with the original EMF Java implementation.PyEcore allows you to handle models and metamodels (structured data model), and
gives the key you need for building MDE-based tools and other applications based
on a structured data model. It supports out-of-the-box:Data inheritance,Two-ways relationship management (opposite references),XMI (de)serialization,JSON (de)serialization,Notification system,Reflexive API…Let see how to create on a very simple “dynamic” metamodel (in opposite to
static ones, see thedocumentationfor more details):>>>frompyecore.ecoreimportEClass,EAttribute,EString,EObject>>>Graph=EClass('Graph')# We create a 'Graph' concept>>>Node=EClass('Node')# We create a 'Node' concept>>>>>># We add a "name" attribute to the Graph concept>>>Graph.eStructuralFeatures.append(EAttribute('name',EString,default_value='new_name'))>>># And one on the 'Node' concept>>>Node.eStructuralFeatures.append(EAttribute('name',EString))>>>>>># We now introduce a containment relation between Graph and Node>>>contains_nodes=EReference('nodes',Node,upper=-1,containment=True)>>>Graph.eStructuralFeatures.append(contains_nodes)>>># We add an opposite relation between Graph and Node>>>Node.eStructuralFeatures.append(EReference('owned_by',Graph,eOpposite=contains_nodes))With this code, we have defined two concepts:GraphandNode. Both have
aname, and it exists a containment relationship between them. This relation
is bi-directionnal, which means that each time aNodeobject is added to thenodesrelationship of aGraph, theowned_byrelation of theNodeis updated also (it also work in the other way).Let’s create some instances of our freshly created metamodel:>>># We create a Graph>>>g1=Graph(name='Graph 1')>>>g1<pyecore.ecore.Graphat0x7f0055554dd8>>>>>>># And two node instances>>>n1=Node(name='Node 1')>>>n2=Node(name='Node 2')>>>n1,n2(<pyecore.ecore.Nodeat0x7f0055550588>,<pyecore.ecore.Nodeat0x7f00555502b0>)>>>>>># We add them to the Graph>>>g1.nodes.extend([n1,n2])>>>g1.nodesEOrderedSet([<pyecore.ecore.Nodeobjectat0x7f0055550588>,<pyecore.ecore.Nodeobjectat0x7f00555502b0>])>>>>>># bi-directional references are updated>>>n1.owned_by<pyecore.ecore.Graphat0x7f0055554dd8>This example gives a quick overview of some of the features you get for free
when using PyEcore.The project slowly grows and it still requires more love.InstallationPyEcore is available onpypi, you can simply install it usingpip:$pipinstallpyecoreThe installation can also be performed manually (better in a virtualenv):$pythonsetup.pyinstallDocumentationYou can read the documentation (for Python 3), which sums up pretty much everything you can
find in this page, and more:https://pyecore.readthedocs.io/en/latest/You can also find on this page a part of the Documentation:ContentsInstallationDocumentationDynamic MetamodelsExplore Dynamic metamodel/model objectsEnhance the Dynamic metamodelStatic MetamodelsHow to Generate the Static Metamodel CodeStatic/DynamicEOperationNotificationsDeep Journey Inside PyEcoreEClasse Instances as FactoriesImporting an Existing XMI Metamodel/ModelDynamic Metamodels HelperAdding External Metamodel ResourcesMetamodel References by ‘File Path’Adding External resourcesExporting an Existing XMI ResourceDealing with JSON ResourcesCreating Your own URIDeleting ElementsThedelete()methodRemoving an element from it’s containerModifying Elements Using CommandsDynamically Extending PyEcore Base ClassesExtending PyEcore Base ClassesModifying PyEcore Base ClassesDependenciesRun the TestsLiberty Regarding the Java EMF ImplementationStateExisting ProjectsContributorsAdditional ResourcesDynamic MetamodelsDynamic metamodels reflects the ability to create metamodels “on-the-fly”. You
can create metaclass hierarchie, addEAttributeandEReference.In order to create a new metaclass, you need to create anEClassinstance:>>>importpyecore.ecoreasEcore>>>MyMetaclass=Ecore.EClass('MyMetaclass')You can then create instances of your metaclass:>>>instance1=MyMetaclass()>>>instance2=MyMetaclass()>>>assertinstance1isnotinstance2From the created instances, we can go back to the metaclasses:>>>instance1.eClass<EClassname="MyMetaclass">Then, we can add metaproperties to the freshly created metaclass:>>>instance1.eClass.eAttributes[]>>>MyMetaclass.eStructuralFeatures.append(Ecore.EAttribute('name',Ecore.EString))# We add a 'name' which is a string>>>instance1.eClass.eAttributes# Is there a new feature?[<pyecore.ecore.EAttributeobjectat0x7f7da72ba940>]# Yep, the new feature is here!>>>str(instance1.name)# There is a default value for the new attribute'None'>>>instance1.name='mystuff'>>>instance1.name'mystuff'>>># As the feature exists in the metaclass, the name of the feature can be used in the constructor>>>instance3=MyMetaclass(name='myname')>>>instance3.name'myname'We can also create a new metaclassBand a new meta-references towardsB:>>>B=Ecore.EClass('B')>>>MyMetaclass.eStructuralFeatures.append(Ecore.EReference('toB',B,containment=True))>>>b1=B()>>>instance1.toB=b1>>>instance1.toB<pyecore.ecore.Bobjectat0x7f7da70531d0>>>>b1.eContainer()isinstance1# because 'toB' is a containment referenceTrueOpposite and ‘collection’ meta-references are also managed:>>>C=Ecore.EClass('C')>>>C.eStructuralFeatures.append(Ecore.EReference('toMy',MyMetaclass))>>>MyMetaclass.eStructuralFeatures.append(Ecore.EReference('toCs',C,upper=-1,eOpposite=C.eStructuralFeatures[0]))>>>instance1.toCs[]>>>c1=C()>>>c1.toMy=instance1>>>instance1.toCs# 'toCs' should contain 'c1' because 'toMy' is opposite relation of 'toCs'[<pyecore.ecore.Cobjectat0x7f7da7053390>]Explore Dynamic metamodel/model objectsIt is possible, when you are handling an object in the Python console, to ask
for all the meta-attributes/meta-references and meta-operations that can
be called on it usingdir()on, either a dynamic metamodel object or a
model instance. This allows you to quickly experiment and find the information
you are looking for:>>>A=EClass('A')>>>dir(A)['abstract','delete','eAllContents','eAllOperations','eAllReferences','eAllStructuralFeatures','eAllSuperTypes','eAnnotations','eAttributes','eContainer',# ... there is many others'findEOperation','findEStructuralFeature','getEAnnotation','instanceClassName','interface','name']>>>a=A()>>>dir(a)[]>>>A.eStructuralFeatures.append(EAttribute('myname',EString))>>>dir(a)['myname']Enhance the Dynamic metamodelEven if you define or use a dynamic metamodel, you can add dedicated methods
(e.g:__repr__) to the equivalent Python class. EachEClassinstance is
linked to a Python class which can be reached using thepython_classfield:>>>A=EClass('A')>>>A.python_classpyecore.ecore.AYou can directly add new “non-metamodel” related method to this class:>>>a=A()>>>a<pyecore.ecore.Aat0x7f4f0839b7b8>>>>A.python_class.__repr__=lambdaself:'My repr for real'>>>aMyreprforrealStatic MetamodelsThe static definition of a metamodel using PyEcore mostly relies on the
classical classes definitions in Python. Each Python class is linked to anEClassinstance and has a special metaclass. The static code for metamodel
also provides a model layer and the ability to easily refer/navigate inside the
defined meta-layer.$lslibrary__init__.pylibrary.py$catlibrary/library.py# ... stuffs hereclassWriter(EObject):__metaclass__=MetaEClassname=EAttribute(eType=EString)books=EReference(upper=-1)def__init__(self,name=None,books=None,**kwargs):ifkwargs:raiseAttributeError('unexpected arguments:{}'.format(kwargs))super().__init__()ifnameisnotNone:self.name=nameifbooks:self.books.extend(books)# ... Other stuffs here$python...>>>importlibrary>>># we can create elements and handle the model level>>>smith=library.Writer(name='smith')>>>book1=library.Book(title='Ye Old Book1')>>>book1.pages=100>>>smith.books.append(book1)>>>assertbook1insmith.books>>>assertsmithinbook1.authors>>># ...>>># We can also navigate the meta-level>>>importpyecore.ecoreasEcore# We import the Ecore metamodel only for tests>>>assertisinstance(library.Book.authors,Ecore.EReference)>>>library.Book.authors.upperBound-1>>>assertisinstance(library.Writer.name,Ecore.EAttribute)There is two main ways of creating staticEClasswith PyEcore. The first
one relies on automatic code generation while the second one uses manual
definition.The automatic code generator defines a Python package hierarchie instead of
only a Python module. This allows more freedom for dedicated operations and
references between packages.How to Generate the Static Metamodel CodeThe static code is generated from a.ecorewhere your metamodel is defined
(the EMF.genmodelfiles are not yet supported (probably in future version).There is currently two ways of generating the code for your metamodel. The first
one is to use a MTL generator (in/generator) and the second one is to use a
dedicated command line tool written in Python, using Pymultigen, Jinja and PyEcore.Using the Accelo/MTL GeneratorTo use this generator, you need Eclipse and the right Acceleo plugins. Once
Eclipse is installed with the right plugins, you need to create a new Acceleo
project, copy the  PyEcore generator in it, configure a new Acceleo runner,
select your.ecoreand your good to go. There is plenty of documentation
over the Internet for Acceleo/MTL project creation/management…WARNING:the Acceleo generator is now deprecated, use pyecoregen instead!Using the Dedicated CLI Generator (PyEcoregen)For simple generation, the Acceleo generator will still do the job, but for more
complex metamodel and a more robust generation, pyecoregen is significantly better.
Its use is the prefered solution for the static metamodel code generation.
Advantages over the Acceleo generator are the following:it gives the ability to deal with generation from the command line,it gives the ability to launch generation programmatically (and very simply),it introduces into PyEcore a framework for code generation,it allows you to code dedicated behavior in mixin classes,it can be installed frompip.This generator source code can be found at this address with mode details:https://github.com/pyecore/pyecoregenand is available on Pypi, so you can
install it quite symply using:$pipinstallpyecoregenThis will automatically install all the required dependencies and give you a new
CLI tool:pyecoregen.Using this tool, your static code generation is very simple:$pyecoregen-eyour_ecore_file.ecore-oyour_output_pathThe generated code is automatically formatted usingautopep8. Once the code
is generated, your can import it and use it in your Python code.Manually defines staticEClassTo manually defines staticEClass, it is simply a matter of creating a
Python class, and adding to it the@EMetaclassclass decorator. This
decorator will automatically add the righ metaclass to the defined class, and
introduce the missing classes in it’s inheritance tree. Defining simple
metaclass is thus fairly easy:@EMetaclassclassPerson(object):name=EAttribute(eType=EString)age=EAttribute(eType=EInt)children=EReference(upper=-1,containment=True)def__init__(self,name):self.name=namePerson.children.eType=Person# As the relation is reflexive, it must be set AFTER the metaclass creationp1=Person('Parent')p1.children.append(Person('Child'))Without more information, all the created metaclass will be added to a defaultEPackage, generated on the fly. If theEPackagemust be controlled, a
global variable ofEPackagetype, namedeClass, must be created in the
module.eClass=EPackage(name='pack',nsURI='http://pack/1.0',nsPrefix='pack')@EMetaclassclassTestMeta(object):passassertTestMeta.eClass.ePackageiseClassHowever, when@EMetaclassis used, the directsuper()call in
the__init__constructor cannot be directly called. Instead,super(x, self)must be called:classStuff(object):def__init__(self):self.stuff=10@EMetaclassclassA(Stuff):def__init__(self,tmp):super(A,self).__init__()self.tmp=tmpa=A()asserta.stuff==10If you want to directly extends the PyEcore metamodel, the@EMetaclassis
not required, andsuper()can be used.classMyNamedElement(ENamedElement):def__init__(self,tmp=None,**kwargs):super().__init__(**kwargs)self.tmp=tmpStatic/DynamicEOperationPyEcore also supportEOperationdefinition for static and dynamic metamodel.
For static metamodel, the solution is simple, a simple method with the code is
added inside the defined class. The correspondingEOperationis created on
the fly. Theire is still some “requirements” for this. In order to be understood
as anEOperationcandidate, the defined method must have at least one
parameter and the first parameter must always be namedself.For dynamic metamodels, the simple fact of adding anEOperationinstance in
theEClassinstance, adds an “empty” implementation:>>>importpyecore.ecoreasEcore>>>A=Ecore.EClass('A')>>>operation=Ecore.EOperation('myoperation')>>>param1=Ecore.EParameter('param1',eType=Ecore.EString,required=True)>>>operation.eParameters.append(param1)>>>A.eOperations.append(operation)>>>a=A()>>>help(a.myoperation)Helponmethodmyoperation:myoperation(param1)methodofpyecore.ecore.Ainstance>>>a.myoperation('test')...NotImplementedError:Methodmyoperation(param1)isnotyetimplementedFor eachEParameter, therequiredparameter express the fact that the
parameter is required or not in the produced operation:>>>operation2=Ecore.EOperation('myoperation2')>>>p1=Ecore.EParameter('p1',eType=Ecore.EString)>>>operation2.eParameters.append(p1)>>>A.eOperations.append(operation2)>>>a=A()>>>a.operation2(p1='test')# Will raise a NotImplementedError exceptionYou can then create an implementation for the eoperation and link it to the
EClass:>>>defmyoperation(self,param1):...print(self,param1)...>>>A.python_class.myoperation=myoperationTo be able to propose a dynamic empty implementation of the operation, PyEcore
relies on Python code generation at runtime.NotificationsPyEcore gives you the ability to listen to modifications performed on an
element. TheEObserverclass provides a basic observer which can receive
notifications from theEObjectit is register in:>>>importlibraryaslib# we use the wikipedia library example>>>frompyecore.notificationimportEObserver,Kind>>>smith=lib.Writer()>>>b1=lib.Book()>>>observer=EObserver(smith,notifyChanged=lambdax:print(x))>>>b1.authors.append(smith)# observer receive the notification from smith because 'authors' is eOpposite or 'books'TheEObservernotification method can be set using a lambda as in the
previous example, using a regular function or by class inheritance:>>>defprint_notif(notification):...print(notification)...>>>observer=EObserver()>>>observer.observe(b1)>>>observer.notifyChanged=print_notif>>>b1.authors.append(smith)# observer receive the notification from b1Using inheritance:>>>classPrintNotification(EObserver):...def__init__(self,notifier=None):...super().__init__(notifier=notifier)......defnotifyChanged(self,notification):...print(notification)......>>>observer=PrintNotification(b1)>>>b1.authors.append(smith)# observer receive the notification from b1TheNotificationobject contains information about the performed
modification:new-> the new added value (can be a collection) orNoneis remove or unsetold-> the replaced value (alwaysNonefor collections)feature-> theEStructuralFeaturemodifiednotifer-> the object that have been modifiedkind-> the kind of modification performedThe different kind of notifications that can be currently received are:ADD-> when an object is added to a collectionADD_MANY-> when many objects are added to a collectionREMOVE-> when an object is removed from a collectionSET-> when a value is set in an attribute/referenceUNSET-> when a value is removed from an attribute/referenceDeep Journey Inside PyEcoreThis section will provide some explanation of how PyEcore works.EClasse Instances as FactoriesThe most noticeable difference between PyEcore and Java-EMF implementation is
the fact that there is no factories (as you probably already seen). Each EClass
instance is in itself a factory. This allows you to do this kind of tricks:>>>A=EClass('A')>>>eobject=A()# We create an A instance>>>eobject.eClass<EClassname="A">>>>eobject2=eobject.eClass()# We create another A instance>>>assertisinstance(eobject2,eobject.__class__)>>>frompyecore.ecoreimportEcoreUtils>>>assertEcoreUtils.isinstance(eobject2,A)In fact, each EClass instance create a new Pythonclassnamed after the
EClass name and keep a strong relationship towards it. Moreover, EClass
implements is acallableand each time()is called on an EClass
instance, an instance of the associated Pythonclassis created. Here is a
small example:>>>MyClass=EClass('MyClass')# We create an EClass instance>>>type(MyClass)pyecore.ecore.EClass>>>MyClass.python_classpyecore.ecore.MyClass>>>myclass_instance=MyClass()# MyClass is callable, creates an instance of the 'python_class' class>>>myclass_instance<pyecore.ecore.MyClassat0x7f64b697df98>>>>type(myclass_instance)pyecore.ecore.MyClass# We can access the EClass instance from the created instance and go back>>>myclass_instance.eClass<EClassname="MyClass">>>>assertmyclass_instance.eClass.python_classisMyClass.python_class>>>assertmyclass_instance.eClass.python_class.eClassisMyClass>>>assertmyclass_instance.__class__isMyClass.python_class>>>assertmyclass_instance.__class__.eClassisMyClass>>>assertmyclass_instance.__class__.eClassismyclass_instance.eClassThe Python class hierarchie (inheritance tree) associated to the EClass instance>>>B=EClass('B')# in complement, we create a new B metaclass>>>list(B.eAllSuperTypes())[]>>>B.eSuperTypes.append(A)# B inherits from A>>>list(B.eAllSuperTypes()){<EClassname="A">}>>>B.python_class.mro()[pyecore.ecore.B,pyecore.ecore.A,pyecore.ecore.EObject,pyecore.ecore.ENotifier,object]>>>b_instance=B()>>>assertisinstance(b_instance,A.python_class)>>>assertEcoreUtils.isinstance(b_instance,A)Importing an Existing XMI Metamodel/ModelXMI support is still a little rough on the edges, but the XMI import is on good tracks.
Currently, only basic XMI metamodel (.ecore) and model instances can be
loaded:>>>frompyecore.resourcesimportResourceSet,URI>>>rset=ResourceSet()>>>resource=rset.get_resource(URI('path/to/mm.ecore'))>>>mm_root=resource.contents[0]>>>rset.metamodel_registry[mm_root.nsURI]=mm_root>>># At this point, the .ecore is loaded in the 'rset' as a metamodel>>>resource=rset.get_resource(URI('path/to/instance.xmi'))>>>model_root=resource.contents[0]>>># At this point, the model instance is loaded!TheResourceSet/Resource/URIwill evolve in the future. At the moment, only
basic operations are enabled:create_resource/get_resource/load/save....Dynamic Metamodels HelperOnce a metamodel is loaded from an XMI metamodel (from a.ecorefile), the
metamodel root that is gathered is anEPackageinstance. To access eachEClassfrom the loaded resource, agetEClassifier(...)call must be
performed:>>>#...>>>resource=rset.get_resource(URI('path/to/mm.ecore'))>>>mm_root=resource.contents[0]>>>A=mm_root.getEClassifier('A')>>>a_instance=A()When a big metamodel is loaded, this operation can be cumbersome. To ease this
operation, PyEcore proposes an helper that gives a quick access to eachEClasscontained in theEPackageand its subpackages. This helper is theDynamicEPackageclass. Its creation is performed like so:>>>#...>>>resource=rset.get_resource(URI('path/to/mm.ecore'))>>>mm_root=resource.contents[0]>>>frompyecore.utilsimportDynamicEPackage>>>MyMetamodel=DynamicEPackage(mm_root)# We create a DynamicEPackage for the loaded root>>>a_instance=MyMetamodel.A()>>>b_instance=MyMetamodel.B()Adding External Metamodel ResourcesExternal resources for metamodel loading should be added in the resource set.
For example, some metamodels use the XMLType instead of the Ecore one.
The resource creation should be done by hand first:int_conversion=lambdax:int(x)# translating str to int durint load()String=Ecore.EDataType('String',str)Double=Ecore.EDataType('Double',int,0,from_string=int_conversion)Int=Ecore.EDataType('Int',int,from_string=int_conversion)IntObject=Ecore.EDataType('IntObject',int,None,from_string=int_conversion)Boolean=Ecore.EDataType('Boolean',bool,False,from_string=lambdax:xin['True','true'],to_string=lambdax:str(x).lower())Long=Ecore.EDataType('Long',int,0,from_string=int_conversion)EJavaObject=Ecore.EDataType('EJavaObject',object)xmltype=Ecore.EPackage()xmltype.eClassifiers.extend([String,Double,Int,EJavaObject,Long,Boolean,IntObject])xmltype.nsURI='http://www.eclipse.org/emf/2003/XMLType'xmltype.nsPrefix='xmltype'xmltype.name='xmltype'rset.metamodel_registry[xmltype.nsURI]=xmltype# Then the resource can be loaded (here from an http address)resource=rset.get_resource(HttpURI('http://myadress.ecore'))root=resource.contents[0]Metamodel References by ‘File Path’If a metamodel references others in a ‘file path’ manner (for example, a
metamodelAhad some relationship towards aBmetamodel like this:../metamodelb.ecore), PyEcore requires that theBmetamodel is loaded
first and registered against the metamodel path URI like (in the example, against
the../metamodelb.ecoreURI).>>># We suppose that the metamodel A.ecore has references towards B.ecore...# '../../B.ecore'. Path of A.ecore is 'a/b/A.ecore' and B.ecore is '.'>>>resource=rset.get_resource(URI('B.ecore'))# We load B.ecore first>>>root=resource.contents[0]>>>rset.metamodel_registry['../../B.ecore']=root# We register it against the 'file path' uri>>>resource=rset.get_resource(URI('a/b/A.ecore'))# A.ecore now loads just fineAdding External resourcesWhen a model reference another one, they both need to be added inside the same
ResourceSet.rset.get_resource(URI('uri/towards/my/first/resource'))resource=rset.get_resource(URI('uri/towards/my/secon/resource'))If for some reason, you want to dynamically create the resource which is
required for XMI deserialization of another one, you need to create an empty
resource first:# Other model is 'external_model'resource=rset.create_resource(URI('the/wanted/uri'))resource.append(external_model)Exporting an Existing XMI ResourceAs for the XMI import, the XMI export (serialization) is still somehow very
basic. Here is an example of how you could save your objects in a file:>>># we suppose we have an already existing model in 'root'>>>frompyecore.resources.xmiimportXMIResource>>>frompyecore.resourcesimportURI>>>resource=XMIResource(URI('my/path.xmi'))>>>resource.append(root)# We add the root to the resource>>>resource.save()# will save the result in 'my/path.xmi'>>>resource.save(output=URI('test/path.xmi'))# save the result in 'test/path.xmi'You can also use aResourceSetto deal with this:>>># we suppose we have an already existing model in 'root'>>>frompyecore.resourcesimportResourceSet,URI>>>rset=ResourceSet()>>>resource=rset.create_resource(URI('my/path.xmi'))>>>resource.append(root)>>>resource.save()Dealing with JSON ResourcesPyEcore is also able to load/save JSON models/metamodels. The JSON format it uses
tries to be close from the one described in theemfjson-jacksonproject.
The way the JSON serialization/deserialization works, on a user point of view,
is pretty much the same than the XMI resources, but as the JSON resource factory
is not loaded by default (for XMI, it is), you have to manually register it
first. The registration can be performed globally or at aResourceSetlevel.
Here is how to register the JSON resource factory for a givenResourceSet.>>>frompyecore.resourcesimportResourceSet>>>frompyecore.resources.jsonimportJsonResource>>>rset=ResourceSet()# We have a resource set>>>rset.resource_factory['json']=lambdauri:JsonResource(uri)# we register the factory for '.json' extensionsAnd here is how to register the factory at a global level:>>>frompyecore.resourcesimportResourceSet>>>frompyecore.resources.jsonimportJsonResource>>>ResourceSet.resource_factory['json']=lambdauri:JsonResource(uri)Once the resource factory is registered, you can load/save models/metamodels
exactly the same way you would have done it for XMI. Check the XMI section to
see how to load/save resources using aResourceSet.NOTE:Currently, the Json serialization is performed using the defaut Pythonjsonlib. It means that your PyEcore model is first translated to a hugedictbefore the export/import. For huge models, this could implies a memory
and a performance cost.Creating Your own URIPyEcore usesURIto deal with ‘stream’ opening/reading/writing/closing.
AnURIis used to give a file-like object to aResource. By default,
the basicURIprovides a way to read and write to files on your system (if
the path used is a file system path, abstract paths or logical ones are not
serialized onto the disk). Another,HttpURIopens a file-like object from
a remote URL, but does not give the ability to write to a remote URL.As example, in this section, we will create aStringURIthat gives the
resource the ability to read/write from/to a Python String.classStringURI(URI):def__init__(self,uri,text=None):super(StringURI,self).__init__(uri)iftextisnotNone:self.__stream=StringIO(text)defgetvalue(self):returnself.__stream.getvalue()defcreate_instream(self):returnself.__streamdefcreate_outstream(self):self.__stream=StringIO()returnself.__streamTheStringURIclass inherits fromURI, and adds a new parameter to the
constructor:text. In this class, the__streamattribute is handled in
theURIbase class, and inherited from it.The constructor builds a newStringIOinstance if a text is passed to thisURI. This parameter is used when a string must be decoded.  In this context,
thecreate_instream()method is used to provide the__streamto read
from. In this case, it only returns the stream created in the constructor.Thecreate_outstream()methods is used to create the output stream. In this
case, a simpleStringIOinstance is created.In complement, thegetvalue()method provides a way of getting the result
of the load/save operation. The following code illustrate how theStringURIcan be used:# we have a model in memory in 'root'uri=StringURI('myuri')resource=rset.create_resource(uri)resource.append(root)resource.save()print(uri.getvalue())# we get the result of the serializationmystr=uri.getvalue()# we assume this is a new stringuri=StringURI('newuri',text=mystr)resource=rset.create_resource(uri)resource.load()root=resource.contents[0]# we get the root of the loaded resourceDeleting ElementsDeleting elements in EMF is still a sensible point because of all the special
model “shape” that can impact the deletion algorithm. PyEcore supports two main
way of deleting elements: one is a real kind of deletion, while the other is
more less direct.Thedelete()methodThe first way of deleting element is to use thedelete()method which is
owned by everyEObject/EProxy:>>># we suppose we have an already existing element in 'elem'>>>elem.delete()This call is also recursive by default: every sub-object of the deleted element
is also deleted. This behavior is the one by default as a “containment”
reference is a strong constraint.The behavior of thedelete()method can be confusing when there is manyEProxyin the game. As theEProxyonly gives a partial view of the
object while it is not resolved, thedelete()can only correctly remove
resolved proxies. If a resource or many elements that are referenced in many
other resources must be destroyed, in order to be sure to not introduce broken
proxies, the best solution is to resolve all the proxies first, then to delete
them.Removing an element from it’s containerYou can also, in a way, removing elements from a model using the XMI
serialization. If you want to remove an element from a Resource, you have to
remove it from its container. PyEcore does not serialize elements that are not
contained by aResourceand each reference to this ‘not-contained’ element
is not serialized.Modifying Elements Using CommandsPyEcore objects can be modified as shown previously, using basic Python
operators, but these mofifications cannot be undone. To do so, it is required to
useCommandand aCommandStack. Each command represent a basic action
that can be performed on an element (set/add/remove/move/delete):>>>frompyecore.commandsimportSet>>># we assume have a metamodel with an A EClass that owns a 'name' feature>>>a=A()>>>set=Set(owner=a,feature='name',value='myname')>>>ifset.can_execute:...set.execute()>>>a.namemynameIf you use a simple command withtoutCommandStack, thecan_executecall
is mandatory! It performs some prior computation before the actual command
execution. Each executed command also supports ‘undo’ and ‘redo’:>>>ifset.can_undo:...set.undo()>>>asserta.nameisNone>>>set.redo()>>>asserta.name=='myname'As for theexecute()method, thecan_undocall must be done before
calling theundo()method. However, there is nocan_redo, theredo()call can be mad right away after an undo.To compose all of these commands, aCompoundcan be used. Basically, aCompoundacts as a list with extra methods (execute,undo,redo…):>>>frompyecore.commandsimportCompound>>>a=A()# we use a new A instance>>>c=Compound(Set(owner=a,feature='name',value='myname'),...Set(owner=a,feature='name',value='myname2'))>>>len(c)2>>>ifc.can_execute:...c.execute()>>>a.namemyname2>>>ifc.can_undo:...c.undo()>>>asserta.nameisNoneIn order to organize and keep a stack of each played command, aCommandStackcan be used:>>>frompyecore.commandsimportCommandStack>>>a=A()>>>stack=CommandStack()>>>stack.execute(Set(owner=a,feature='name',value='myname'))>>>stack.execute(Set(owner=a,feature='name',value='myname2'))>>>stack.undo()>>>asserta.name=='myname'>>>stack.redo()>>>asserta.name=='myname2'Here is a quick review of each command:Set–> sets afeatureto avaluefor anownerAdd–> adds avalueobject to afeaturecollection from anownerobject (Add(owner=a,feature='collection',value=b)). This command can also add avalueat a dedicatedindex(Add(owner=a,feature='collection',value=b, index=0))Remove–> removes avalueobject from afeaturecollection from anowner(Remove(owner=a,feature='collection',value=b)). This command can also remove an object located at anindex(Remove(owner=a,feature='collection',index=0))Move–> moves avalueto ato_indexposition inside afeaturecollection (Move(owner=a,feature='collection',value=b, to_index=1)). This command can also move an element from afrom_indexto ato_indexin a collection (Move(owner=a,feature='collection',from_index=0, to_index=1))Delete–> deletes an elements and its contained elements (Delete(owner=a))Dynamically Extending PyEcore Base ClassesPyEcore is extensible and there is two ways of modifying it: either by extending
the basic concepts (asEClass,EStructuralFeature…), or by directly
modifying the same concepts.Extending PyEcore Base ClassesTo extend the PyEcore base classes, the only thing to do is to create newEClassinstances that have some base classes assuperclass.
The following excerpt shows how you can create anEClassinstance that
will add supportEAnnotationto each created instance:>>>frompyecore.ecoreimport*>>>A=EClass('A',superclass=(EModelElement.eClass))# we need to use '.eClass' to stay in the PyEcore EClass instance level>>>a=A()# we create an instance that has 'eAnnotations' support>>>a.eAnnotationsEOrderedSet()>>>annotation=EAnnotation(source='testSource')>>>annotation.details['mykey']=33>>>a.eAnnotations.append(annotation)>>>EOrderedSet([<pyecore.ecore.EAnnotationobjectat0x7fb860a99f28>])If you want to extendEClass, the process is mainly the same, but there is a
twist:>>>frompyecore.ecoreimport*>>>NewEClass=EClass('NewEClass',superclass=(EClass.eClass))# NewEClass is an EClass instance and an EClass>>>A=NewEClass('A')# here is the twist, currently, EClass instance MUST be named>>>a=A()# we can create 'A' instance>>>a<pyecore.ecore.Aat0x7fb85b6c06d8>Modifying PyEcore Base ClassesPyEcore let you dynamically add new features to the base class and thus
introduce new feature for base classes instances:>>>frompyecore.ecoreimport*>>>EClass.new_feature=EAttribute('new_feature',EInt)# EClass has now a new EInt feature>>>A=EClass('A')>>>A.new_feature0>>>A.new_feature=5>>>A.new_feature5DependenciesThe dependencies required by pyecore are:ordered-set which is used for theorderedanduniquecollections expressed in the metamodel,lxml which is used for the XMI parsing.These dependencies are directly installed if you choose to usepip.Run the TestsTests usespy.testand ‘coverage’. Everything is driven byTox, so in order
to run the tests simply run:$toxLiberty Regarding the Java EMF ImplementationThere is some meta-property that could be missing inside PyEcore. If you see one missing, please open a new ticket!Proxies are not “removed” once resolved as in the the Java version, instead they acts as transparent proxies and redirect each calls to the ‘proxied’ object.PyEcore is able to automatically load some model/metamodel dependencies on its own.StateIn the current state, the project implements:the dynamic/static metamodel definitions,reflexive API,inheritance,enumerations,abstract metaclasses,runtime typechecking,attribute/reference creations,collections (attribute/references with upper bound set to-1),reference eopposite,containment reference,introspection,select/reject on collections,Eclipse XMI import (partially, only single root models),Eclipse XMI export (partially, only single root models),simple notification/Event system,EOperations support,code generator for the static part,EMF proxies (first version),object deletion (first version),EMF commands (first version),EMF basic command stack,EMF very basic Editing Domain,JSON import (simple JSON format),JSON export (simple JSON format),introduce behavior @runtime,resources auto-load for some cross-references,derived collections,multiple roots ressources,xsi:schemaLocationsupport for XMI resources,URI mapper like.The things that are in the roadmap:new implementation ofEOrderedSet,EList,ESetandEBag,new implementation ofEStringToStringMapEntryandEFeatureMapEntry,improve documentation,copy/paste (?).Existing ProjectsThere is not so much projects proposing to handle model and metamodel in Python.
The only projects I found are:PyEMOF (http://www.lifl.fr/~marvie/software/pyemof.html)EMF4CPP (https://github.com/catedrasaes-umu/emf4cpp)PyEMOFUC (http://www.istr.unican.es/pyemofuc/index_En.html)PyEMOF proposes an implementation of the OMG’s EMOF in Python. The project
targets Python2, only supports Class/Primitive Types (no Enumeration), XMI
import/export and does not provide a reflexion layer. The project didn’t move
since 2005.EMF4CPP proposes a C++ implementation of EMF. This implementation also
introduces Python scripts to call the generated C++ code from a Python
environment. It seems that the EMF4CPP does not provide a reflexive layer
either.PyEMOFUC proposes, like PyEMOF, a pure Python implementation of the OMG’s EMOF.
If we stick to a kind of EMF terminology, PyEMOFUC only supports dynamic
metamodels and seems to provide a reflexive layer. The project does not appear
seems to have moved since a while.ContributorsThanks for making PyEcore better!Mike Pagel (@moltob), which is also the author
ofpyecoregenandpymultigen(on which pyecoregen is based)Terry Kingston (@TerryKingston)Afonso Pinto (@afonsobspinto)Andy (@CFAndy)Additional ResourcesThis articleon the blog of the Professor Jordi Cabot gives more information and
implementations details about PyEcore.