Created
October 31, 2019 20:13
-
-
Save campagnola/5630870e45ed8a9f66d854fee11271f0 to your computer and use it in GitHub Desktop.
Composable, inheritance-free data modeling in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Goals: | |
- Extensible data modeling in python | |
- Does _not_ use inheritance as a mechanism for model extension | |
- Lightweight / simple infrastructure | |
- Data objects have reasonably nice user API | |
""" | |
class Model: | |
"""Base class providing infrastructure for composable data modeling | |
""" | |
def __init__(self, attachments=()): | |
self._attachments = {} | |
self._attached_types = set() | |
for a in attachments: | |
self.attach(a) | |
def attach(self, attachment): | |
name = attachment._attaches_to[type(self)] | |
assert name not in self._attachments | |
self._attachments[name] = attachment | |
self._attached_tyes.add(type(attachment)) | |
def __getattr__(self, name): | |
return self._attachments[name] | |
def has(self, attachment_type): | |
return attachment_type in self._attached_types | |
### Data model classes | |
class Image(Model): | |
data = None | |
class AcquiredImageMeta(Model): | |
_attaches_to = {Image: 'acq'} | |
timestamp = None | |
transform = None | |
device = None | |
class CameraImageMeta(Model): | |
_attaches_to = {AcquiredImageMeta: 'camera'} | |
binning = None | |
region = None | |
exposure = None | |
class LaserScanImageMeta(Model): | |
_attaches_to = {AcquiredImageMeta: 'laser_scan'} | |
pixel_dwell_time = None | |
laser_power = None | |
laser_wavelength = None | |
### Use example | |
# one-shot construction: | |
img = Image(data, attachments=[ | |
AcquiredImageMeta(timestamp, transform, attachments=[ | |
CameraImageMeta(region, binning, exposure) | |
]) | |
]) | |
# piecewise construction (because sometimes all of the pieces can't be | |
# easily instantiated at the same time) | |
img = Image(data) | |
img.attach(AcquiredImageMeta(timestamp, transform)) | |
img.acq.attach(CameraImageMeta(region, binning, exposure)) | |
# introspection | |
assert img.has(AcquiredImageMeta) | |
assert img.acq.has(CameraImageMeta) | |
# with modifications to the Model class, we could also support this kind of introspection: | |
# (all Image instances have an `acq` attribute, but it is set to None if the attachment is not present) | |
assert img.acq is not None | |
# data access | |
assert img.acq.timestamp == timestamp | |
assert img.acq.camera.binning == binning |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment