Newer
Older
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
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):
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
### 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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**
$ 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:
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.
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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()
```
**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**:
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
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.
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()`
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:
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.
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).
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()
```
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.
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()
```
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()`
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()
```
Materials and material types are deprecated in newer versions of openBIS.
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()
```
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)
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()
```
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
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
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()
```
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
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
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')
```
Projects live within spaces and usually contain experiments (aka collections):
- space
- project
- experiment / collection
- sample / object
- dataset
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
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
```
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()`
exp = o.new_experiment
code='MY_NEW_EXPERIMENT',
type='DEFAULT_EXPERIMENT',
space='MY_SPACE',
project='YEASTS'
)
exp.save()
```
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
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'] []
```
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
```
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**
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
```
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.
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
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')
```
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**
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**
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**
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.
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
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')
```
sample.get_tags()
sample.set_tags('tag1')
sample.add_tags(['tag2','tag3'])
sample.del_tags('tag1')
```
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**
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
```
The result of a search is always list, even when no items are found. The `.df` attribute returns
the Pandas dataFrame of the results.
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
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'] []
```
sample.freeze = True
sample.freezeForComponents = True
sample.freezeForChildren = True
sample.freezeForParents = True
sample.freezeForDataSets = True
```
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
**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
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]
```