Skip to content

Commit

Permalink
Next few object model changes
Browse files Browse the repository at this point in the history
Merge pull request #42 from DiamondLightSource/objectmodel
  • Loading branch information
Anthchirp committed Sep 3, 2018
2 parents 4af4b6d + e81cea1 commit bf2b417
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 0 deletions.
32 changes: 32 additions & 0 deletions ispyb/model/README.MD
@@ -0,0 +1,32 @@
ISPyB database access via model objects
=======================================

*Note: The model object API is not finalized yet and should be treated as
unstable for the time being.*

Model objects provide a pythonic view on the information stored in ISPyB.
Database records are presented as Python objects and can be chained easily
to access related records. For example:

```python
import ispyb
with ispyb.open(...) as i:
dc = i.get_data_collection(12345)
dc.image_count # number of images in data collection 12345

# To get the orientation of a grid scan data collection:
dc.group.gridinfo.orientation

# Name of a ProcessingJob
pj = i.get_processing_job(1234)
print(pj.name)

# A human readable summary of the ProcessingJob
print(pj)

# A list of parameters for the ProcessingJob
list(pj.parameters)
```

Model objects only query the database when necessary and cache results locally
when possible. Every object offers a .reload() method to force loading/updating.
24 changes: 24 additions & 0 deletions ispyb/model/__future__.py
Expand Up @@ -10,9 +10,16 @@
import configparser
except ImportError:
import ConfigParser as configparser
import logging
import mysql.connector

def enable(configuration_file):
logging.getLogger('ispyb').info(
'NOTICE: This code uses __future__ functionality in the ISPyB API. '
'This enables unsupported and potentially unstable code, which may '
'change from version to version without warnings. Here be dragons.'
)

global _db, _db_cc
'''Enable access to features that are currently under development.'''

Expand All @@ -30,6 +37,8 @@ def enable(configuration_file):
_db = mysql.connector.connect(host=host, port=port, user=username, password=password, database=database)
_db_cc = DictionaryContextcursorFactory(_db.cursor)

import ispyb.model.datacollection
ispyb.model.datacollection.DataCollection.integrations = _get_linked_autoprocintegration_for_dc
import ispyb.model.gridinfo
ispyb.model.gridinfo.GridInfo.reload = _get_gridinfo
import ispyb.model.processingprogram
Expand Down Expand Up @@ -82,6 +91,7 @@ def __call__(self, **parameters):
return self._contextmanager_factory(parameters)

def _get_gridinfo(self):
# https://jira.diamond.ac.uk/browse/MXSW-1173
with _db_cc() as cursor:
cursor.run("SELECT * "
"FROM GridInfo "
Expand All @@ -90,6 +100,7 @@ def _get_gridinfo(self):
self._data = cursor.fetchone()

def _get_autoprocprogram(self):
# https://jira.diamond.ac.uk/browse/SCI-7414
with _db_cc() as cursor:
cursor.run("SELECT processingCommandLine as commandLine, processingPrograms as programs, "
"processingStatus as status, processingMessage as message, processingEndTime as endTime, "
Expand All @@ -99,3 +110,16 @@ def _get_autoprocprogram(self):
"WHERE autoProcProgramId = %s "
"LIMIT 1;", self._app_id)
self._data = cursor.fetchone()

@property
def _get_linked_autoprocintegration_for_dc(self):
# not yet requested
import ispyb.model.integration
with _db_cc() as cursor:
cursor.run("SELECT * "
"FROM AutoProcIntegration "
"WHERE dataCollectionId = %s", self.dcid)
return [
ispyb.model.integration.IntegrationResult(ir['autoProcIntegrationId'], self._db, preload=ir)
for ir in cursor.fetchall()
]
5 changes: 5 additions & 0 deletions ispyb/model/datacollection.py
Expand Up @@ -43,6 +43,11 @@ def group(self):
self._cache_group = DataCollectionGroup(self.dcgid, self._db.conn)
return self._cache_group

@property
def integrations(self):
'''Returns the list of IntegrationResult objects associated with this DC.'''
raise NotImplementedError('TODO: Not implemented yet')

@property
def file_template_full(self):
'''Template for file names with full directory path. As with file_template
Expand Down
7 changes: 7 additions & 0 deletions ispyb/model/gridinfo.py
Expand Up @@ -32,6 +32,13 @@ def dcgid(self):
information.'''
return self._dcgid

def __bool__(self):
'''GridInfo object evaluates to True in a boolean context if grid
information exists in the database. Otherwise it evaluates to False.'''
self.load()
return self._data is not None
__nonzero__ = __bool__ # Python 2 compatibility

def __repr__(self):
'''Returns an object representation, including the DataCollectionGroupID,
the database connection interface object, and the cache status.'''
Expand Down
87 changes: 87 additions & 0 deletions ispyb/model/integration.py
@@ -0,0 +1,87 @@
from __future__ import absolute_import, division, print_function

import collections

import ispyb.model
import ispyb.model.processingprogram

class IntegrationResult(ispyb.model.DBCache):
'''An object representing a data collection integration result database entry.
The object lazily accesses the underlying database when necessary and
exposes record data as python attributes.
'''

def __init__(self, apiid, db_conn, preload=None):
'''Create a IntegrationResult object for a defined AutoProcIntegrationID.
Requires a database connection object exposing further data access
methods.
:param apiid: AutoProcIntegrationID
:param db_conn: ISPyB database connection object
:return: An IntegrationResult object representing the database entry for
the specified AutoProcIntegrationID
'''
self._cache_dc = None
self._cache_app = None
self._db = db_conn
self._apiid = int(apiid)
if preload:
self._data = preload

def reload(self):
'''Load/update information from the database.'''
raise NotImplementedError('TODO: Not implemented yet')

@property
def DCID(self):
'''Returns the main data collection id.'''
dcid = self._data['dataCollectionId']
if dcid is None:
return None
return int(dcid)

@property
def data_collection(self):
'''Returns the DataCollection model object for the main data collection of
the ProcessingJob.'''
if self._cache_dc is None:
if self.DCID is None:
return None
self._cache_dc = self._db.get_data_collection(self.DCID)
return self._cache_dc

@property
def APIID(self):
'''Returns the AutoProcIntegrationID.'''
return self._apiid

def __repr__(self):
'''Returns an object representation, including the AutoProcIntegrationID,
the database connection interface object, and the cache status.'''
return '<IntegrationResult #%d (%s), %r>' % (
self._apiid,
'cached' if self.cached else 'uncached',
self._db
)

def __str__(self):
'''Returns a pretty-printed object representation.'''
if not self.cached:
return 'IntegrationResult #%d (not yet loaded from database)' % self._apiid
return ('\n'.join((
'IntegrationResult #{ir.APIID}',
' DCID : {ir.DCID}',
' APPID : {ir.APPID}',
' Start Image : {ir.image_start}',
' End Image : {ir.image_end}',
' Detector Distance: {ir.detector_distance}',
' Timestamp : {ir.timestamp}',
))).format(ir=self)

ispyb.model.add_properties(IntegrationResult, (
('APPID', 'autoProcProgramId'),
('image_start', 'startImageNumber'),
('image_end', 'endImageNumber'),
('detector_distance', 'refinedDetectorDistance'),
('timestamp', 'recordTimeStamp'),
))

0 comments on commit bf2b417

Please sign in to comment.