SynBioDex / pySBOL

SWIG-Python wrappers implementing SBOL 2.2
Apache License 2.0
25 stars 8 forks source link

pySBOL Interaction does not support advertised argument #121

Open rpgoldman opened 4 years ago

rpgoldman commented 4 years ago

I tried to construct an Interaction using the documented API, and got this error:

>>> inter = sbol.Interaction(types=["http://identifiers.org/biomodels.sbo/SBO:0000170"], name="BE stimulates r3_gRNA_Gene", \
    identity="https://hub.sd2e.org/user/sd2e/design/beta_estradiol_stimulates_r3_gRNA/1", displayId="beta_estradiol_stimulates_r3_gRNA") 
... Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: __init__() got an unexpected keyword argument 'types'
>>> 

I tried a few modifications, treating the URI (identity) and types as positional, instead of keyword, but no dice.

tcmitchell commented 4 years ago

I think the documentation is misleading. All of the bullets under what appears to be the constructor are really the attributes of the class, not the arguments to a constructor.

Try something like this:

>>> sbol.setHomespace('https://hub.sd2e.org/user/sd2e/design')
>>> display_id = 'beta_estradiol_stimulates_r3_gRNA'
>>> inter = sbol.Interaction(sbol.SBOL_INTERACTION, display_id, sbol.SBO_STIMULATION)
>>> inter.name = 'BE stimulates r3_gRNA_Gene'
>>> 
>>> 
>>> inter.identity
'https://hub.sd2e.org/user/sd2e/design/Interaction/beta_estradiol_stimulates_r3_gRNA/1'
>>> inter.displayId
'beta_estradiol_stimulates_r3_gRNA'
>>> inter.name
'BE stimulates r3_gRNA_Gene'
>>> inter.types
['http://identifiers.org/biomodels.sbo/SBO:0000170']

Note that I used sbol.SBO_STIMULATION instead of the raw URI. This avoids typos, and makes the intent clearer to those who are reading the code and are not in the know about the various biomodels.so.

Let us know if that gets you unstuck, or if you have more questions.

rpgoldman commented 4 years ago

Thanks, @tcmitchell -- that's just what I needed.

Note that at least in the numpy style, it's considered ok to add docs for the initialization method to the class doc string. So just adding a Parameters section to that doc string would really help.

It seems odd that we need to pass sbol.SBOL_INTERACTION to the constructor for Interaction -- shouldn't it know that it's an interaction?

Anyway, yes, this has me at least partially unstuck! Thanks again.

tcmitchell commented 4 years ago

Note that at least in the numpy style, it's considered ok to add docs for the initialization method to the class doc string. So just adding a Parameters section to that doc string would really help.

I agree. But the documentation that I think you're reading (https://pysbol2.readthedocs.io/en/latest/API.html#sbol.libsbol.Interaction) is derived from C++ code, not from Python docstrings. So it's not quite so simple with the pySBOL library. As the pure python version reaches maturity, we hope things like this are a little easier.

It seems odd that we need to pass sbol.SBOL_INTERACTION to the constructor for Interaction -- shouldn't it know that it's an interaction?

I agree. I have a slightly different case with the Implementation class that similarly requires a type_uri as the first argument to the constructor. It's on my list of things to ask @bbartley. I don't know why some classes require this and some (ComponentDefinition, ModuleDefinition) do not. I'll bet there's a reason, so we'll have to have @bbartley weigh in on this aspect.

rpgoldman commented 4 years ago

In order to get me unstuck, would it be possible to assemble a cheat sheet of initialization args? If it helps, I'm only concerned right now with adding enough extra stuff to add a stimulation relationship between a chemical and a gene. So I note that would require only a few entries for the cheat sheet for now. Thanks!

tcmitchell commented 4 years ago

I'd be happy to give that a try. I'm just source diving in the C++ and then experimenting in a Python interpreter. Your explanation does not give me enough info to work from. I do not possess biological knowledge. If you pass along class names that you're having trouble with, I can give it a try.

rpgoldman commented 4 years ago

Well, that will be the blind leading the one-eyed! 😉

Seriously, I think what I need is to make a Participation. I believe the participations are all nested inside the Interaction.

Inside the Participation, I need to put a FunctionalComponent. I think (hope) these three are enough to do the job for me.

tcmitchell commented 4 years ago

Here is the relevant bit of the C++ Participant class:

        /// @param uri A full URI including a scheme, namespace, and identifier.  If SBOLCompliance configuration is enabled, then this argument is simply the displayId for the new object and a full URI will automatically be constructed.  
        /// @param participant A reference to the participating FunctionalComponent in the parent Interaction           
        Participation(std::string uri = "example", std::string participant = "", std::string version = VERSION_STRING)

And here's the relevant bit of C++ for FunctionalComponent:

        /// Construct a FunctionalComponent. If operating in SBOL-compliant mode, use ModuleDefinition::functionalComponents::create instead.                                                                                                  
        /// @param A full URI including a scheme, namespace, and identifier.  If SBOLCompliance configuration is enabled, then this argument is simply the displayId for the new object and a full URI will automatically be constructed.      
        /// @param definition A URI referring to the ComponentDefinition that defines this instance                     
        /// @param access Flag indicating whether the FunctionalComponent can be referred to remotely by a MapsTo       
        /// @param direction The direction property specifies whether a FunctionalComponent serves as an input, output, both, or neither for its parent ModuleDefinition object                                                                
        /// @param version An arbitrary version string. If SBOLCompliance is enabled, this should be a Maven version string of the form "major.minor.patch".                                                                                   
        FunctionalComponent(std::string uri = "example", std::string definition = "", std::string access = SBOL_ACCESS_PUBLIC, std::string direction = SBOL_DIRECTION_NONE, std::string version = VERSION_STRING)

I think your best bet is to create both with a shortened URI as in my Interaction example above, and then set values on attributes after that. You can experiment with setting things up in the constructor if that's what you prefer. Those arguments should be there for convenience, I don't believe any of the parameters are required.

tcmitchell commented 4 years ago

Here's some quick Python to demonstrate that the constructor args are all optional:

>>> import sbol
>>> sbol.setHomespace('https://hub.sd2e.org/user/sd2e/design')
>>> 
>>> p = sbol.Participation()
>>> p.identity
'https://hub.sd2e.org/user/sd2e/design/Participation/example/1'
>>> p = sbol.Participation('my_participation')
>>> p.identity
'https://hub.sd2e.org/user/sd2e/design/Participation/my_participation/1'
>>> 
>>> fc = sbol.FunctionalComponent()
>>> fc.identity
'https://hub.sd2e.org/user/sd2e/design/FunctionalComponent/example/1'
>>> fc = sbol.FunctionalComponent('my_func_comp')
>>> fc.identity
'https://hub.sd2e.org/user/sd2e/design/FunctionalComponent/my_func_comp/1'
>>> 
rpgoldman commented 4 years ago

Follow-up question -- I see that I need to add these entities to the Document object: they don't get added automatically.

That is straightforward for my new ModuleDefinition, which I add with Document.addModuleDefinition per the docs: " if the user wishes to write out a file with all the information contained in their object, they must first add it to the Document. This is done using add methods. The names of these methods follow a simple pattern, simply “add” followed by the type of object."

But what do I do about Interaction and Participation definitions? There are no corresponding add methods? I thought maybe they should use addComponentDefinition, but:

>>> issubclass(sbol.Interaction, sbol.ComponentDefinition)
False
>>> issubclass(sbol.Participation, sbol.ComponentDefinition)
False

I looked at the SBOL spec, and a Participation seems to be top level -- its only parent is Identified.

if not, how do I add them? Or is it sufficient to add just the ModuleDefinition, and assume that all these properties will be carried along?

Thanks!

bbartley commented 4 years ago

Hi @rpgoldman,

Note that Participation is not TopLevel:

>>> issubclass(sbol.Participation, sbol.TopLevel)
False

You don't add a Participation to a Document directly...you add it to a parent Interaction. There are two alternative ways to do this:

>>> i = Interaction('i')
>>> p1 = i.participations.create('p1')

or:

>>> i = Interaction('i')
>>> p1 = Participation('p1')
>>> i.participations.add(p1)

Hope that helps!

rpgoldman commented 4 years ago

@bbartley Sorry: one more -- do I need to add the parent Interaction? And if so, how is this to be done? Presumably since it's not top level, either, Interaction also does not need to be added? That does not seem to work for me. In my code snippet (see Slack, since this isn't my data to post to GitHub), I create the interaction, and add it to the ModuleDefinition, but it does not appear in the resulting XML file, although the containing ModuleDefinition does appear.

bbartley commented 4 years ago

Use:

module_definition.interactions.add(i)
rpgoldman commented 4 years ago

@bbartley I did this:

module_def.interactions = [inter]

Do you think that's the problem? And, if so, maybe pySBOL should make it an error if someone tries to set the interactions in this way.