Tutorial 03: Sessions and variables

In this tutorial we will explain how objects and the variables assigned to them behave with different sessions.

Background

Some use cases of OSP-core, like coupling and linking, require interaction between multiple sessions. Even using just one wrapper usually means working with the default CoreSession initially, and then the wrapper session.

When an object from one session is added to a different one, a deepcopy of the object is carried out. This means that both objects are technically the same at that point (same uid, oclas, relationships…), but reside in different memory areas and can theoretically differ in the future. The purpose of this behaviour is to allow selective synching and enable different sessions to have different states of an instance.

Variables that point to an object in the origin session will keep pointing to it (and not to the added session) unless explicitedly updated.

Here we will try to explain said behaviour with simple illustrative examples.

Note: This tutorial is not meant to be run like the others. The session classes and ontology entities are not real implementations. However, the behaviour shown will be the same as that of a real setup.

Let’s show an example

We start by importing the necessary components, namely the session and a sample namespace:

[ ]:
from osp.core import namespace
from osp.wrappers.wrapper_x import SessionX
from osp.wrappers.wrapper_y import SessionY

We can now instantiate the Session objects and bind them to wrapper instances:

[ ]:
sess_x = SessionX()
sess_y = SessionY()

wrapper_x = namespace.WrapperX(session=sess_x)
wrapper_y = namespace.WrapperY(session=sess_y)

Next, we will add some entities to the wrappers. For simplicity, we will use the session parameter available to all entities in their initialisation. This is just to explicitly work with SessionX and SessionY, without worrying about the default CoreSession.

[ ]:
a = namespace.A(session=sess_x, name="a")
wrapper_x.add(a)

b = namespace.B(session=sess_y, name="b")
wrapper_y.add(b)

Let’s add a to wrapper_y:

[ ]:
wrapper_y.add(a)

After the previous statement, both wrappers have an identical version of a. However, they are not linked together. This means one can be changed and the other one won’t be. Also, the variable a points to the instance inside wrapper_x, and there is no reference to the one inside wrapper_y. We can test that:

[4]:
a.name = "a updated"

a_in_y = wrapper_y.get(a.uid)

print("Name of a: ", a.name)
print("Name of a_in_y: ", a_in_y.name)
Name of a: a updated
Name of a_in_y: a

As you can see, changing the name through a, which points to the object in SessionX, only changed one version. Also bear in mind that SessionX and SessionY represent two arbitrary sessions, so this situation could arise when adding objects to a wrapper from the normal instantiation (remember that objects reside in the CoreSession by default).

In order to get a reference to the added object, you can assign the return of the call to add to a variable. For example:

[ ]:
b = wrapper_x.add(b)

That way we can modify the name of b in wrapper_x more easily:

[5]:
b.name = "b updated"

b_in_y = wrapper_y.get(b.uid)

print("Name of b: ", b.name)
print("Name of b_in_y: ", b_in_y.name)
Name of b: b updated
Name of b_in_y: b

Summary

In short, these are the things you should be aware of:

  • Adding an object to a different session creates a deepcopy.
  • Two versions of the same instance (same uid and type) in different sessions are not automatically synced.
  • Variables point to an object in a session, and will not change when the objects are added somewhere else.
  • If you want to quickly assign a variable to an object in a new session, you can capture the return of the add call.