-
Notifications
You must be signed in to change notification settings - Fork 664
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
strict attribute setting issue (#1092) #1186
Changes from 21 commits
14bd0d4
224ee3b
2c96fca
a71ed70
5b0cf1e
4cb4345
5444614
5c2f0de
3d7aac3
000c5f2
7c50845
db9a9e1
fa83729
b56254c
e84c3fd
68c2da4
d37b669
4dcfb7d
b9db71d
3c4d607
958e971
e7104a5
de695f4
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 |
---|---|---|
|
@@ -30,6 +30,8 @@ Enhancements | |
* added support for Tinker TXYZ and ARC files | ||
* libmdaxdr and libdcd classes can now be pickled (PR #1680) | ||
* speed up atomgroup.rotate when point = (0, 0, 0) (Issue #1707) | ||
* Universe.add_TopologyAttr now accepts strings to add a given attribute | ||
to the Universe (Issue #1092, PR #1186) | ||
|
||
Deprecations | ||
|
||
|
@@ -81,6 +83,8 @@ Changes | |
tuple (filename_or_array, trajectory_type) (Issue #1613) | ||
* docs: URLs to (www|docs).mdanalysis.org now link to SSL-encrypted site | ||
(see issue MDAnalysis/MDAnalysis.github.io#61) | ||
* attributes can not be assigned to AtomGroups (and similar objects) unless | ||
they are part of the Universe topology (Issue #1092 PR #1186) | ||
|
||
|
||
06/29/17 richardjgowers, rathann, jbarnoud, orbeckst, utkbansal | ||
|
@@ -965,7 +969,7 @@ Fixes | |
|
||
04/01/14 orbeckst, jandom, zhuyi.xue, xdeupi, tyler.je.reddy, | ||
manuel.nuno.melo, danny.parton, sebastien.buchoux, denniej0, | ||
rmcgibbo, richardjgowers, lennardvanderfeltz, bernardin.alejandro | ||
rmcgibbo, richardjgowers, lennardvanderfeltz, bernardin.alejandro | ||
matthieu.chavent | ||
|
||
* 0.8.1 | ||
|
@@ -1480,7 +1484,7 @@ Fixes | |
alternative atoms and insertion codes that are not needed for | ||
handling MD simulation data). | ||
- The default behaviour of MDAnalysis can be set through the flag | ||
MDAnalysis.core.flag['permissive_pdb_reader']. The default is True. | ||
MDAnalysis.core.flag['permissive_pdb_reader']. The default is True. | ||
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. wrong indentation |
||
- One can always manually select the PDB reader by providing the | ||
permissive keyword to Universe; e.g. Universe(...,permissive=False) | ||
will read the input file with the Bio.PDB reader. This might be | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,6 +164,8 @@ | |
_MULTIFRAME_WRITERS = {} | ||
_PARSERS = {} | ||
_SELECTION_WRITERS = {} | ||
# Registry of TopologyAttributes | ||
_TOPOLOGY_ATTRS = {} | ||
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. So this thing is global. Who adds to this and when? 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. it's like the _READERS and _PARSERS dicts, it just keeps track of any Classes that get defined 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. Also when a user actually wants to add something like 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. I just tested. This then means we can only add attributes for which we have defined classes. 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. Yeah, so things are included as they are defined. It's always been the case (with the new system) that for an Attribute to exist there needs to be a class behind it |
||
|
||
# Storing anchor universes for unpickling groups | ||
import weakref | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,15 +153,14 @@ def make_classes(): | |
|
||
# The 'GBase' middle man is needed so that a single topologyattr | ||
# patching applies automatically to all groups. | ||
GBase = bases[GroupBase] = _TopologyAttrContainer._subclass() | ||
GBase = bases[GroupBase] = _TopologyAttrContainer._subclass(is_group=True) | ||
for cls in groups: | ||
bases[cls] = GBase._subclass() | ||
|
||
# In the current Group-centered topology scheme no attributes apply only | ||
# to ComponentBase, so no need to have a 'CB' middle man. | ||
#CBase = _TopologyAttrContainer(singular=True) | ||
bases[cls] = GBase._subclass(is_group=True) | ||
# CBase for patching all components | ||
CBase = bases[ComponentBase] = _TopologyAttrContainer._subclass( | ||
is_group=False) | ||
for cls in components: | ||
bases[cls] = _TopologyAttrContainer._subclass(singular=True) | ||
bases[cls] = CBase._subclass(is_group=False) | ||
|
||
# Initializes the class cache. | ||
for cls in groups + components: | ||
|
@@ -184,27 +183,37 @@ class _TopologyAttrContainer(object): | |
The mixed subclasses become the final container classes specific to each | ||
:class:`Universe`. | ||
""" | ||
_singular = False | ||
|
||
@classmethod | ||
def _subclass(cls, singular=None): | ||
def _subclass(cls, is_group): | ||
"""Factory method returning :class:`_TopologyAttrContainer` subclasses. | ||
|
||
Parameters | ||
---------- | ||
singular : bool | ||
The :attr:`_singular` of the returned class will be set to | ||
*singular*. It controls the type of :class:`TopologyAttr` addition. | ||
is_group : bool | ||
The :attr:`_is_group` of the returned class will be set to | ||
*is_group*. This is used to distinguish between Groups (AtomGroup | ||
etc.) and Components (Atom etc.) in internal methods when | ||
considering actions such as addition between objects, adding | ||
TopologyAttributes to them. | ||
|
||
Returns | ||
------- | ||
type | ||
A subclass of :class:`_TopologyAttrContainer`, with the same name. | ||
""" | ||
if singular is not None: | ||
return type(cls.__name__, (cls,), {'_singular': bool(singular)}) | ||
newcls = type(cls.__name__, (cls,), {'_is_group': bool(is_group)}) | ||
if is_group: | ||
newcls._SETATTR_WHITELIST = { | ||
'positions', 'velocities', 'forces', 'dimensions', | ||
'atoms', 'residue', 'residues', 'segment', 'segments', | ||
} | ||
else: | ||
return type(cls.__name__, (cls,), {}) | ||
newcls._SETATTR_WHITELIST = { | ||
'position', 'velocity', 'force', 'dimensions', | ||
'atoms', 'residue', 'residues', 'segment', | ||
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. Does this mean that I could add 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. Yeah you've found a loophole in everything :) Erm, so currently there's two whitelists, one for Groups and one for Components. The way to fix this would be to instead have a whitelist for each class (Atom, Res, Seg, AG, RG, SG). Not sure I'll find the time to fix this soonish, so this might have to get spun out into a low priority issue 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. Add a TODO comment to the code and raise an issue once this is merged... |
||
} | ||
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. Whitelist is the same in both cases. What is the purpose of singular in this particular case? 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. one version is singular (position, velocity, etc) one is plural (positions, veloctities) 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. why are the segments then plural? How can something have one position and multiple segments or atoms? 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. yeah you're right - fixed 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. The I don't understand the meaning of |
||
|
||
return newcls | ||
|
||
@classmethod | ||
def _mix(cls, other): | ||
|
@@ -249,12 +258,26 @@ def getter(self): | |
def setter(self, values): | ||
return attr.__setitem__(self, values) | ||
|
||
if cls._singular: | ||
setattr(cls, attr.singular, | ||
property(getter, setter, None, attr.singledoc)) | ||
else: | ||
if cls._is_group: | ||
setattr(cls, attr.attrname, | ||
property(getter, setter, None, attr.groupdoc)) | ||
cls._SETATTR_WHITELIST.add(attr.attrname) | ||
else: | ||
setattr(cls, attr.singular, | ||
property(getter, setter, None, attr.singledoc)) | ||
cls._SETATTR_WHITELIST.add(attr.singular) | ||
|
||
def __setattr__(self, attr, value): | ||
# `ag.this = 42` calls setattr(ag, 'this', 42) | ||
if not (attr.startswith('_') or # 'private' allowed | ||
attr in self._SETATTR_WHITELIST or # known attributes allowed | ||
hasattr(self, attr)): # preexisting (eg properties) allowed | ||
raise AttributeError( | ||
"Cannot set arbitrary attributes to a {}".format( | ||
'Group' if self._is_group else 'Component')) | ||
# if it is, we allow the setattr to proceed by deferring to the super | ||
# behaviour (ie do it) | ||
super(_TopologyAttrContainer, self).__setattr__(attr, value) | ||
|
||
|
||
class _MutableBase(object): | ||
|
@@ -429,7 +452,7 @@ def __hash__(self): | |
return hash((self._u, self.__class__, tuple(self.ix.tolist()))) | ||
|
||
def __len__(self): | ||
return len(self._ix) | ||
return len(self.ix) | ||
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. What is the significance of using 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. |
||
|
||
def __getitem__(self, item): | ||
# supports | ||
|
@@ -2780,7 +2803,7 @@ def __init__(self, base_group, selections, strings): | |
# its check, no self.attribute access can be made before this line | ||
self._u = base_group.universe | ||
self._selections = selections | ||
self.selection_strings = strings | ||
self._selection_strings = strings | ||
self._base_group = base_group | ||
self._lastupdate = None | ||
self._derived_class = base_group._derived_class | ||
|
@@ -2857,8 +2880,9 @@ def _ensure_updated(self): | |
|
||
def __getattribute__(self, name): | ||
# ALL attribute access goes through here | ||
# If the requested attribute isn't in the shortcut list, update ourselves | ||
if not name in _UAG_SHORTCUT_ATTRS: | ||
# If the requested attribute is public (not starting with '_') and | ||
# isn't in the shortcut list, update ourselves | ||
if not (name.startswith('_') or name in _UAG_SHORTCUT_ATTRS): | ||
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. Is this necessary due to the changes in the topology system or did you decide to apply the same philosophy of implicitly accepting private attributes to UAGs as well? 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. If it is the former, add a comment. 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. philosophy rather than necessity |
||
self._ensure_updated() | ||
# Going via object.__getattribute__ then bypasses this check stage | ||
return object.__getattribute__(self, name) | ||
|
@@ -2869,13 +2893,13 @@ def __reduce__(self): | |
# - recreate UAG as created through select_atoms (basegroup and selstrs) | ||
# even if base_group is a UAG this will work through recursion | ||
return (_unpickle_uag, | ||
(self._base_group.__reduce__(), self._selections, self.selection_strings)) | ||
(self._base_group.__reduce__(), self._selections, self._selection_strings)) | ||
|
||
def __repr__(self): | ||
basestr = super(UpdatingAtomGroup, self).__repr__() | ||
if not self.selection_strings: | ||
if not self._selection_strings: | ||
return basestr | ||
sels = "'{}'".format("' + '".join(self.selection_strings)) | ||
sels = "'{}'".format("' + '".join(self._selection_strings)) | ||
# Cheap comparison. Might fail for corner cases but this is | ||
# mostly cosmetic. | ||
if self._base_group is self.universe.atoms: | ||
|
@@ -2884,7 +2908,7 @@ def __repr__(self): | |
basegrp = "another AtomGroup." | ||
# With a shorthand to conditionally append the 's' in 'selections'. | ||
return "{}, with selection{} {} on {}>".format(basestr[:-1], | ||
"s"[len(self.selection_strings)==1:], sels, basegrp) | ||
"s"[len(self._selection_strings)==1:], sels, basegrp) | ||
|
||
# Define relationships between these classes | ||
# with Level objects | ||
|
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.
wrong indentation