-
Notifications
You must be signed in to change notification settings - Fork 16
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
implement a Field class similar to fields in Django, Marshmallow and DRF #68
Changes from 3 commits
d3f8315
aba70dd
9950336
d95b4f2
80fc320
48654e0
dad883b
4694f16
e3c0483
7a57ea8
850c6a8
f063e42
0911643
47ee88f
50b94d2
f9b396e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,8 @@ | |
import numpy as np | ||
import h5py | ||
from carousel.core.data_readers import DataReader | ||
from carousel.core import Q_ # UREG | ||
from django.db.models import AutoField | ||
from carousel.core.data_sources import DataParameter | ||
from carousel.core import Q_ | ||
import logging | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
@@ -19,9 +19,11 @@ def copy_model_instance(obj): | |
""" | ||
https://djangosnippets.org/snippets/1040/ | ||
""" | ||
return {f.name: getattr(obj, f.name) for f in obj._meta.get_fields() | ||
if not isinstance(f, AutoField) and | ||
f not in obj._meta.parents.values()} | ||
meta = getattr(obj, '_meta') # make pycharm happy | ||
# dictionary of model values excluding auto created and related fields | ||
return {f.name: getattr(obj, f.name) | ||
for f in meta.get_fields(include_parents=False) | ||
if not f.auto_created} | ||
|
||
|
||
# TODO: make parameters consistent for all readers | ||
|
@@ -58,8 +60,10 @@ def load_data(self, *args, **kwargs): | |
""" | ||
# get positional argument names from parameters and apply them to args | ||
# update data with additional kwargs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a lot of context I need to catch-up on here... |
||
argpos = {v['argpos']: k for k, v in self.parameters.iteritems() | ||
if 'argpos' in v} | ||
argpos = { | ||
v['extras']['argpos']: k for k, v in self.parameters.iteritems() | ||
if 'argpos' in v['extras'] | ||
} | ||
data = dict( | ||
{argpos[n]: a for n, a in enumerate(args)}, **kwargs | ||
) | ||
|
@@ -91,30 +95,32 @@ def __init__(self, parameters=None, meta=None): | |
raise AttributeError('model not specified in Meta class') | ||
#: Django model | ||
self.model = meta.model | ||
all_field_names = [f.name for f in self.model._meta.get_fields()] | ||
model_meta = getattr(self.model, '_meta') # make pycharm happy | ||
# model fields excluding AutoFields and related fields like one-to-many | ||
all_model_fields = [ | ||
f for f in model_meta.get_fields(include_parents=False) | ||
if not f.auto_created | ||
] | ||
all_field_names = [f.name for f in all_model_fields] # field names | ||
# use all fields if no parameters given | ||
if parameters is None: | ||
parameters = dict.fromkeys( | ||
parameters = DataParameter.fromkeys( | ||
all_field_names, {} | ||
) | ||
fields = getattr(meta, 'fields', all_field_names) | ||
fields = getattr(meta, 'fields', all_field_names) # specified fields | ||
LOGGER.debug('fields:\n%r', fields) | ||
exclude = getattr(meta, 'exclude', []) | ||
model_meta_parents_values = self.model._meta.parents.values() | ||
for f in self.model._meta.fields: | ||
# pop and skip any AutoFields or parents | ||
if isinstance(f, AutoField) or f in model_meta_parents_values: | ||
parameters.pop(f.name, None) | ||
continue | ||
exclude = getattr(meta, 'exclude', []) # specifically excluded fields | ||
for f in all_model_fields: | ||
# skip any fields not specified in data source | ||
if f.name not in fields or f.name in exclude: | ||
LOGGER.debug('skipping %s', f.name) | ||
continue | ||
# add field to parameters or update parameters with field type | ||
param_dict = {'ftype': f.get_internal_type()} | ||
if f.name in parameters: | ||
parameters[f.name].update(param_dict) | ||
parameters[f.name]['extras'].update(param_dict) | ||
else: | ||
parameters[f.name] = param_dict | ||
parameters[f.name] = DataParameter(**param_dict) | ||
super(DjangoModelReader, self).__init__(parameters) | ||
|
||
def load_data(self, model_instance, *args, **kwargs): | ||
|
@@ -138,8 +144,9 @@ def load_data(self, h5file, *args, **kwargs): | |
h5data = dict.fromkeys(self.parameters) | ||
for param, attrs in self.parameters.iteritems(): | ||
LOGGER.debug('parameter:\n%r', param) | ||
node = attrs['node'] # full name of node | ||
member = attrs.get('member') # composite datatype member | ||
node = attrs['extras']['node'] # full name of node | ||
# composite datatype member | ||
member = attrs['extras'].get('member') | ||
if member is not None: | ||
# if node is a table then get column/field/description | ||
h5data[param] = np.asarray(h5f[node][member]) # copy member | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,24 +77,28 @@ class Registry(dict): | |
calling the :func:`super` built-in function. | ||
|
||
By default there are no meta attributes, only the register method. | ||
To set meta attributes, in a subclass, add them in the constructor:: | ||
To set meta attributes, in a subclass, set the ``meta_names`` class | ||
attribute in the subclass:: | ||
|
||
def __init__(self): | ||
self.meta1 = {} | ||
self.meta2 = {} | ||
... | ||
class MyRegistry(Registry): | ||
meta_names = ['meta1', 'meta2', ...] | ||
|
||
The ``Registry`` superclass will check that the meta names are not already | ||
attributes and then set instance attributes as empty dictionaries in the | ||
subclass. To document them, use the class docstring or document them in the | ||
documentation API. | ||
""" | ||
meta_names = [] | ||
|
||
def __init__(self): | ||
if hasattr(self, 'meta_names'): | ||
self.meta_names = _listify(self.meta_names) | ||
if [m for m in self.meta_names if m.startswith('_')]: | ||
raise AttributeError('No underscores in meta names.') | ||
for m in self.meta_names: | ||
# check for m in cls and bases | ||
if m in dir(Registry): | ||
msg = ('Class %s already has %s member.' % | ||
(self.__class__.__name__, m)) | ||
raise AttributeError(msg) | ||
self.meta_names = _listify(self.meta_names) # convert to list | ||
for m in self.meta_names: | ||
# check for m in cls and bases | ||
if m in dir(Registry): | ||
msg = ('Class %s already has %s member.' % | ||
(self.__class__.__name__, m)) | ||
raise AttributeError(msg) | ||
setattr(self, m, {}) # create instance attribute and set to dict() | ||
super(Registry, self).__init__() | ||
|
||
def register(self, newitems, *args, **kwargs): | ||
|
@@ -105,13 +109,10 @@ def register(self, newitems, *args, **kwargs): | |
items, keys are not allowed to override existing keys in the | ||
registry. | ||
:type newitems: mapping | ||
:param args: Key-value pairs of meta-data. The key is the meta-name, | ||
and the value is a map of the corresponding meta-data for new | ||
item-keys. Each set of meta-keys must be a subset of new item-keys. | ||
:type args: tuple or list | ||
:param args: Positional arguments with meta data corresponding to order | ||
of meta names class attributes | ||
:param kwargs: Maps of corresponding meta for new keys. Each set of | ||
meta keys must be a subset of the new item keys. | ||
:type kwargs: mapping | ||
:raises: | ||
:exc:`~carousel.core.exceptions.DuplicateRegItemError`, | ||
:exc:`~carousel.core.exceptions.MismatchRegMetaKeysError` | ||
|
@@ -121,19 +122,13 @@ def register(self, newitems, *args, **kwargs): | |
raise DuplicateRegItemError(self.viewkeys() & newkeys) | ||
self.update(newitems) # register new item | ||
# update meta fields | ||
if any(isinstance(_, dict) for _ in args): | ||
# don't allow kwargs to passed as args! | ||
raise TypeError('*args should be all named tuples.') | ||
# combine the meta args and kwargs together | ||
kwargs.update(args) # doesn't work for combo of dicts and tuples | ||
kwargs.update(zip(self.meta_names, args)) | ||
for k, v in kwargs.iteritems(): | ||
meta = getattr(self, k) # get the meta attribute | ||
if v: | ||
if not v.viewkeys() <= newkeys: | ||
raise MismatchRegMetaKeysError(newkeys - v.viewkeys()) | ||
meta.update(v) # register meta | ||
# TODO: default "tag" meta field for all registries? | ||
# TODO: append "meta" to all meta fields, so they're easier to find? | ||
|
||
def unregister(self, items): | ||
""" | ||
|
@@ -258,7 +253,7 @@ def set_param_file_or_parameters(mcs, attr): | |
attr['param_file'] = os.path.join(cls_path, cls_file) | ||
else: | ||
attr['parameters'] = dict.fromkeys( | ||
k for k in attr if not k.startswith('_') | ||
k for k, v in attr.iteritems() if isinstance(v, Parameter) | ||
) | ||
for k in attr['parameters']: | ||
attr['parameters'][k] = attr.pop(k) | ||
|
@@ -277,3 +272,24 @@ def get_parents(bases, parent): | |
:rtype: list | ||
""" | ||
return [b for b in bases if isinstance(b, parent)] | ||
|
||
|
||
class Parameter(dict): | ||
_attrs = [] | ||
|
||
def __init__(self, *args, **kwargs): | ||
items = dict(zip(self._attrs, args)) | ||
extras = {} | ||
for key, val in kwargs.iteritems(): | ||
if key in self._attrs: | ||
items[key] = val | ||
else: | ||
extras[key] = val | ||
LOGGER.warning('This key: "%s" is not an attribute.', key) | ||
super(Parameter, self).__init__(items, extras=extras) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, so we're keeping the extra attribute fields entered. It's probably dealt with later but I was wondering what would happen to the attribute fields not entered when instantiating. |
||
|
||
def __repr__(self): | ||
fmt = ('<%s(' % self.__class__.__name__) | ||
fmt += ', '.join('%s=%r' % (k, v) for k, v in self.iteritems()) | ||
fmt += ')>' | ||
return fmt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
snippet doesn't seem to work...
I'm trying to understand where '_meta' comes from right now. And 'get_fields' as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the snippet has been removed, it was to copy a model instance using the Django
_meta
:But Django formalized and refactored the
_meta
Options API starting with Django-1.8, so this snippet was probably removed since it is now obsolete.Django recommends using
get_fields()
in the new Options API and discourages using_fields
as indicated in the snippet. Therefore I replaced the snippet with[f for f in get_fields(include_parents=False) if not f.auto_created]
but I didn't remove the link.For more background on the new _meta Options API see this summer of code refactoring project on the django wiki, Django ticket 12663 and Django PR 3114.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the outdated link to the snippet in 0b91a5a but forgot to change the commit message, sorry!