How to get the associations between classes with Py4C?

Thank you @YvanLussaud

Any idea regarding Question #3?

Getting this error on this line:
for element in e_inverse(aClassInChildrenPackage, “type”):

org.eclipse.ease.ScriptExecutionException: Traceback (most recent call last):
File “workspace://Python4Capella/MBPC/test.py”, line 41, in
_pyease_string_types = (basestring,)
File “workspace://Python4Capella/java_api/EMF_API.py”, line 36, in e_inverse
from six import integer_types as _pyease_integer_types
File “<…>”, line 57, in eInverse
File “C:\SMW\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\java_gateway.py”, line 1313, in call
args_command, temp_args = self._build_args(*args)
File “C:\SMW\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\java_gateway.py”, line 1283, in _build_args
[get_command_part(arg, self.pool) for arg in new_args])
File “C:\SMW\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\java_gateway.py”, line 1283, in
[get_command_part(arg, self.pool) for arg in new_args])
File “C:\SMW\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\protocol.py”, line 298, in get_command_part
command_part = REFERENCE_TYPE + parameter._get_object_id()
AttributeError: ‘Class’ object has no attribute ‘_get_object_id’

Added “.get_java_object()”:

  	elements = e_inverse(aClassInChildrenPackage.get_java_object(), "type")
  	print (len(elements))
  	for element in e_inverse(elements, "type"):

The error is gone but there are no elements returned.

Any idea why? Tx

For question 3 I think I’m missing a bit of context and maybe what is the actual question.

and for e_invers() the passed object should be the Java object, my bad. It should look like this:

e_inverse(myClass.get_java_object(), “type”)

This error append when a Python object is passed to a Java method that expect a Java Object (see this page for more details). But in the case of Python4Capella you can get the Java object with get_java_object().

Hi,

I will clarify my question #3, “In my test data, I have only one level of Generalization and in your code, the returned mySuperClass if correct but mySubClass is the class I am looking the specialization/generalization links to/from. In my test data, mySubClass should be “None”, I think”.

In my model: Class A is specialized by Class B
When I apply the code to Class B, mySuperClass = “Class A” which is fine but mySubClass = “Class B” where I would have expected “None”.

I would love also to understand why the e_inverse function is not returning anything:
Added “.get_java_object()”:

  	elements = e_inverse(aClassInChildrenPackage.get_java_object(), "type")
  	print (len(elements))
  	for element in e_inverse(elements, "type"):

Thank you

OK, my mistake; when it prints print("Generalization subClass: " + mySubClass.get_name()), It is actually printing the class name from which the generalization link is leaving, so what I meant is that the Class is the subclass of the SuperClass, not that the code is giving you the subclasses of the current class.

About your question on e_inverse returning nothing: there is no “type” relations between classes in Capella, so the code is giving you nothing. The easiest way for you to navigate in reverse Associations and Generalizations is probably this (it is using the queries from the semantic browser that already calculate the reverse links):

for element in capella_query_by_name(myClass, "Typing Elements"):
    if isinstance(element, Class):
        print(element.get_name())

for element in capella_query_by_name(myClass, "Generalizing Elements"):
    if isinstance(element, Class):
        print(element.get_name())

Stephane Lacrampe
Obeo Canada

Thanks @StephaneLacrampe

From my trials:

  • capella_query_by_name(myClass, "Typing Elements") returns Property objects so the ```
    isinstance(element, Class) is never true

How Typing Elements related to Associations?

Thank you.

“Typing Elements” is the name of a Semantic Browser query. Open the Semantic Browser view, click on one of your Class that is a “SuperClass” in the Project Explorer, and you should see in the Semantic Browser view, on the left part, the list of "Typed Elements on your object.

Very clear and the Semantic Browser is a very useful tool that I am going to use a lot I think.

Still, I don’t understand why none of the returned objects are of Class type but are of Property type. (but I might doing something wrong). Here is the Python function that I wrote based on your inputs:

def getAllClassesAndTheirAssociationsInDataPackageAndBelow(aDataPackage: DataPkg):
    allClassesAndTheirAssociationsInDataPackage = []
    print(aDataPackage.get_name() + " Data Package:")
    #Get All Classes from the data package in the Operational Analysis
    classesInPackage = aDataPackage.get_owned_classes()
    #Get the first Class
    if (classesInPackage != None):
        for aClassInPackage in classesInPackage:
            print("     Found this class: " + aClassInPackage.get_name())
            #Get all properties (associations)
            myAssociations = aClassInPackage.get_contained_properties()
            print (str(len(myAssociations)) + " associations")
            for anAssociation in myAssociations:
                #Get the types of the Property(
                myAssociationTypes = anAssociation.get_type()
                #Get the first type
                myAssociationType = myAssociationTypes[0]
                #Print the name
                print("         The class has outgoing association to: " + myAssociationType.get_name())
            typingElements = capella_query_by_name(aClassInPackage, "Typing Elements")
            print (str(len(typingElements)) + " typing elements")
            for aTypingElement in typingElements:
                print(aTypingElement[0].get_type())
                if isinstance(aTypingElement, Class):
                    print("         The class has incoming association from: " + aTypingElement.get_name())
            #Get all Generalization links
            generalizingElements = capella_query_by_name(aClassInPackage, "Generalizing Elements")
            print (str(len(generalizingElements)) + " generalizing elements")
            for aGeneralizingElement in generalizingElements:
                if isinstance(aGeneralizingElement, Class):
                    print("         The class has incoming association from: " + aGeneralizingElement.get_name())
            #Get all Generalization links
            classGeneralizations = aClassInPackage.get_java_object().getOwnedGeneralizations()
            print (str(len(classGeneralizations)) + " OwnedGeneralizations")
            if (len(classGeneralizations) != 0):
                for aClassGeneralization in classGeneralizations:
                    #Get the source
                    aSubClass = Class(aClassGeneralization.getSub())
                    #Get the target
                    aSuperClass = Class(aClassGeneralization.getSuper())
                    print("         Generalization found: subClass: " + aSubClass.get_name() + " and superClass: " +  aSuperClass.get_name())
                    
        for aChildDataPackage in aDataPackage.get_owned_data_pkgs():
            childrenClassesAndAssociations = []
            childrenClassesAndAssociations = getAllClassesAndTheirAssociationsInDataPackageAndBelow(aChildDataPackage)
            for achildrenClasseAndAssociations in childrenClassesAndAssociations:
                allClassesAndTheirAssociationsInDataPackage.append(achildrenClasseAndAssociations)
    return allClassesAndTheirAssociationsInDataPackage

I understand that the core Obeo team cannot reply to all posts here so I hope that the Capella community can help here:

capella_query_by_name(myClass, "Typing Elements") returns Property objects so I’d like to get the Class object that I see in the Semantic Browser.

What would be the method to apply on the returned object?

Thank you.

This call returns a Class object :grinning:

Using the Semantic Browser on the different Class in your model, you should see when capella_query_by_name(myClass, "Typing Elements") returns Properties or Class objects (it depends if the association/generalization is leaving or arriving on your Class).

The reason why your code was not working is because in:

            for aTypingElement in typingElements:
                print(aTypingElement[0].get_type())

should be

            for aTypingElement in typingElements:
                print(aTypingElement.get_type())

since aTypingElement is not an array but the element in your loop lopping on an array.

Strange design choice.

What if it returns a Property? How can I navigate to the Class object?

Finally, Obeo may not answer all questions, sometimes we may not have time, but also we do not know all answers!

I have tried both syntax

Maybe Associations, regardless of the incoming/outgoing direction, are in Properties while Generalizations are in Classes?

First of all: Please be aware that I do not know exactly what you are trying to do. I am answering some of your questions, but lacking of context, I may not have all the best answers.
For example, in this post How to get the associations between classes with Py4C? - #18 by StephaneLacrampe I am giving options because the e_inverse that was suggested was not working.

If you want to navigate associations and generalizations between classes, there are 4 things to navigate: associations that leave, associations that arrive, generalizations that leave, and generalizations that arrive. For that, I believe that using the Semantic Browser queries is the easiest for you as it avoids you from having to navigate all detailed Capella objects. In theory, if the Python4Capella simplified API was complete on this part of the Capella meta-model, you should not have to go through these queries. But this is not the case, hence the solution I proposed.

So, the easiest way for you to see what queries you should call is to create 4 classes, with one association and one generalization between each pair, and see what the queries return by clicking on each class with the Semantic Browser view open. I would also make sure that make visible all model elements in your project explorer so that you have more detail on the model structure (in the project view, select “Quick Filter->Deselect All Filters”)

I see 4 queries corresponding to your need:

    1. (leaving Association) : Properties
    1. (leaving Generalization): Generalized Elements
    1. (arriving Association) : Typing Elements
    1. (arriving Generalization): Generalizing Elements

If you look at what items (2) and (4) are giving as results in the Semantic Browser, you’re going to get Classes, so you’re good even if these are 2 different queries.
If you look at items (1) and (3), the queries and give you a Property as a result. If you want to get the Class out of a Property, you can use get_type()

I hope it helps.
Stephane LACRAMPE

1 Like

Please note I modified my initial previous response.

Please also note that there are other types of associations between Classes (Aggregation, Composition) and also that there are ways to create Properties in Classes. I do not know precisely how it all works, so some of the code you are writing for navigating Associations and Generalizations may fail when adding these other concepts to your model. It would need some testing to see how these other elements affect the queries results.

1 Like