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

Immutable columns #138

Closed
jace opened this issue Aug 29, 2017 · 5 comments
Closed

Immutable columns #138

jace opened this issue Aug 29, 2017 · 5 comments

Comments

@jace
Copy link
Member

jace commented Aug 29, 2017

As discussed in #100, there is a need for immutable columns on models with the following characteristics:

  1. Once a value is set (usually at init), it cannot be changed.
  2. The immutable columns on a model are available for inspection.

This Stack Overflow answer demonstrates how to achieve immutability using SQLAlchemy events. While this doesn't validate within the database, it's not a major concern as immutability is an application data model feature: immutable fields are a signal that cache fields in other models (or in other databases) can hold their cached values indefinitely without risk.

Note that an immutable field is not necessarily a natural key and should not be used to identify the row. It may, for instance, be a type field indicating the type of data the row holds.

We can use the annotation mechanism introduced with RoleMixin in #109 to implement an ImmutableMixin or AnnotationMixin.

@jace
Copy link
Member Author

jace commented Aug 30, 2017

If we take the generic annotation approach, we have distinct use cases that call for immutability:

  1. Immutable column (for whatever reason)
  2. Private surrogate primary key (a serial number)
  3. Public surrogate primary key (a UUID)
  4. Natural unique key (ideally indexed too)
  5. Natural non-unique key (like an email claim, where multiple principals can claim it)

All these field types need immutability, but we get a couple extra use cases:

  1. An autogenerated get method can look up instances by any primary or unique key (although slightly tricky with something like the email key where the lookup is on sql.func.lower(email) as that expression will need to be added as an annotated hybrid property to the model).
  2. An external facing API can also advertise ability to lookup by these columns.

We're aiming for an API that looks like this, where immutable is a pre-declared wrapper that attaches a string annotation 'immutable' to the column (similar to how SQLAlchemy's deferred annotation works):

class MyModel(db.Model):
    my_field = immutable(db.Column(db.Unicode(250)))

How would this API allow multiple annotations? Options:

  1. With multiple wrappers: immutable(publickey(Column(…)))
  2. With a single wrapper that adds multiple annotations: immutable_key(Column(…))

@jace
Copy link
Member Author

jace commented Aug 30, 2017

SQLAlchemy's extensive use of __slots__ starting with release 1.0 will interfere with the approach we took in #109 of sticking flags into objects and reading them back in the mapper_configured event. In particular, using immutable(deferred(Column(…))) won't work as the deferred function returns a ColumnProperty instance (instead of a Column instance) which has __slots__ defined (the Column class does not).

We will instead need a global dictionary that maps objects to these attributes, which is then read back during a mapper_configured event.

@jace
Copy link
Member Author

jace commented Aug 30, 2017

Problem with using a global dictionary: if an attribute is declared with declared_attr in the mixin, it transforms into a hybrid property in the subclass and can no longer be discovered in the global dictionary as hash(obj) is no longer the same.

jace added a commit that referenced this issue Aug 30, 2017
…column properties

Fixes #139 and addresses the concerns raised in #138.
jace added a commit that referenced this issue Aug 31, 2017
…column properties (#140)

Fixes #139 and addresses the concerns raised in #138.
@jace
Copy link
Member Author

jace commented Sep 1, 2017

SQLAlchemy allows listening to mapper_configured events on all mappers. An AnnotationMixin base class is unnecessary if it doesn't provide any methods.

However, since we are inserting __annotations__ and __annotations_by_attr__ in every class that is processed, these will show up everywhere and may have unintended side effects.

@jace
Copy link
Member Author

jace commented Sep 3, 2017

Implementation and additional discussion in #141.

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

No branches or pull requests

1 participant