Skip to content
Snippets Groups Projects
python-v3-api.md 52.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    # Python (V3 API) - pyBIS!
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    pyBIS is a Python module for interacting with openBIS. pyBIS is designed to be most useful in a [Jupyter Notebook](https://jupyter.org) or IPython environment, especially if you are developing Python scripts for automatisation. Jupyter Notebooks offer some sort of IDE for openBIS, supporting TAB completition and immediate data checks, making the life of a researcher hopefully easier.
    
    ## Dependencies and Requirements
    
    - pyBIS relies the openBIS API v3
    - openBIS version 16.05.2 or newer is required
    - 19.06.5 or later is recommended
    - pyBIS uses Python 3.6 or newer and the Pandas module
    
    ## Installation
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    pip install --upgrade pybis
    ```
    
    That command will download install pyBIS and all its dependencies. If pyBIS is already installed, it will be upgraded to the latest version.
    
    If you haven't done yet, install Jupyter and/or Jupyter Lab (the next Generation of Jupyter):
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    pip install jupyter
    pip install jupyterlab
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ## General Usage
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    ### TAB completition and other hints in Jupyter / IPython
    
    - in a Jupyter Notebook or IPython environment, pybis helps you to enter the commands
    - After every dot `.` you might hit the `TAB` key in order to look at the available commands.
    - if you are unsure what parameters to add to a , add a question mark right after the method and hit `SHIFT+ENTER`
    - Jupyter will then look up the signature of the method and show some helpful docstring
    
    ### Checking input
    
    - When working with properties of entities, they might use a **controlled vocabulary** or are of a specific **property type**.
    - Add an underscore `_` character right after the property and hit `SHIFT+ENTER` to show the valid values
    - When a property only acceps a controlled vocabulary, you will be shown the valid terms in a nicely formatted table
    - if you try to assign an **invalid value** to a property, you'll receive an error immediately
    
    ### Glossary
    
    - **spaces:** used for authorisation eg. to separate two working groups. If you have permissions in a space, you can see everything which in that space, but not necessarily in another space (unless you have the permission).
    - **projects:** a space consists of many projects.
    - **experiments / collections:** a projects contain many experiments. Experiments can have _properties_
    - **samples / objects:** an experiment contains many samples. Samples can have _properties_
    - **dataSet:** a dataSet which contains the actual _data files_, either pyhiscal (stored in openBIS dataStore) or linked
    - **attributes:** every entity above contains a number of attributes. They are the same accross all instances of openBIS and independent of their type.
    - **properties:** Additional specific key-value pairs, available for these entities:
    
      - experiments
      - samples
      - dataSets
    
      every single instance of an entity must be of a specific **entity type** (see below). The type defines the set of properties.
    
    - **experiment type / collection type:** a type for experiments which specifies its properties
    - **sample type / object type:** a type for samples / objects which specifies its properties
    - **dataSet type:** a type for dataSets which specifies its properties
    - **property type:** a single property, as defined in the entity types above. It can be of a classic data type (e.g. INTEGER, VARCHAR, BOOLEAN) or its values can be controlled (CONTROLLEDVOCABULARY).
    - **plugin:** a script written in [Jython](https://www.jython.org) which allows to check property values in a even more detailed fashion
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ## connect to OpenBIS
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### login
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    In an **interactive session** e.g. inside a Jupyter notebook, you can use `getpass` to enter your password safely:
    
    ```python
    from pybis import Openbis
    o = Openbis('https://example.com')
    o = Openbis('example.com')          # https:// is assumed
    
    import getpass
    password = getpass.getpass()
    
    o.login('username', password, save_token=True)   # save the session token in ~/.pybis/example.com.token
    ```
    
    In a **script** you would rather use two **environment variables** to provide username and password:
    
    ```python
    from pybis import Openbis
    o = Openbis(os.environ['OPENBIS_HOST'])
    
    o.login(os.environ['OPENBIS_USERNAME'], os.environ['OPENBIS_PASSWORD'])
    ```
    
    As an even better alternative, you should use personal access tokens (PAT) to avoid username/password altogether. See below.
    
    ### Verify certificate
    
    By default, your SSL-Certification is being verified. If you have a test-instance with a self-signed certificate, you'll need to turn off this verification explicitly:
    
    ```python
    from pybis import Openbis
    o = Openbis('https://test-openbis-instance.com', verify_certificates=False)
    ```
    
    ### Check session token, logout()
    
    Check whether your session, i.e. the **session token** is still valid and log out:
    
    ```python
    print(f"Session is active: {o.is_session_active()} and token is {o.token}")
    o.logout()
    print(f"Session is active: {o.is_session_active()"}
    ```
    
    ### Personal access token (PAT)
    
    As an (new) alternative to login every time you run a script, you can create tokens which
    
    - once issued, do **not need username or password**
    - are **much longer valid** than session tokens (default is one year)
    - **survive restarts** of an openBIS instance
    
    To create a token, you first need a valid session – either through classic login or by assigning an existing valid session token:
    
    ```python
    from pybis import Openbis
    o = Openbis('https://test-openbis-instance.com')
    
    o.login("username", "password")
    # or
    o.set_token("your_username-220808165456793xA3D0357C5DE66A5BAD647E502355FE2C")
    ```
    
    Then you can create a new personal access token (PAT) and use it for all further pyBIS queries:
    
    ```python
    pat = o.get_or_create_personal_access_token(sessionName="Project A")
    o.set_token(pat, save_token=True)
    ```
    
    You may also use permId directly:
    
    ```python
    pat = o.get_or_create_personal_access_token(sessionName="Project A")
    o.set_token(pat.permId, save_token=True) 
    ```
    
    **Note:** If there is an existing PAT with the same _sessionName_ which is still valid and the validity is within the warning period (defined by the server), then this existing PAT is returned instead. However, you can enforce creating a new PAT by passing the argument `force=True`.
    
    **Note:** Most operations are permitted using the PAT, _except_:
    
    - all operations on personal access tokens itself
    - i.e. create, list, delete operations on tokens
    
    For these operations, you need to use a session token instead.
    
    To get a list of all currently available tokens:
    
    ```python
    o.get_personal_access_tokens()
    o.get_personal_access_tokens(sessionName="APPLICATION_1")
    ```
    
    To delete the first token shown in the list:
    
    ```python
    o.get_personal_access_tokens()[0].delete('some reason')
    ```
    
    ### Caching
    
    With `pyBIS 1.17.0`, a lot of caching has been introduced to improve the speed of object lookups that do not change often. If you encounter any problems, you can turn it off like this:
    
    ```python
    o = Openbis('https://example.com', use_cache=False)
    
    # or later in the script
    o.use_cache = False
    o.clear_cache()
    o.clear_cache('sampleType')
    ```
    
    ## Mount openBIS dataStore server
    
    ### Prerequisites: FUSE / SSHFS
    
    Mounting an openBIS dataStore server requires FUSE / SSHFS to be installed (requires root privileges). The mounting itself requires no root privileges.
    
    **Mac OS X**
    
    Follow the installation instructions on
    https://osxfuse.github.io
    
    **Unix Cent OS 7**
    
    
    ```bash
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    $ sudo yum install epel-release
    $ sudo yum --enablerepo=epel -y install fuse-sshfs
    $ user="$(whoami)"
    $ usermod -a -G fuse "$user"
    ```
    
    After the installation, an `sshfs` command should be available.
    
    ### Mount dataStore server with pyBIS
    
    Because the mount/unmount procedure differs from platform to platform, pyBIS offers two simple methods:
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    o.mount()
    o.mount(username, password, hostname, mountpoint, volname)
    o.is_mounted()
    o.unmount()
    o.get_mountpoint()
    ```
    
    Currently, mounting is supported for Linux and Mac OS X only.
    
    All attributes, if not provided, are re-used by a previous login() command. If no mountpoint is provided, the default mounpoint will be `~/hostname`. If this directory does not exist, it will be created. The directory must be empty before mounting.
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ## Masterdata
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    OpenBIS stores quite a lot of meta-data along with your dataSets. The collection of data that describes this meta-data (i.e. meta-meta-data) is called masterdata. It consists of:
    
    - sample types
    - dataSet types
    - material types
    - experiment types
    - property types
    - vocabularies
    - vocabulary terms
    - plugins (jython scripts that allow complex data checks)
    - tags
    - semantic annotations
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### browse masterdata
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample_types = o.get_sample_types()  # get a list of sample types
    sample_types.df                      # DataFrame object
    st = o.get_sample_types()[3]         # get 4th element of that list
    st = o.get_sample_type('YEAST')
    st.code
    st.generatedCodePrefix
    st.attrs.all()                       # get all attributes as a dict
    st.get_validationPlugin()            # returns a plugin object
    
    st.get_property_assignments()        # show the list of properties
                                         # for that sample type
    o.get_material_types()
    o.get_dataset_types()
    o.get_experiment_types()
    o.get_collection_types()
    
    o.get_property_types()
    pt = o.get_property_type('BARCODE_COMPLEXITY_CHECKER')
    pt.attrs.all()
    
    o.get_plugins()
    pl = o.get_plugin('Diff_time')
    pl.script  # the Jython script that processes this property
    
    o.get_vocabularies()
    o.get_vocabulary('BACTERIAL_ANTIBIOTIC_RESISTANCE')
    o.get_terms(vocabulary='STORAGE')
    o.get_tags()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create property types
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    **Samples** (objects), **experiments** (collections) and **dataSets** contain type-specific **properties**. When you create a new sample, experiment or datasSet of a given type, the set of properties is well defined. Also, the values of these properties are being type-checked.
    
    The first step in creating a new entity type is to create a so called **property type**:
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    pt_text = o.new_property_type(
        code        = 'MY_NEW_PROPERTY_TYPE',
        label       = 'yet another property type',
        description = 'my first property',
        dataType    = 'VARCHAR',
    )
    pt_text.save()
    
    pt_int = o.new_property_type(
        code        = 'MY_NUMBER',
        label       = 'property contains a number',
        dataType    = 'INTEGER',
    )
    pt_int.save()
    
    pt_voc = o.new_property_type(
        code        = 'MY_CONTROLLED_VOCABULARY',
        label       = 'label me',
        description = 'give me a description',
        dataType    = 'CONTROLLEDVOCABULARY',
        vocabulary  = 'STORAGE',
    )
    pt_voc.save()
    
    pt_richtext = o.new_property_type(
        code        = 'MY_RICHTEXT_PROPERTY',
        label       = 'richtext data',
        description = 'property contains rich text',
        dataType    = 'MULTILINE_VARCHAR',
        metaData    = {'custom_widget' : 'Word Processor'}
    )
    pt_richtext.save()
    
    pt_spread = o.new_property_type(
        code        = 'MY_TABULAR_DATA',
        label       = 'data in a table',
        description = 'property contains a spreadsheet',
        dataType    = 'XML',
        metaData    = {'custom_widget': 'Spreadsheet'}
    )
    pt_spread.save()
    ```
    
    The `dataType` attribute can contain any of these values:
    
    - `INTEGER`
    - `VARCHAR`
    - `MULTILINE_VARCHAR`
    - `REAL`
    - `TIMESTAMP`
    - `BOOLEAN`
    - `HYPERLINK`
    - `XML`
    - `CONTROLLEDVOCABULARY`
    - `MATERIAL`
    
    When choosing `CONTROLLEDVOCABULARY`, you must specify a `vocabulary` attribute (see example). Likewise, when choosing `MATERIAL`, a `materialType` attribute must be provided.
    
    To create a **richtext property**, use `MULTILINE_VARCHAR` as `dataType` and set `metaData` to `{'custom_widget' : 'Word Processor'}` as shown in the example above.
    
    To create a **tabular, spreadsheet-like property**, use `XML` as `dataType` and set `metaData` to `{'custom_widget' : 'Spreadhseet'}`as shown in the example above.
    
    **Note**: PropertyTypes that start with a \$ are by definition `managedInternally` and therefore this attribute must be set to True.
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create sample types / object types
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    The second step (after creating a property type, see above) is to create the **sample type**. The new name for **sample** is **object**. You can use both methods interchangeably:
    
    - `new_sample_type()` == `new_object_type()`
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample_type = o.new_sample_type(
        code                = 'my_own_sample_type',  # mandatory
        generatedCodePrefix = 'S',                   # mandatory
        description         = '',
        autoGeneratedCode   = True,
        subcodeUnique       = False,
        listable            = True,
        showContainer       = False,
        showParents         = True,
        showParentMetadata  = False,
        validationPlugin    = 'Has_Parents'          # see plugins below
    )
    sample_type.save()
    ```
    
    When `autoGeneratedCode` attribute is set to `True`, then you don't need to provide a value for `code` when you create a new sample. You can get the next autoGeneratedCode like this:
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample_type.get_next_sequence()    # eg. 67
    sample_type.get_next_code()        # e.g. FLY77
    ```
    
    From pyBIS 1.31.0 onwards, you can provide a `code` even for samples where its sample type has `autoGeneratedCode=True` to offer the same functionality as ELN-LIMS. In earlier versions of pyBIS, providing a code in this situation caused an error.
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### assign and revoke properties to sample type / object type
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    The third step, after saving the sample type, is to **assign or revoke properties** to the newly created sample type. This assignment procedure applies to all entity types (dataset type, experiment type).
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample_type.assign_property(
    	prop                 = 'diff_time',           # mandatory
    	section              = '',
    	ordinal              = 5,
    	mandatory            = True,
    	initialValueForExistingEntities = 'initial value'
    	showInEditView       = True,
    	showRawValueInForms  = True
    )
    sample_type.revoke_property('diff_time')
    sample_type.get_property_assignments()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create a dataset type
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    The second step (after creating a **property type**, see above) is to create the **dataset type**. The third step is to **assign or revoke the properties** to the newly created dataset type.
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    dataset_type = o.new_dataset_type(
        code                = 'my_dataset_type',       # mandatory
        description         = None,
        mainDataSetPattern  = None,
        mainDataSetPath     = None,
        disallowDeletion    = False,
        validationPlugin    = None,
    )
    dataset_type.save()
    dataset_type.assign_property('property_name')
    dataset_type.revoke_property('property_name')
    dataset_type.get_property_assignments()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create an experiment type / collection type
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    The second step (after creating a **property type**, see above) is to create the **experiment type**.
    
    The new name for **experiment** is **collection**. You can use both methods interchangeably:
    
    - `new_experiment_type()` == `new_collection_type()`
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    experiment_type = o.new_experiment_type(
        code,
        description      = None,
        validationPlugin = None,
    )
    experiment_type.save()
    experiment_type.assign_property('property_name')
    experiment_type.revoke_property('property_name')
    experiment_type.get_property_assignments()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create material types
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Materials and material types are deprecated in newer versions of openBIS.
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    material_type = o.new_material_type(
        code,
        description=None,
        validationPlugin=None,
    )
    material_type.save()
    material_type.assign_property('property_name')
    material_type.revoke_property('property_name')
    material_type.get_property_assignments()
    
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create plugins
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Plugins are Jython scripts that can accomplish more complex data-checks than ordinary types and vocabularies can achieve. They are assigned to entity types (dataset type, sample type etc). [Documentation and examples can be found here](https://wiki-bsse.ethz.ch/display/openBISDoc/Properties+Handled+By+Scripts)
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    pl = o.new_plugin(
        name       ='my_new_entry_validation_plugin',
        pluginType ='ENTITY_VALIDATION',       # or 'DYNAMIC_PROPERTY' or 'MANAGED_PROPERTY',
        entityKind = None,                     # or 'SAMPLE', 'MATERIAL', 'EXPERIMENT', 'DATA_SET'
        script     = 'def calculate(): pass'   # a JYTHON script
    )
    pl.save()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Users, Groups and RoleAssignments
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Users can only login into the openBIS system when:
    
    - they are present in the authentication system (e.g. LDAP)
    - the username/password is correct
    - the user's mail address needs is present
    - the user is already added to the openBIS user list (see below)
    - the user is assigned a role which allows a login, either directly assigned or indirectly assigned via a group membership
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    o.get_groups()
    group = o.new_group(code='group_name', description='...')
    group = o.get_group('group_name')
    group.save()
    group.assign_role(role='ADMIN', space='DEFAULT')
    group.get_roles()
    group.revoke_role(role='ADMIN', space='DEFAULT')
    
    group.add_members(['admin'])
    group.get_members()
    group.del_members(['admin'])
    group.delete()
    
    o.get_persons()
    person = o.new_person(userId='username')
    person.space = 'USER_SPACE'
    person.save()
    # person.delete() is currently not possible.
    
    person.assign_role(role='ADMIN', space='MY_SPACE')
    person.assign_role(role='OBSERVER')
    person.get_roles()
    person.revoke_role(role='ADMIN', space='MY_SPACE')
    person.revoke_role(role='OBSERVER')
    
    o.get_role_assignments()
    o.get_role_assignments(space='MY_SPACE')
    o.get_role_assignments(group='MY_GROUP')
    ra = o.get_role_assignment(techId)
    ra.delete()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Spaces
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Spaces are fundamental way in openBIS to divide access between groups. Within a space, data can be easily shared. Between spaces, people need to be given specific access rights (see section above). The structure in openBIS is as follows:
    
    - space
      - project
        - experiment / collection
          - sample / object
            - dataset
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    space = o.new_space(code='space_name', description='')
    space.save()
    o.get_spaces(
        start_with = 0,                   # start_with and count
        count      = 10,                  # enable paging
    )
    space = o.get_space('MY_SPACE')
    
    # get individual attributes
    space.code
    space.description
    space.registrator
    space.registrationDate
    space.modifier
    space.modificationDate
    
    # set individual attribute
    # most of the attributes above are set automatically and cannot be modified.
    space.description = '...'
    
    # get all attributes as a dictionary
    space.attrs.all()
    
    space.delete('reason for deletion')
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Projects
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Projects live within spaces and usually contain experiments (aka collections):
    
    - space
      - project
        - experiment / collection
          - sample / object
            - dataset
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    project = o.new_project(
        space       = space,
        code        = 'project_name',
        description = 'some project description'
    )
    project = space.new_project(
    	code         = 'project_code',
    	description  = 'project description'
    )
    project.save()
    
    o.get_projects(
        space       = 'MY_SPACE',         # show only projects in MY_SPACE
        start_with  = 0,                  # start_with and count
        count       = 10,                 # enable paging
    )
    o.get_projects(space='MY_SPACE')
    space.get_projects()
    
    project.get_experiments() # see details and limitations in Section 'search for experiments'
    
    project.get_attachments()             # deprecated, as attachments are not compatible with ELN-LIMS.
                                          # Attachments are an old concept and should not be used anymore.
    p.add_attachment(                     # deprecated, see above
        fileName='testfile',
         description= 'another file',
         title= 'one more attachment'
    )
    project.download_attachments(<path or cwd>)  # deprecated, see above
    
    # get individual attributes
    project.code
    project.description
    
    # set individual attribute
    project.description = '...'
    
    # get all attributes as a dictionary
    project.attrs.all()
    
    project.freeze = True
    project.freezeForExperiments = True
    project.freezeForSamples = True
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Experiments / Collections
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Experiments live within projects:
    
    - space
      - project
        - experiment / collection
          - sample / object
            - dataset
    
    The new name for **experiment** is **collection**. You can use boths names interchangeably:
    
    - `get_experiment()` = `get_collection()`
    - `new_experiment()` = `new_collection()`
    - `get_experiments()` = `get_collections()`
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### create a new experiment
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    exp = o.new_experiment
        code='MY_NEW_EXPERIMENT',
        type='DEFAULT_EXPERIMENT',
        space='MY_SPACE',
        project='YEASTS'
    )
    exp.save()
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### search for experiments
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    experiments = o.get_experiments(
        project       = 'YEASTS',
        space         = 'MY_SPACE',
        type          = 'DEFAULT_EXPERIMENT',
        tags          = '*',
        finished_flag = False,
        props         = ['name', 'finished_flag']
    )
    experiments = project.get_experiments()
    experiment = experiments[0]        # get first experiment of result list
    experiment = experiment
    for experiment in experiments:     # iterate over search results
        print(experiment.props.all())
    dataframe = experiments.df         # get Pandas DataFrame of result list
    
    exp = o.get_experiment('/MY_SPACE/MY_PROJECT/MY_EXPERIMENT')
    ```
    
    ***Note: Attributes download***
    
    The `get_experiments()` method, by default, returns fewer details to make the download process faster.
    However, if you want to include specific attributes in the results, you can do so by using the `attrs` parameter.
    
    The `get_experiments()` method results include only `identifier`, `permId`, `type`, `registrator`, `registrationDate`, `modifier`, `modificationDate`
    
    ```get attributes
    experiments = o.get_experiments(
        project       = 'YEASTS',
        space         = 'MY_SPACE',
        type          = 'DEFAULT_EXPERIMENT',
        attrs          = ["parents", "children"]
    )
    
        identifier             permId                type               registrator    registrationDate     modifier    modificationDate     parents                    children
    --  ---------------------  --------------------  -----------------  -------------  -------------------  ----------  -------------------  -------------------------  ----------
     0  /MY_SPACE/YEASTS/EXP1  20230407070122991-46  DEFAULT_EXPERIMENT  admin          2023-04-07 09:01:23  admin       2023-04-07 09:02:22  ['/MY_SPACE/YEASTS/EXP2']  []
    
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### Experiment attributes
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    exp.attrs.all()                    # returns all attributes as a dict
    
    exp.attrs.tags = ['some', 'tags']
    exp.tags = ['some', 'tags']        # same thing
    exp.save()
    
    exp.code
    exp.description
    exp.registrator
    ...
    
    exp.project = 'my_project'
    exp.space   = 'my_space'
    exp.freeze = True
    exp.freezeForDataSets = True
    exp.freezeForSamples = True
    
    exp.save()                         # needed to save/update the changed attributes and properties
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### Experiment properties
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    **Getting properties**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    experiment.props == ds.p                  # you can use either .props or .p to access the properties
    experiment.p                              # in Jupyter: show all properties in a nice table
    experiment.p()                            # get all properties as a dict
    experiment.props.all()                    # get all properties as a dict
    experiment.p('prop1','prop2')             # get some properties as a dict
    experiment.p.get('$name')                 # get the value of a property
    experiment.p['property']                  # get the value of a property
    ```
    
    **Setting properties**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    experiment.experiment = 'first_exp'       # assign sample to an experiment
    experiment.project = 'my_project'         # assign sample to a project
    
    experiment.p. + TAB                       # in Jupyter/IPython: show list of available properties
    experiment.p.my_property_ + TAB           # in Jupyter/IPython: show datatype or controlled vocabulary
    experiment.p['my_property']= "value"      # set the value of a property
    experiment.p.set('my_property, 'value')   # set the value of a property
    experiment.p.my_property = "some value"   # set the value of a property
    experiment.p.set({'my_property':'value'}) # set the values of some properties
    experiment.set_props({ key: value })      # set the values of some properties
    
    experiment.save()                         # needed to save/update the changed attributes and properties
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Samples / Objects
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Samples usually live within experiments/collections:
    
    - space
      - project
        - experiment / collection
          - sample / object
            - dataset
    
    The new name for **sample** is **object**. You can use boths names interchangeably:
    
    - `get_sample()` = `get_object()`
    - `new_sample()` = `new_object()`
    - `get_samples()` = `get_objects()`
    
    etc.
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample = o.new_sample(
        type       = 'YEAST',
        space      = 'MY_SPACE',
        experiment = '/MY_SPACE/MY_PROJECT/EXPERIMENT_1',
        parents    = [parent_sample, '/MY_SPACE/YEA66'],   # you can use either permId, identifier
        children   = [child_sample],                       # or sample object
        props      = {"name": "some name", "description": "something interesting"}
    )
    sample = space.new_sample( type='YEAST' )
    sample.save()
    
    sample = o.get_sample('/MY_SPACE/MY_SAMPLE_CODE')
    sample = o.get_sample('20170518112808649-52')
    samples= o.get_samples(type='UNKNOWN')    # see details and limitations in Section 'search for samples / objects'
    
    # get individual attributes
    sample.space
    sample.code
    sample.permId
    sample.identifier
    sample.type  # once the sample type is defined, you cannot modify it
    
    # set attribute
    sample.space = 'MY_OTHER_SPACE'
    
    sample.experiment    # a sample can belong to one experiment only
    sample.experiment = '/MY_SPACE/MY_PROJECT/MY_EXPERIMENT'
    
    sample.project
    sample.project = '/MY_SPACE/MY_PROJECT'  # only works if project samples are
    enabled
    
    sample.tags
    sample.tags = ['guten_tag', 'zahl_tag' ]
    
    sample.attrs.all()                    # returns all attributes as a dict
    sample.props.all()                    # returns all properties as a dict
    
    sample.get_attachments()              # deprecated, as attachments are not compatible with ELN-LIMS.
                                          # Attachments are an old concept and should not be used anymore.
    sample.download_attachments(<path or cwd>)  # deprecated, see above
    sample.add_attachment('testfile.xls') # deprecated, see above
    
    sample.delete('deleted for some reason')
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### create/update/delete many samples in a transaction
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Creating a single sample takes some time. If you need to create many samples, you might want to create them in one transaction. This will transfer all your sample data at once. The Upside of this is the **gain in speed**. The downside: this is a **all-or-nothing** operation, which means, either all samples will be registered or none (if any error occurs).
    
    **create many samples in one transaction**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    trans = o.new_transaction()
    for i in range (0, 100):
        sample = o.new_sample(...)
        trans.add(sample)
    
    trans.commit()
    ```
    
    **update many samples in one transaction**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    trans = o.new_transaction()
    for sample in o.get_samples(count=100):
        sample.prop.some_property = 'different value'
        trans.add(sample)
    
    trans.commit()
    ```
    
    **delete many samples in one transaction**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    trans = o.new_transaction()
    for sample in o.get_samples(count=100):
        sample.mark_to_be_deleted()
        trans.add(sample)
    
    trans.reason('go what has to go')
    trans.commit()
    ```
    
    **Note:** You can use the `mark_to_be_deleted()`, `unmark_to_be_deleted()` and `is_marked_to_be_deleted()` methods to set and read the internal flag.
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### parents, children, components and container
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample.get_parents()
    sample.set_parents(['/MY_SPACE/PARENT_SAMPLE_NAME')
    sample.add_parents('/MY_SPACE/PARENT_SAMPLE_NAME')
    sample.del_parents('/MY_SPACE/PARENT_SAMPLE_NAME')
    
    sample.get_children()
    sample.set_children('/MY_SPACE/CHILD_SAMPLE_NAME')
    sample.add_children('/MY_SPACE/CHILD_SAMPLE_NAME')
    sample.del_children('/MY_SPACE/CHILD_SAMPLE_NAME')
    
    # A Sample may belong to another Sample, which acts as a container.
    # As opposed to DataSets, a Sample may only belong to one container.
    sample.container    # returns a sample object
    sample.container = '/MY_SPACE/CONTAINER_SAMPLE_NAME'   # watch out, this will change the identifier of the sample to:
                                                           # /MY_SPACE/CONTAINER_SAMPLE_NAME:SAMPLE_NAME
    sample.container = ''                                  # this will remove the container.
    
    # A Sample may contain other Samples, in order to act like a container (see above)
    # caveat: containers are NOT compatible with ELN-LIMS
    # The Sample-objects inside that Sample are called «components» or «contained Samples»
    # You may also use the xxx_contained() functions, which are just aliases.
    sample.get_components()
    sample.set_components('/MY_SPACE/COMPONENT_NAME')
    sample.add_components('/MY_SPACE/COMPONENT_NAME')
    sample.del_components('/MY_SPACE/COMPONENT_NAME')
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### sample tags
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample.get_tags()
    sample.set_tags('tag1')
    sample.add_tags(['tag2','tag3'])
    sample.del_tags('tag1')
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### Sample attributes and properties
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    **Getting properties**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample.attrs.all()                    # returns all attributes as a dict
    sample.attribute_name                 # return the attribute value
    
    sample.props == ds.p                  # you can use either .props or .p to access the properties
    sample.p                              # in Jupyter: show all properties in a nice table
    sample.p()                            # get all properties as a dict
    sample.props.all()                    # get all properties as a dict
    sample.p('prop1','prop2')             # get some properties as a dict
    sample.p.get('$name')                 # get the value of a property
    sample.p['property']                  # get the value of a property
    ```
    
    **Setting properties**
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample.experiment = 'first_exp'       # assign sample to an experiment
    sample.project = 'my_project'         # assign sample to a project
    
    sample.p. + TAB                       # in Jupyter/IPython: show list of available properties
    sample.p.my_property_ + TAB           # in Jupyter/IPython: show datatype or controlled vocabulary
    sample.p['my_property']= "value"      # set the value of a property
    sample.p.set('my_property, 'value')   # set the value of a property
    sample.p.my_property = "some value"   # set the value of a property
    sample.p.set({'my_property':'value'}) # set the values of some properties
    sample.set_props({ key: value })      # set the values of some properties
    
    sample.save()                         # needed to save/update the attributes and properties
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### search for samples / objects
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    The result of a search is always list, even when no items are found. The `.df` attribute returns
    the Pandas dataFrame of the results.
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    samples = o.get_samples(
        space      ='MY_SPACE',
        type       ='YEAST',
        tags       =['*'],                # only sample with existing tags
        start_with = 0,                   # start_with and count
        count      = 10,                  # enable paging
        where = {
            "$SOME.WEIRD-PROP": "hello"   # only receive samples where properties match
        }
    
        registrationDate = "2020-01-01",  # date format: YYYY-MM-DD
        modificationDate = "<2020-12-31", # use > or < to search for specified date and later / earlier
        attrs=[                           # show these attributes in the dataFrame
            'sample.code',
            'registrator.email',
            'type.generatedCodePrefix'
        ],
        parent_property = 'value',        # search in a parent's property
        child_property  = 'value',        # search in a child's property
        container_property = 'value'      # search in a container's property
        parent = '/MY_SPACE/PARENT_SAMPLE', # sample has this as its parent
        parent = '*',                     # sample has at least one parent
        child  = '/MY_SPACE/CHILD_SAMPLE',
        child  = '*',                     # sample has at least one child
        container = 'MY_SPACE/CONTAINER',
        container = '*'                   # sample lives in a container
        props=['$NAME', 'MATING_TYPE']    # show these properties in the result
    )
    
    sample = samples[9]                   # get the 10th sample
                                          # of the search results
    sample = samples['/SPACE/AABC']       # same, fetched by identifier
    for sample in samples:                # iterate over the
       print(sample.code)                 # search results
    
    
    samples.df                            # returns a Pandas DataFrame object
    
    samples = o.get_samples(props="*")    # retrieve all properties of all samples
    ```
    
    ***Note: Attributes download***
    
    The `get_samples()` method, by default, returns fewer details to make the download process faster.
    However, if you want to include specific attributes in the results, you can do so by using the `attrs` parameter.
    
    The `get_samples()` method results include only `identifier`, `permId`, `type`, `registrator`, `registrationDate`, `modifier`, `modificationDate`
    
    ```get attributes
    experiments = o.get_samples(
        space         = 'MY_SPACE',
        type          = 'YEAST',
        attrs          = ["parents", "children"]
    )
    
        identifier                permId                type               registrator    registrationDate     modifier    modificationDate     parents                    children
    --  ---------------------     --------------------  -----------------  -------------  -------------------  ----------  -------------------  -------------------------  ----------
     0  /MY_SPACE/YEASTS/SAMPLE1  20230407070121337-47  YEAST              admin          2023-04-07 09:06:23  admin       2023-04-07 09:06:22  ['/MY_SPACE/YEASTS/EXP2']  []
    
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### freezing samples
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    sample.freeze = True
    sample.freezeForComponents = True
    sample.freezeForChildren = True
    sample.freezeForParents = True
    sample.freezeForDataSets = True
    ```
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    ### Datasets
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    Datasets are by all means the most important openBIS entity. The actual files are stored as datasets; all other openBIS entities mainly are necessary to annotate and to structure the data:
    
    - space
      - project
        - experiment / collection
          - sample / object
            - dataset
    
    
    Marco Del Tufo's avatar
    .  
    Marco Del Tufo committed
    #### working with existing dataSets
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    
    **search for datasets**
    
    This example does the following
    
    - search for all datasets of type `SCANS`, retrieve the first 10 entries
    - print out all properties
    - print the list of all files in this dataset
    - download the dataset
    
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    ```python
    
    Marco Del Tufo's avatar
    Marco Del Tufo committed
    datasets = sample.get_datasets(type='SCANS', start_with=0, count=10)
    for dataset in datasets:
        print(dataset.props())
        print(dataset.file_list)
        dataset.download()
    dataset = datasets[0]
    ```