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

strict attribute setting issue (#1092) #1186

Merged
merged 23 commits into from
Dec 7, 2017
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
14bd0d4
Work on Issue #1092
richardjgowers Nov 11, 2017
224ee3b
More tests for attribute setting
richardjgowers Nov 12, 2017
2c96fca
Fixed WHITELIST for singular and plural attributes
richardjgowers Nov 20, 2017
a71ed70
Changed to a _SETATTR_WHITELIST per class, (not shared)
richardjgowers Nov 21, 2017
5b0cf1e
Fixup
richardjgowers Nov 21, 2017
4cb4345
Simplified setattr across Component and Group
richardjgowers Nov 21, 2017
5444614
Fixed UAGs with restricted attribute setting
richardjgowers Nov 21, 2017
5c2f0de
TopologyAttrs now registered upon definiton
richardjgowers Nov 21, 2017
3d7aac3
Added u.add_TopologyAttribute with string
richardjgowers Nov 23, 2017
000c5f2
Removed TestCase usage from test_universe
richardjgowers Nov 23, 2017
7c50845
added tests for universe.add_TopologyAttr
richardjgowers Nov 23, 2017
db9a9e1
fix doc build error
kain88-de Dec 3, 2017
fa83729
removed segments from WHITELIST
richardjgowers Dec 3, 2017
b56254c
Added example to add_TopologyAttr
richardjgowers Dec 6, 2017
e84c3fd
Added explanation of _gen_initial_values
richardjgowers Dec 6, 2017
68c2da4
Renamed _TopologyAttrContainer._singular to _is_group
richardjgowers Dec 6, 2017
d37b669
clarified UAG upadte procedure
richardjgowers Dec 6, 2017
4dcfb7d
Changelog for attribute setting changes
richardjgowers Dec 6, 2017
b9db71d
Small fix to Bond TopologyAttrs
richardjgowers Dec 6, 2017
3c4d607
Further clarified is_group in TopologyAttrContainer.subclass
richardjgowers Dec 6, 2017
958e971
reverted change to Bond TopologyAttr
richardjgowers Dec 6, 2017
e7104a5
Fixed up CHANGELOG
richardjgowers Dec 6, 2017
de695f4
Updated Universe.add_TopologyAttr docstring
richardjgowers Dec 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong indentation

matthieu.chavent

* 0.8.1
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down
2 changes: 2 additions & 0 deletions package/MDAnalysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@
_MULTIFRAME_WRITERS = {}
_PARSERS = {}
_SELECTION_WRITERS = {}
# Registry of TopologyAttributes
_TOPOLOGY_ATTRS = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this thing is global. Who adds to this and when?

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also when a user actually wants to add something like color? Also is it possible to add custom attrs with this? (No need if not just want to know)

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down
80 changes: 52 additions & 28 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that I could add atoms or residues to an Atom or are there additional checks for the different levels or does it not matter?

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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...

}
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one version is singular (position, velocity, etc) one is plural (positions, veloctities)

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah you're right - fixed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The singular=False still has segment and segments.

I don't understand the meaning of singular in the context of the topology.


return newcls

@classmethod
def _mix(cls, other):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the significance of using ix instead of _ix?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


def __getitem__(self, item):
# supports
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is the former, add a comment.

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
Expand All @@ -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:
Expand All @@ -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
Expand Down
Loading