Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next few object model changes #42

Merged
merged 4 commits into from
Sep 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions ispyb/model/README.MD
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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._appid)
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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'),
))