diff --git a/pybis/src/python/CHANGELOG.md b/pybis/src/python/CHANGELOG.md
index ddc9cefd6540d09e80d603c682be4513a9200b1a..e6651980ce2cedd3490b534541b29ac1b6d061d7 100644
--- a/pybis/src/python/CHANGELOG.md
+++ b/pybis/src/python/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Changes with pybis-1.12.0
+
+- added possibility to get any additional attributes in the get_samples() method
+- added possibility to get any additional attributes in the get_dataSets() method
+
 ## Changes with pybis-1.11.1
 
 - added automatically accepting host key, otherwise mount() will hang the first time 
diff --git a/pybis/src/python/README.md b/pybis/src/python/README.md
index 61b7e0777841c6d59276def3ff1cf9824abf2136..b19055202cece87044a5d2776850e1f8ee5a24ab 100644
--- a/pybis/src/python/README.md
+++ b/pybis/src/python/README.md
@@ -1,33 +1,62 @@
 # Welcome to pyBIS!
-pyBIS is a Python module for interacting with openBIS, designed to be used in Jupyter. It offers some sort of IDE for openBIS, supporting TAB completition and input checks, making the life of a researcher hopefully easier.
+
+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 input 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
 - 18.06.2 or later is recommended
-- pyBIS uses Python 3.3 and pandas
+- pyBIS uses Python 3.3 and Pandas
 
 ## Installation
 
 ```
-pip install pybis
+pip install --upgrade pybis
 ```
-That command will download install pybis and all its dependencies.
-
-If you haven't done yet, install Jupyter Notebook:
+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):
 ```
 pip install jupyter
+pip install jupyterlab
 ```
 
 # General Usage
 
-## Tab completition and other hints
-Used in a Jupyter Notebook 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.
+### 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
+
 
-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.
+* 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 
 
 # connect to OpenBIS
 
@@ -55,7 +84,7 @@ o.login(os.environ['OPENBIS_USERNAME'], os.environ['OPENBIS_PASSWORD'])
 ```
 
 
-Check whether the session token is still valid and log out:
+Check whether the **session token** is still valid and log out:
 
 ```
 o.token
@@ -67,7 +96,7 @@ o.logout()
 
 ### Prerequisites: FUSE / SSHFS
 
-Mounting an openBIS dataStore server requires FUSE / SSHFS to be installed (requires root privileges):
+Mounting an openBIS dataStore server requires FUSE / SSHFS to be installed (requires root privileges). The mounting itself requires no root privileges.
 
 **Mac OS X**
 
@@ -128,10 +157,10 @@ 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')
@@ -191,37 +220,39 @@ The `dataType` attribute can contain any of these values:
 When choosing `CONTROLLEDVOCABULARY`, you must specify a `vocabulary` attribute (see example). Likewise, when choosing `MATERIAL`, a `materialType` attribute must be provided. PropertyTypes that start with a $ belong by definition to the `internalNameSpace` and therefore this attribute must be set to True.
 
 
-## create sample types
+## create sample types / object types
+
+The new name for `sample_type` is `object_type`. You can use boths names interchangeably.
 
 ```
 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
+    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()
 ```
 
-## assign properties to sample type
+## assign properties to sample type / object type
 
 A sample type needs to be saved before properties can be assigned to. This assignment procedure applies to all entity types (dataset type, experiment type, material type).
 
 ```
 sample_type.assign_property(
-	prop = 'diff_time',             # mandatory
-	section = '',
-	ordinal = 5,
-	mandatory = True,
+	prop                 = 'diff_time',           # mandatory
+	section              = '',
+	ordinal              = 5,
+	mandatory            = True,
 	initialValueForExistingEntities = 'initial value'
-	showInEditView = True,
-	showRawValueInForms = True
+	showInEditView       = True,
+	showRawValueInForms  = True
 )
 sample_type.revoke_property('diff_time')
 sample_type.get_property_assignments()
@@ -231,12 +262,12 @@ sample_type.get_property_assignments()
 
 ```
 dataset_type = o.new_dataset_type(
-    code = 'my_dataset_type',       # mandatory
-    description=None,
-    mainDataSetPattern=None,
-    mainDataSetPath=None,
-    disallowDeletion=False,
-    validationPlugin=None,
+    code                = 'my_dataset_type',       # mandatory
+    description         = None,
+    mainDataSetPattern  = None,
+    mainDataSetPath     = None,
+    disallowDeletion    = False,
+    validationPlugin    = None,
 )
 dataset_type.save()
 dataset_type.assign_property('property_name')
@@ -244,13 +275,15 @@ dataset_type.revoke_property('property_name')
 dataset_type.get_property_assignments()
 ```
 
-## create experiment types
+## create experiment types / collection types
+
+The new name for `experiment_type` is `collection_type`. You can use boths names interchangeably.
 
 ```
 experiment_type = o.new_experiment_type(
     code, 
-    description=None,
-    validationPlugin=None,
+    description      = None,
+    validationPlugin = None,
 )
 experiment_type.save()
 experiment_type.assign_property('property_name')
@@ -280,9 +313,9 @@ Plugins are Jython scripts that can accomplish more complex data-checks than ord
 ```
 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
+    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()
 ```
@@ -328,8 +361,8 @@ space = o.new_space(code='space_name', description='')
 space.save()
 space.delete('reason for deletion')
 o.get_spaces(
-    start_with = 1,                   # start_with and count
-    count = 7,                        # enable paging
+    start_with = 0,                   # start_with and count
+    count      = 10,                  # enable paging
 )
 space = o.get_space('MY_SPACE')
 space.code
@@ -344,20 +377,20 @@ space.attrs.all()                     # returns a dict containing all attributes
 ## Projects
 ```
 project = o.new_project(
-    space=space, 
-    code='project_name',
-    description='some project description'
+    space       = space, 
+    code        = 'project_name',
+    description = 'some project description'
 )
 project = space.new_project(
-	code='project_code',
-	description='project description'
+	code         = 'project_code',
+	description  = 'project description'
 )
 project.save()
 
 o.get_projects(
-    space = 'MY_SPACE',               # show only projects in MY_SPACE
-    start_with = 1,                   # start_with and count
-    count = 7,                        # enable paging
+    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()
@@ -369,7 +402,7 @@ project.download_attachments()
 
 project.code
 project.description
-# ... and many more
+# ... any other attribute
 project.attrs.all()                   # returns a dict containing all attributes
 
 project.freeze = True
@@ -378,19 +411,24 @@ project.freezeForSamples = True
 
 ```
 
-## Samples
-Samples are nowadays called **Objects** in openBIS. pyBIS is not yet thoroughly supporting this term in all methods where «sample» occurs.
+## Samples / Objects
+
+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()`
 
-NOTE: In openBIS, `samples` entities have recently been renamed to `objects`. All methods have synonyms using the term `object`, e.g. `get_object`, `new_object`, `get_object_types`.
+etc.
 
 ```
 sample = o.new_sample(
-    type     = 'YEAST', 
-    space    = 'MY_SPACE',
+    type       = 'YEAST', 
+    space      = 'MY_SPACE',
     experiment = '/MY_SPACE/MY_PROJECT/EXPERIMENT_1',
-    parents  = [parent_sample, '/MY_SPACE/YEA66'], 
-    children = [child_sample],
-    props    = {"name": "some name", "description": "something interesting"}
+    parents    = [parent_sample, '/MY_SPACE/YEA66'], 
+    children   = [child_sample],
+    props      = {"name": "some name", "description": "something interesting"}
 )
 sample = space.new_sample( type='YEAST' )
 sample.save()
@@ -478,22 +516,30 @@ sample.p.my_property = "some value"   # set the value of a property
 sample.save()                         # update the sample in openBIS
 ```
 
-### querying samples
+### search for samples / objects
+
+The result of a search is always list, even when no items are found. The `.df` attribute returns
+the Pandas dataFrame of the results.
 
 ```
 samples = o.get_samples(
-    space ='MY_SPACE',
-    type  ='YEAST',
-    tags  =['*'],                     # only sample with existing tags
-    start_with = 1,                   # start_with and count
-    count = 7,                        # enable paging
-    NAME  = 'some name',              # properties are always uppercase 
+    space      ='MY_SPACE',
+    type       ='YEAST',
+    tags       =['*'],                # only sample with existing tags
+    start_with = 0,                   # start_with and count
+    count      = 10,                  # enable paging
+    NAME       = 'some name',         # properties are always uppercase 
                                       # to distinguish them from attributes
     **{ "SOME.WEIRD:PROP": "value"}   # property name contains a dot or a
                                       # colon: cannot be passed as an argument 
-    props=['NAME', 'MATING_TYPE']     # show these properties in the result
+    attrs=[                           # show these attributes in the dataFrame
+        'sample.code',
+        'registrator.email',
+        'type.generatedCodePrefix'
+    ],
+    props=['$NAME', 'MATING_TYPE']    # show these properties in the result
 )
-samples.df                            # returns a pandas DataFrame object
+samples.df                            # returns a Pandas DataFrame object
 samples.get_datasets(type='ANALYZED_DATA')
 ```
 
@@ -507,9 +553,13 @@ sample.freezeForParents = True
 sample.freezeForDataSets = True
 ```
 
-## Experiments
+## Experiments / Collections
 
-NOTE: In openBIS, `experiment` entities have recently been renamed to `collection`. All methods have synonyms using the term `collection`, e.g. `get_collections`, `new_collection`, `get_collection_types`.
+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
@@ -586,7 +636,7 @@ ds.download_attachments()
 ### download dataSets
 
 ```
-ds.get_files(start_folder="/")    # get file list as pandas table
+ds.get_files(start_folder="/")    # get file list as Pandas table
 ds.file_list                      # get file list as array
 
 ds.download()                     # simply download all files to hostname/permId/
@@ -613,10 +663,34 @@ ds.attrs.all()                    # returns all attributes as a dict
 ds.props.all()                    # returns all properties as a dict
 ```
 
-### querying dataSets
+### search for dataSets
+
+* The result of a search is always list, even when no items are found
+* The `.df` attribute returns the Pandas dataFrame of the results
+* properties must be in UPPERCASE to distinguish them from attributes
+
+```
+dataSets = o.get_dataSets(
+    type  ='MY_DATASET_TYPE',
+    NAME  = 'some name',              # properties are always uppercase 
+                                      # to distinguish them from attributes
+    **{ "SOME.WEIRD:PROP": "value"},  # property name contains a dot or a
+                                      # colon: cannot be passed as an argument 
+    start_with = 0,                   # start_with and count
+    count      = 10,                  # enable paging
+    attrs=[                           # show these attributes in the dataFrame
+        'sample.code',
+        'registrator.email',
+        'type.generatedCodePrefix'
+    ],
+    props=['$NAME', 'MATING_TYPE']    # show these properties in the result
+)
+
+df = dataSets.df                      # returns the Pandas dataFrame object
+```
 
-* examples of a complex queries with methods chaining.
-* NOTE: properties must be in UPPERCASE to distinguish them from attributes
+In some cases, you might want to retrieve precisely certain datasets. This can be achieved by
+methods chaining (but be aware, it might not be very performant): 
 
 ```
 datasets = o.get_experiments(project='YEASTS')\
@@ -627,7 +701,7 @@ datasets = o.get_experiments(project='YEASTS')\
 					MY_PROPERTY='some analyzed data'
 		 	 )
 ```
-
+* another example:
 ```
 datasets = o.get_experiment('/MY_NEW_SPACE/MY_PROJECT/MY_EXPERIMENT4')\
            .get_samples(type='UNKNOWN')\
@@ -635,16 +709,6 @@ datasets = o.get_experiment('/MY_NEW_SPACE/MY_PROJECT/MY_EXPERIMENT4')\
            .get_datasets(type='RAW_DATA')
 ```
 
-### deal with dataSets query results
-
-```
-datasets.df                       # get a pandas dataFrame object
-
-# use it in a for-loop:
-for dataset in datasets:
-    print(dataset.permID)
-    dataset.delete('give me a reason')
-```
 ### freeze dataSets
 * once a dataSet has been frozen, it cannot be changed by anyone anymore
 * so be careful!
@@ -673,8 +737,9 @@ ds_new.save()
 
 ### create dataSet with zipfile
 
-```
-# DataSet containing one zipfile which will be unzipped in openBIS
+DataSet containing one zipfile which will be unzipped in openBIS:
+
+```python
 ds_new = o.new_dataset(
     type       = 'RAW_DATA', 
     sample     = '/SPACE/SAMP1',
@@ -693,15 +758,17 @@ ds_new.save()
    * `my_file.txt` --> `/my_file.txt`
    * `../somwhere/else/my_other_file.txt` --> `/my_other_file.txt`
    * `some/folder/file.txt` --> `/file.txt`
+* useful if DataSet contains files and folders
+* the content of the folder will be zipped (on-the-fly) and uploaded to openBIS
+* openBIS will keep the folder structure intact
+* relative path will be shortened to its basename. For example:
+
+| local                      | openBIS    |
+|----------------------------|------------|
+| `../../myData/`            | `myData/`  |
+| `some/experiment/results/` | `results/` |
 
 ```
-# Dataset containing files and folders
-# the content of the folder will be zipped (on-the-fly) and uploaded to openBIS.
-# openBIS will keep the folder structure intact.
-# relative path will be shortened to its basename. For example:
-# local                         openBIS
-# ../../myData/                 myData/
-# some/experiment/results/      results/
 ds_new = o.new_dataset(
     type       = 'RAW_DATA', 
     sample     = '/SPACE/SAMP1',
@@ -712,8 +779,9 @@ ds_new.save()
 
 ### create dataSet container
 
+A DataSet of kind=CONTAINER contains other DataSets, but no files:
+
 ```
-# DataSet CONTAINER (contains other DataSets, but no files)
 ds_new = o.new_dataset(
     type       = 'ANALYZED_DATA', 
     experiment = '/SPACE/PROJECT/EXP1', 
@@ -766,8 +834,11 @@ dataset.del_components(['20170115220259155-412'])
 ```
 
 ## Semantic Annotations
+
+create semantic annotation for sample type 'UNKNOWN':
+
 ```
-# create semantic annotation for sample type 'UNKNOWN'
+
 sa = o.new_semantic_annotation(
 	entityType = 'UNKNOWN',
 	predicateOntologyId = 'po_id',
@@ -778,33 +849,56 @@ sa = o.new_semantic_annotation(
 	descriptorAccessionId = 'da_id'
 )
 sa.save()
+```
+
+Create semantic annotation for property type (predicate and descriptor values omitted for brevity)
 
-# create semantic annotation for property type 
-# (predicate and descriptor values omitted for brevity)
+```
 sa = o.new_semantic_annotation(propertyType = 'DESCRIPTION', ...)
 sa.save()
+```
 
-# create semantic annotation for sample property assignment (predicate and descriptor values omitted for brevity)
-sa = o.new_semantic_annotation(entityType = 'UNKNOWN', propertyType = 'DESCRIPTION', ...)
+**Create** semantic annotation for sample property assignment (predicate and descriptor values omitted for brevity)
+
+```
+sa = o.new_semantic_annotation(
+	entityType = 'UNKNOWN',
+	propertyType = 'DESCRIPTION', 
+	...
+)
 sa.save()
+```
 
-# create a semantic annotation directly from a sample type
-# will also create sample property assignment annotations when propertyType is given
+**Create** a semantic annotation directly from a sample type. Will also create sample property assignment annotations when propertyType is given:
+
+```
 st = o.get_sample_type("ORDER")
 st.new_semantic_annotation(...)
+```
+
+**Get all** semantic annotations
 
-# get all semantic annotations
+```
 o.get_semantic_annotations()
+```
 
-# get semantic annotation by perm id
+**Get** semantic annotation by perm id
+
+```
 sa = o.get_semantic_annotation("20171015135637955-30")
+```
+
+**Update** semantic annotation
 
-# update semantic annotation
+```
 sa.predicateOntologyId = 'new_po_id'
 sa.descriptorOntologyId = 'new_do_id'
 sa.save()
+```
 
-# delete semantic annotation
+**Delete** semantic annotation
+
+```
 sa.delete('reason')
 ```
 
@@ -830,8 +924,8 @@ tag.delete('why?')
 
 An entity such as Sample (Object), Experiment (Collection), Material or DataSet can be of a specific *entity type*:
 
-* Sample Type
-* Experiment Type
+* Sample Type (Object Type)
+* Experiment Type (Collection Type)
 * DataSet Type
 * Material Type
 
diff --git a/pybis/src/python/pybis/__init__.py b/pybis/src/python/pybis/__init__.py
index b0e578dcb1db65cd510e7b661f80be5f42689c29..f4acf1fd1cd6ab42c3d18823bd19b17e74d58d78 100644
--- a/pybis/src/python/pybis/__init__.py
+++ b/pybis/src/python/pybis/__init__.py
@@ -1,7 +1,7 @@
 name = 'pybis'
 __author__ = 'Swen Vermeul'
 __email__ = 'swen@ethz.ch'
-__version__ = '1.11.1'
+__version__ = '1.12.0'
 
 from . import pybis
 from .pybis import Openbis
diff --git a/pybis/src/python/pybis/pybis.py b/pybis/src/python/pybis/pybis.py
index 4729f9e0d3c8beed22bcb9468c4baa0483e2204f..4496e54d94d7db1acea79771615b4bdb05ec4f58 100644
--- a/pybis/src/python/pybis/pybis.py
+++ b/pybis/src/python/pybis/pybis.py
@@ -1727,9 +1727,36 @@ class Openbis:
         self, identifier=None, code=None, permId=None,
         space=None, project=None, experiment=None, collection=None, type=None,
         start_with=None, count=None,
-        withParents=None, withChildren=None, tags=None, props=None, **properties
+        withParents=None, withChildren=None, tags=None, attrs=None, props=None, **properties
     ):
-        """ Get a list of all samples for a given space/project/experiment (or any combination)
+        """Returns a DataFrame of all samples for a given space/project/experiment (or any combination)
+        Filters:
+        --------
+        space        -- a space code or a space object
+        project      -- a project code or a project object
+        experiment   -- an experiment code or an experiment object
+        collection   -- same as experiment
+        tags         -- only return samples with the specified tags
+        type         -- a sampleType code
+
+        Paging:
+        -------
+        start_with   -- default=None
+        count        -- number of samples that should be fetched. default=None.
+
+        Include:
+        --------
+        withParents  -- the list of parent's permIds in a column 'parents'
+        withChildren -- the list of children's permIds in a column 'children'
+        attrs        -- list of all desired attributes. Examples:
+                        space, project, experiment: just return their identifier
+                        space.code, project.code, experiment.code
+                        registrator.email, registrator.firstName
+                        type.generatedCodePrefix
+        props        -- list of all desired properties. Returns an empty string if
+                        a) property is not present
+                        b) property is not defined for this sampleType
+
         """
 
         if collection is not None:
@@ -1743,6 +1770,7 @@ class Openbis:
 
         if space:
             sub_criteria.append(_subcriteria_for(space, 'space'))
+
         if project:
             sub_criteria.append(_subcriteria_for(project, 'project'))
         if experiment:
@@ -1771,35 +1799,54 @@ class Openbis:
             "operator": "AND"
         }
 
+        options = self._get_fetchopts_for_attrs(attrs)
+
         # build the various fetch options
         fetchopts = fetch_option['sample']
         fetchopts['from'] = start_with
         fetchopts['count'] = count
 
-        for option in ['tags', 'properties', 'registrator', 'modifier', 'experiment']:
+        for option in ['tags', 'properties', 'registrator', 'modifier']+options:
             fetchopts[option] = fetch_option[option]
 
         request = {
             "method": "searchSamples",
-            "params": [self.token,
-                       criteria,
-                       fetchopts,
-                       ],
+            "params": [
+                self.token,
+                criteria,
+                fetchopts,
+            ],
         }
         resp = self._post_request(self.as_v3, request)
 
         return self._sample_list_for_response(
             response=resp['objects'],
+            attrs=attrs,
             props=props,
             start_with=start_with,
             count=count,
             totalCount=resp['totalCount'],
         )
 
-
     get_objects = get_samples # Alias
 
 
+    def _get_fetchopts_for_attrs(self, attrs=None):
+        if attrs is None:
+            return []
+
+        fetchopts = []
+        for attr in attrs:
+            if attr.startswith('space'): fetchopts.append('space')
+            if attr.startswith('project'): fetchopts.append('project')
+            if attr.startswith('experiment'): fetchopts.append('experiment')
+            if attr.startswith('sample'): fetchopts.append('sample')
+            if attr.startswith('registrator'): fetchopts.append('registrator')
+            if attr.startswith('modifier'): fetchopts.append('modifier')
+
+        return fetchopts
+
+
     def get_experiments(
         self, code=None, permId=None, type=None, space=None, project=None,
         start_with=None, count=None,
@@ -1896,8 +1943,36 @@ class Openbis:
         self, code=None, type=None, withParents=None, withChildren=None,
         start_with=None, count=None, kind=None,
         status=None, sample=None, experiment=None, collection=None, project=None,
-        tags=None, props=None, **properties
+        tags=None, attrs=None, props=None, **properties
     ):
+        """Returns a DataFrame of all dataSets for a given project/experiment/sample (or any combination)
+        Filters:
+        --------
+        project      -- a project code or a project object
+        experiment   -- an experiment code or an experiment object
+        sample       -- a sample code/permId or a sample/object
+        collection   -- same as experiment
+        tags         -- only return dataSets with the specified tags
+        type         -- a dataSetType code
+
+        Paging:
+        -------
+        start_with   -- default=None
+        count        -- number of dataSets that should be fetched. default=None.
+
+        Include:
+        --------
+        withParents  -- the list of parent's permIds in a column 'parents'
+        withChildren -- the list of children's permIds in a column 'children'
+        attrs        -- list of all desired attributes. Examples:
+                        project, experiment, sample: just return their identifier
+                        space.code, project.code, experiment.code
+                        registrator.email, registrator.firstName
+                        type.generatedCodePrefix
+        props        -- list of all desired properties. Returns an empty string if
+                        a) property is not present
+                        b) property is not defined for this dataSetType
+        """
 
         if 'object' in properties:
             sample = properties['object']
@@ -1945,6 +2020,7 @@ class Openbis:
         }
         fetchopts['from'] = start_with
         fetchopts['count'] = count
+
         if kind:
             kind = kind.upper()
             if kind not in ['PHYSICAL_DATA', 'CONTAINER', 'LINK']:
@@ -1952,7 +2028,8 @@ class Openbis:
             fetchopts['kind'] = kind
             raise NotImplementedError('you cannot search for dataSet kinds yet')
 
-        for option in ['tags', 'properties', 'sample', 'experiment', 'physicalData']:
+        options = self._get_fetchopts_for_attrs(attrs)
+        for option in ['tags', 'properties', 'physicalData']+options:
             fetchopts[option] = fetch_option[option]
 
         request = {
@@ -1966,6 +2043,7 @@ class Openbis:
 
         return self._dataset_list_for_response(
             response=resp['objects'],
+            attrs=attrs,
             props=props,
             start_with=start_with,
             count=count,
@@ -3320,22 +3398,40 @@ class Openbis:
 
 
     def _dataset_list_for_response(
-        self, response, props=None, 
+        self, response, attrs=None, props=None, 
         start_with=None, count=None, totalCount=0
     ):
         """returns a Things object, containing a DataFrame plus some additional information
         """
+        def extract_attribute(attribute_to_extract):
+            def return_attribute(obj):
+                if obj is None: return ''
+                return obj.get(attribute_to_extract,'')
+            return return_attribute
 
         parse_jackson(response)
-        attrs = ['permId', 'type', 'experiment', 'sample',
+
+        if attrs is None: attrs=[]
+        default_attrs = ['permId', 'type', 'experiment', 'sample',
                  'registrationDate', 'modificationDate',
                  'location', 'status', 'presentInArchive', 'size',
                  'properties'
                 ]
+        display_attrs = default_attrs + attrs
+
         if len(response) == 0:
-            datasets = DataFrame(columns=attrs)
+            datasets = DataFrame(columns=display_attrs)
         else:
             datasets = DataFrame(response)
+            for attr in attrs:
+                if '.' in attr:
+                    entity, attribute_to_extract = attr.split('.')
+                    datasets[attr] = datasets[entity].map(extract_attribute(attribute_to_extract))
+            for attr in attrs:
+                # if no dot supplied, just display the code of the space, project or experiment
+                if attr in ['space', 'project', 'experiment', 'sample']:
+                    datasets[attr] = datasets[attr].map(extract_nested_identifier)
+
             datasets['registrationDate'] = datasets['registrationDate'].map(format_timestamp)
             datasets['modificationDate'] = datasets['modificationDate'].map(format_timestamp)
             datasets['experiment'] = datasets['experiment'].map(extract_nested_identifier)
@@ -3351,13 +3447,18 @@ class Openbis:
             if isinstance(props, str):
                 props = [props]
             for prop in props:
-                datasets[prop.upper()] = datasets['properties'].map(lambda x: x.get(prop.upper(), ''))
-                attrs.append(prop.upper())
+                if datasets.get('properties') is not None:
+                    datasets[prop.upper()] = datasets['properties'].map(
+                        lambda x: x.get(prop.upper(), '')
+                    )
+                else:
+                    datasets[prop.upper()] = ''
+                display_attrs.append(prop.upper())
 
         return Things(
             openbis_obj = self,
             entity = 'dataset',
-            df = datasets[attrs],
+            df = datasets[display_attrs],
             identifier_name = 'permId',
             start_with=start_with,
             count=count,
@@ -3430,40 +3531,63 @@ class Openbis:
                 props=props,
             )
 
+
     def _sample_list_for_response(
-        self, response, props=None,
+        self, response, attrs=None, props=None,
         start_with=None, count=None, totalCount=0
     ):
-        """returns a Things object, containing a DataFrame plus some additional information
+        """returns a Things object, containing a DataFrame plus additional information
         """
+        def extract_attribute(attribute_to_extract):
+            def return_attribute(obj):
+                if obj is None: return ''
+                return obj.get(attribute_to_extract,'')
+            return return_attribute
 
         parse_jackson(response)
-        attrs = ['identifier', 'permId', 'experiment', 'type',
+
+        if attrs is None: attrs = []
+        default_attrs = ['identifier', 'permId', 'type',
                  'registrator', 'registrationDate', 'modifier', 'modificationDate']
+        display_attrs = default_attrs + attrs
+
         if len(response) == 0:
-            samples = DataFrame(columns=attrs)
+            samples = DataFrame(columns=display_attrs)
         else:
             samples = DataFrame(response)
+            for attr in attrs:
+                if '.' in attr:
+                    entity, attribute_to_extract = attr.split('.')
+                    samples[attr] = samples[entity].map(extract_attribute(attribute_to_extract))
+            for attr in attrs:
+                # if no dot supplied, just display the code of the space, project or experiment
+                if attr in ['space', 'project', 'experiment']:
+                    samples[attr] = samples[attr].map(extract_nested_identifier)
+
             samples['registrationDate'] = samples['registrationDate'].map(format_timestamp)
             samples['modificationDate'] = samples['modificationDate'].map(format_timestamp)
             samples['registrator'] = samples['registrator'].map(extract_person)
             samples['modifier'] = samples['modifier'].map(extract_person)
             samples['identifier'] = samples['identifier'].map(extract_identifier)
             samples['permId'] = samples['permId'].map(extract_permid)
-            samples['experiment'] = samples['experiment'].map(extract_nested_identifier)
             samples['type'] = samples['type'].map(extract_nested_permid)
 
         if props is not None:
             if isinstance(props, str):
                 props = [props]
             for prop in props:
-                samples[prop.upper()] = samples['properties'].map(lambda x: x.get(prop.upper(), ''))
-                attrs.append(prop.upper())
+                if samples.get('properties') is not None:
+                    samples[prop.upper()] = samples['properties'].map(
+                        lambda x: x.get(prop.upper(), '')
+                    )
+                else:
+                    samples[prop.upper()] = ''
+                display_attrs.append(prop.upper())
 
         return Things(
             openbis_obj = self,
             entity = 'sample',
-            df = samples[attrs],
+            df = samples[display_attrs],
             identifier_name = 'identifier',
             start_with=start_with,
             count=count,
diff --git a/pybis/src/python/setup.py b/pybis/src/python/setup.py
index 08a6e22b1766a31aa4cf8219855a2d1023e9b372..db38bf27d7f69d51f0c738a33c5256a775f71836 100644
--- a/pybis/src/python/setup.py
+++ b/pybis/src/python/setup.py
@@ -11,7 +11,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
 
 setup(
     name='PyBIS',
-    version= '1.11.1',
+    version= '1.12.0',
     author='Swen Vermeul • ID SIS • ETH Zürich',
     author_email='swen@ethz.ch',
     description='openBIS connection and interaction, optimized for using with Jupyter',