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

Deprecate clock_tick context manager #39

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Conversation

multigl
Copy link
Contributor

@multigl multigl commented Aug 31, 2017

  • backwards compatible deprecation of clock_tick context manager
  • ensure activity set when changing temporal properties and model clocked with activity_cls
  • session and mapper event life cycle tests

@multigl multigl added this to the Version 1.0 milestone Aug 31, 2017
@multigl
Copy link
Contributor Author

multigl commented Sep 6, 2017

Resolves #28


def __get__(self, instance, owner):
if not instance:
return None
Copy link
Contributor

@dargueta-work dargueta-work Oct 4, 2017

Choose a reason for hiding this comment

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

You might want to use return self here, because otherwise any attempt to access reset_activity or activity_required from an instance of a class using it will crash with an AttributeError:

A minimal example:

import types

ns = types.SimpleNamespace(activity=1)

# This works
ActivityState.reset_activity(ns, None)

class B:
    astate = ActivityState()

# This crashes
B.astate.reset_activity(ns, None)


def __get__(self, instance, owner):
if not instance:
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

Same idea here about returning self.

@@ -78,6 +80,10 @@ def temporal_map(*track, mapper: orm.Mapper, activity_class=None, schema=None):
backref_name = '%s_clock' % entity_table.name
clock_properties['activity'] = \
orm.relationship(lambda: activity_class, backref=backref_name)
cls.activity = ActivityState()
event.listen(cls, 'expire', ActivityState.reset_activity)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@NicoleZuckerman this the expire for the activity descriptor

@@ -94,24 +100,19 @@ def temporal_map(*track, mapper: orm.Mapper, activity_class=None, schema=None):
clock_model=clock_model,
activity_cls=activity_class
)

cls.current_clock = ClockState()
event.listen(cls, 'expire', ClockState.reset_tick)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@NicoleZuckerman this is the expire for the current_clock descriptor

@multigl
Copy link
Contributor Author

multigl commented Oct 23, 2017

relevant sqlalchemy documentation on instance events: http://docs.sqlalchemy.org/en/rel_1_1/orm/events.html#instance-events

if not instance:
return self

vclock = getattr(instance, 'vclock') or 0
Copy link
Contributor

@dargueta-work dargueta-work Oct 24, 2017

Choose a reason for hiding this comment

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

Using getattr with a string constant like this is equivalent to instance.vclock. This will still crash with an AttributeError unless you pass a default value as the third argument. Did you mean to do that?

vclock = getattr(instance, 'vclock', 0)

setattr(instance, '__temporal_current_tick', clock_tick)
instance.clock.append(clock_tick)

return getattr(instance, '__temporal_current_tick')
Copy link
Contributor

Choose a reason for hiding this comment

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

Same note here about using getattr without a default argument.

However, be warned: attributes starting with double underscores may not behave as you expect. Python has a concept of "private variables" that may result in an AttributeError getting thrown.

From the docs:

Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class.

Because it's required to occur inside the class definition I think this will be okay, but I thought I'd point it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I think in this case it's okay, but it's a good call out

DeprecationWarning)
if self.temporal_options.activity_cls:
if not activity:
raise ValueError
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we put an error message here please? It'll make debugging easier. I think the original error message might suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 should probably be:

raise ValueError("activity is missing on edit") from None


session.add(new_clock_tick)
return
Copy link
Contributor

@dargueta-work dargueta-work Oct 24, 2017

Choose a reason for hiding this comment

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

return isn't necessary at the end of a function. You can remove this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left it in so it looks less confusing to me, but I'm strange that way 😆

Copy link
Contributor Author

@multigl multigl left a comment

Choose a reason for hiding this comment

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

looking good, looking real good


# clear out db
transaction.rollback()
second_session.close()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is pretty neat, might also want to refetch the model from the first session and see that the vclock is two there

activity=create_activity)
session.add(t)

assert getattr(t, '__temporal_current_tick', None)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if we want to assert that it's an instance of ClockTick (or whatever it actually is) and has properties we expect?

assert t.activity
session.commit()
assert not getattr(t, '__temporal_current_tick', None)
assert not t.activity
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yesssss


T_PROPS = typing.TypeVar(
'T_PROP', orm.RelationshipProperty, orm.ColumnProperty)


class ActivityState:
def __set__(self, instance, value):
assert instance.temporal_options.activity_cls, "Make this better Joey"
Copy link
Contributor

@dargueta-work dargueta-work Oct 27, 2017

Choose a reason for hiding this comment

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

Can we change this error message to something a bit more helpful? 🙂 Perhaps:

if instance.temporal_options.activity_cls:
    raise RuntimeError(
        "Can't set activity state on instance of %r because the activity class is None."
        % type(instance).__name__)

(That's assuming that my interpretation of this assertion is correct.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants