Entities

How? Simple, inherit from Entity and define some properties. Define a property with its type and possibly an extra string that gets included in the ‘CREATE TABLE’ statement. Example:

class User(Entity):
    name = Property(str)
    mail = Property(str, sql_extra="UNIQUE")

(In what follows I will often skip properties that are not essential to exemplify the topic at hand.)

Keys

If you try the above example, sparrow will complain, because there is no key. An Entity class can only have 1 key, and you define it with key. So if we for example want to have a UID attribute and define it as key, we would do:

class User(Entity):
    name = Property(str)
    UID = Property(int)
    key = Key(UID)

However, this would be irritating. We would have to come up with a unique UID every time we create a User. To solve this problem, we have a KeyProperty. This will use Postgres’ SERIAL type to create the property. Use it like this:

class User(Entity):
    name = Property(str)
    UID = KeyProperty()
    key = UID

We can shorten this even further:

class User(Entity):
    name = Property(str)
    key = UID = KeyProperty()

Note that for a KeyProperty, its value will be None until you insert the object into the database (which will provide the value for it).

A Key can also contain multiple properties:

class User(Entity):
    firstname = Property(str)
    lastname = Property(str)
    key = Key(firstname, lastname)

You can always use a key attribute of an object as if it was an entity:

u = User(...)
u.key = ("Evert", "Heylen")

In case of multiple properties, you need to put it in a tuple. Otherwise (also in the case of a KeyProperty, it has to be a simple value.

Constraints

There are two types of constraints: constraints for properties and object-wide constraints. The latter is only checked when calling __init__, update and insert. An example:

class User(Entity):
    name = Property(str)
    password = Property(str, constraint=lambda p: len(p) >= 8)  # Password of minimum length

    constraint = lambda u: u.name != u.password  # Don't use your name as password

References

Often, you want to keep a reference to other objects. In sparrow, you use a Reference to do so. A Reference will automatically create properties to fully save a key of another Entity class. Note that a Reference can not be constrained, but it can be used in a Key.:

class User(Entity):
    firstname = Property(str)
    lastname = Property(str)
    key = Key(firstname, lastname)

class Message(Entity):
    msg = Property(str)
    to = Reference(User)
    from = Reference(User)

In this case, the table created for Message will have 5 attributes: msg, to_firstname, to_lastname, from_firstname and from_lastname. It will also be constrained (in the DB) so so that always refers to a User in the database. However, you should not set these attributes directly, you should rather use to and from as if it were properties.:

>>> msg = Message(msg="Test", to=some_user.key, from=other_user.key)
>>> # Or after initializing
>>> msg.to = another_user.key

Remember to always refer to the key of an object, not the object itself.

JSON

To get a JSON representation, simply call obj.to_json(). Some options are available to change this output. You can override json_repr, which has to return some datatype that is convertible to JSON. By default, it returns a dictionary of all properties. This too you can control:

class User(Entity):
    name = Property(str)
    password = Property(str, json=False)  # We don't want to send passwords

Real-time

Sparrow has excellent support for real-time updates. Or you could call it live updates but ‘spalrow’ is not a word. Anyway, in its simplest form, just inherit from RTEntity instead of Entity. This will allow you to call add_listener (and remove_listener) on the object:

class User(RTEntity):
    name = Property(str)
    key = UID = KeyProperty()

Whenever update or delete is called, all listeners will get notified of this.

A RTEntity gets an extra method send_update which will trigger all listeners to be notified of an update without actually writing to the database.

Real-time references

The real fancy stuff is a RTReference though. This will make sure that whenever some object refers to another object, it will automatically call add_reference on all listeners of the referencing object. For example, with a few modifications we can add live messaging to our Message class of before:

class Message(Entity):
    msg = Property(str)
    to = RTReference(User)  #
    from = Reference(User)  # Assuming the sender knows it has sent a message, it doesn't
                            # need to know it has sent a message again.

A RTReference requires a RTEntity as referencing class.

Both RTReference and RTEntity add some overhead, so only use it when necessary.

The listeners need to following a certain interface, more info about that in RTEntity.

Database

More info about this can be found in the docs for the file sql.py. However, some Entity-specific things are not explained there. A list of the possibilities

  • Classmethods (where Cls is a subclass of Entity):
    • Cls.get(Cls.prop1 == val, Cls.prop2 <= Cls.prop3): returns a SELECT query.
    • Cls.raw("SELECT * FROM ..."): returns whatever query you want.
    • obj = await Cls.find_by_key(some_key, db): returns an instance with that key.
  • Methods (where obj is an instance of a subclass of Entity):
    • await obj.insert(db): inserts the object in the database. Will also fill in the KeyProperty if it exists.
    • await obj.update(db): update in the database.
    • await obj.delete(db): delete from the database.

Caching

It’s there, and it’s magic. All objects in memory with the same key will, in fact, be the exact same object (I’m talking about is equality, not == equality). It will regulate itself and you shouldn’t really care about it. However, I’d like to mention that if an object is in the cache, it will be crazy fast to call find_by_key, as it will not use the database at all.

Reference

exception sparrow.entity.CantSetProperty(obj, propnames)[source]

Bases: sparrow.util.Error

Raised when trying to set properties you may not edit. Mainly when editing/setting through edit_from_json or Cls(json_dict=...).

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class sparrow.entity.ConstrainedProperty(typ, constraint: function=None, sql_extra: str='', required: bool=True, json: bool=True)[source]

Bases: sparrow.entity.Property

sql_def()
type_sql_def()
class sparrow.entity.Entity(*args, **kwargs)[source]

Bases: object

Central class for an Entity. WARNING: Be careful with changing the key as it will fuck with caching. Basically, don’t do it.

check()[source]

Check object-wide constraint.

constraint = None
delete(db=None)[source]

Delete object from the database.

edit_from_json(dct: dict)[source]
classmethod find_by_key(key, db: sparrow.sql.Database=None) → 'cls'[source]

Works different from get, as it will immediatly return the object

classmethod get(*where_clauses: list) → sparrow.sql.Sql[source]
insert(db: sparrow.sql.Database=None, replace=False)[source]

Insert in database.

json_repr() → dict[source]

Returns a dictionary of all properties that don’t contain json = False. When overriding this method, you can return anything you want as long as it is convertible to JSON.

classmethod raw(text: str, dct={}) → sparrow.sql.RawSql[source]

Return a RawSql where the results will be interpreted as objects of cls.

to_json() → str[source]
update(db=None)[source]

Update object in the database.

class sparrow.entity.Enum(*args)[source]

Bases: sparrow.entity.Type

constraint
from_sql(obj)
to_sql(obj: 'self.python_type')
class sparrow.entity.Key(*props)[source]

Bases: sparrow.entity.Queryable

A reference to other properties that define the key of this object.

referencing_props()[source]
sql_constraint() → str[source]

Returns the SQL needed to define the PRIMARY KEY constraint.

class sparrow.entity.KeyProperty[source]

Bases: sparrow.entity.SingleKey, sparrow.entity.Property

A specifically created property to be used as a key. Type in postgres is SERIAL.

referencing_props()[source]
sql_constraint() → str[source]
sql_def()
type_sql_def()
class sparrow.entity.List(inner_type)[source]

Bases: sparrow.entity.Type

constraint = None
from_sql(obj)
to_sql(obj: 'self.python_type', f=<built-in function repr>)[source]
class sparrow.entity.Listener[source]

Bases: object

Interface for a listener to be used with RTEntity. Main use is documentation, not functionality.

Most implementations will want to define a set of objects they are listening to (listenees).

delete(obj: sparrow.entity.RTEntity)[source]

Handle deletions of the object.

new_reference(obj: sparrow.entity.RTEntity, ref_obj: sparrow.entity.Entity)[source]

Handle a new reference from ref_obj to obj. ref_obj does not have to be a RTEntity.

remove_reference(obj: sparrow.entity.RTEntity, ref_obj: sparrow.entity.Entity)[source]

Handle the removal of a reference from ref_obj to obj. ref_obj does not have to be a RTEntity.

update(obj: sparrow.entity.RTEntity)[source]

Handle updates to the object.

class sparrow.entity.MetaEntity[source]

Bases: type

Metaclass for Entity. This does a whole lot of stuff you should not care about as user of this library. If you do want to know how it works I suggest you read the code.

mro() → list

return a type’s method resolution order

exception sparrow.entity.ObjectConstraintFail(obj)[source]

Bases: sparrow.util.Error

Raised when an object failed to follow its constraint.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class sparrow.entity.Property(typ, constraint: function=None, sql_extra: str='', required: bool=True, json: bool=True)[source]

Bases: sparrow.entity.Queryable

sql_def()[source]
type_sql_def()[source]
exception sparrow.entity.PropertyConstraintFail(obj, prop)[source]

Bases: sparrow.util.Error

Raised when a property failed to follow its constraint.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class sparrow.entity.Queryable[source]

Bases: object

class sparrow.entity.RTEntity(*args, **kwargs)[source]

Bases: sparrow.entity.Entity

Subclass of Entity that sends live updates! Listeners should follow the interface of Listener.

add_listener(l: 'Listener')[source]

Add listeners to this object.

check()

Check object-wide constraint.

constraint = None
delete(db=None)[source]
edit_from_json(dct: dict)
find_by_key(key, db: sparrow.sql.Database=None) → 'cls'

Works different from get, as it will immediatly return the object

get(*where_clauses: list) → sparrow.sql.Sql
insert(db: sparrow.sql.Database=None, replace=False)

Insert in database.

json_repr() → dict

Returns a dictionary of all properties that don’t contain json = False. When overriding this method, you can return anything you want as long as it is convertible to JSON.

new_reference(ref, ref_obj)[source]
raw(text: str, dct={}) → sparrow.sql.RawSql

Return a RawSql where the results will be interpreted as objects of cls.

remove_all_listeners()[source]
remove_listener(l: 'Listener')[source]
remove_reference(ref, ref_obj)[source]
send_update(db=None)[source]
to_json() → str
update(db: sparrow.sql.Database=None)[source]
class sparrow.entity.RTReference(ref: 'MetaEntity')[source]

Bases: sparrow.entity.Reference

Reference that automatically notifies the referencing object.

classmethod single_upgrade()[source]
sql_constraint() → str

Will only generate the SQL constraint. The metaclass will take care of the properties.

class sparrow.entity.RTSingleReference(ref: 'MetaEntity')[source]

Bases: sparrow.entity.RTReference, sparrow.entity.SingleReference

Version of RTReference with only one referencing property. (Don’t directly use this, it will be automatic.)

single_upgrade()
sql_constraint() → str

Will only generate the SQL constraint. The metaclass will take care of the properties.

class sparrow.entity.Reference(ref: 'MetaEntity', json=True, cascade=True)[source]

Bases: sparrow.entity.Queryable

A reference to another Entity type.

classmethod single_upgrade()[source]
sql_constraint() → str[source]

Will only generate the SQL constraint. The metaclass will take care of the properties.

class sparrow.entity.SingleKey(*props)[source]

Bases: sparrow.entity.Key

Version of Key with only one property. (Don’t directly use this, it will be automatic.)

referencing_props()
sql_constraint() → str

Returns the SQL needed to define the PRIMARY KEY constraint.

class sparrow.entity.SingleReference(ref: 'MetaEntity', json=True, cascade=True)[source]

Bases: sparrow.entity.Reference

Version of Reference with only one referencing property. (Don’t directly use this, it will be automatic.)

single_upgrade()
sql_constraint() → str

Will only generate the SQL constraint. The metaclass will take care of the properties.

class sparrow.entity.StaticType(python_type, sql_type=None)[source]

Bases: sparrow.entity.Type

constraint = None
from_sql(obj)
to_sql(obj: 'self.python_type')
class sparrow.entity.Type(python_type, sql_type=None)[source]

Bases: object

constraint = None
from_sql(obj)[source]
to_sql(obj: 'self.python_type')[source]
sparrow.entity.classitems(dct, bases)[source]

Helper function to allow for inheritance

sparrow.entity.create_order(op)[source]
sparrow.entity.create_where_comparison(op)[source]