Skip to main content

Simple Python ORM for CouchDB, and Sync Gateway

Project description

#fam

A simple Python ORM for CouchDB and Couchbase Sync Gateway.

Fam is a work in progress growing as the needs of my current project dictate. This means that while very useful it is not a feature complete ORM. It is probably most useful if, like me, you want to use Couch family of noSQL DBs for their handling of distributed data sets but have highly relational data.

Fam adds a type and namespace to each document:

- **type** - A lower case string derived from the class name
- **namespace** - An opaque string to help avoid class name clashes and allow versioning of classes

And uses them to provide:

- A class to bind methods to documents
- Automatic generation of design documents for relationships between classes
- Lookup of related documents
- Validation of documents
- Document life-cycle callbacks for creation, updates and deletion
- Optional cascading deletion through relationships

You can define a fam class like this:

```python

NAMESPACE = "mynamespace"

class Dog(GenericObject):
use_rev = True
additional_properties = True
fields = {
"name": StringField(),
"owner_id": ReferenceTo(NAMESPACE, "person", cascade_delete=True)
}

def talk(self):
return "woof"

```

and then use it to create a document like this:

```python
dog = Dog(name="fly")
db.put(dog)

```

##Databases

fam has wrappers for connecting to different databases:

- CouchDB
- Couchbase
- Couchbase Sync Gateway

These wrapper classes are stateless and thread safe, at least the CouchDB and Sync Gateway ones certainly are as they use the requests library to manage connection pooling and keep alive. The Couchbase one probably is too but it uses Couchbase's own libraries to connect to the db.

To use fam you have to first create a class mapper passing in your classes eg:

```python

from fam.mapper import ClassMapper

mapper = ClassMapper([Dog, Cat, Person])

```
and then create a db wrapper using the mapper, the address of the database and the name of the database/bucket

```python

db = CouchDBWrapper(mapper, database_url, database_name)

```

This means that documents accessed though the db will be associated with their relative classes.

You can then write or update the relational design documents in the database from the classes in the mapper like this:

```python

db.update_designs()

```

An instance of a database wrapper provides these methods for adding and removing fam objects from databases

- **db.put(an_object)** - Puts this object into the database
- **db.get(key)** - Gets the object with this key from the database
- **db.delete(an_object)** - Removes this object from the database
- **db.delete_key(key)** - Removes the object with this key from the database


##Classes

Fam classes are defined as inheriting from fam.blud.GenericObject like this:


```python

class Cat(GenericObject):
use_rev = False
additional_properties = False
fields = {
"name": StringField(),
"legs": NumberField(),
"owner_id": ReferenceTo(NAMESPACE, "person")
}

```

With three class attributes

- **use_rev** - A boolean, True by default, which if true uses the default rev/cas collision protection of Couch DBs but if false always forces a document update as if this mechanism didn't exist
- **additional_properties** - A boolean, false by default, which if true lets you add arbitrary additional top level attributes to an object and if flase will throw an exception when you try.
- **fields** - A dict of named fields that map to the top level attributes of the underlying json documents. See below for use.

GenericObject also provides six callbacks that occur as documents are saved and deleted

- **pre_save_new_cb(self)**
- **post_save_new_cb(self)**
- **pre_save_update_cb(self, old_properties)**
- **post_save_update_cb(self)**
- **pre_delete_cb(self)**
- **post_delete_cb(self)**

##Fields

There are several types of field defined in fam.blud that map to json types

- **BoolField**
- **NumberField**
- **StringField**
- **ListField**
- **DictField**

When defining a fam class you instantiate each of fields for the class and give it a name eg `"address": StringField()`

###ObjectField Fields

An ObjectField is an instance of another python object. The class of the object must be provided when defining the field. The class has to provide an instance method `to_json` and a class method `from_json` so fam can serialise and deserialise it successfully.

This is an example of a representation of a duration of time:

```python

"duration": ObjectField(cls=TimeDuration)

...

class TimeDuration(object):

def __init__(self, nom=0, denom=0, count=0, per_frame=1):
self.nom = nom
self.denom = denom
self.count = count
self.per_frame = per_frame

def to_json(self):
return {
"nom": self.nom,
"denom": self.denom,
"count": self.count,
"per_frame": self.per_frame
}

@classmethod
def from_json(cls, as_json):
return cls(**as_json)

...

```

###ReferenceTo Fields

ReferenceTo is really just a string field that is the key of another document. ReferenceTo fields are defined with the namespace and name of the class of the referenced document.

```python

"owner_id": ReferenceTo(NAMESPACE, "person")

```

The name should always end with `_id` , this indicates that it is a reference but it also support fam's lookup of related objects. This allows you to directly access related documents for example dog.owner_id will return the key of the owner document but dog.owner will return an instance of the Owner class for that document.

###ReferenceFrom Fields

ReferenceFrom fields are quite different and they have no representation within the json document. Instead they use the automatically created design documents to find a collection of documents with the associated ReferenceTo field. So ReferenceFrom fields only work with as existing ReferenceTo Field. They are defined with the namespace and the class that the reference is from and the name of the ReferenceTo field in that class.

```python

"dogs": ReferenceFrom(NAMESPACE, "dog", "owner_id")

```
This gives way to do one-to-one and one-to-many relationships. In practice I find I tend to model immutable one-to-many relationships internally as lists of keys within documents and mutable ones with fam view lookups. I also create mutable one-to-one and many-to-many relationships with small join documents with compound keys. I also have write extra views by hand for more complex indexing.

##Optional Field Options

There are four optional arguments when creating a field:

- **required** - A boolean, false by default that asserts that this field must be present.
- **immutable** - A boolean, false by default asserts that you cannot change the value of ths field once it has been set.
- **default** - A default value for this field that will be returned on read if this field is absent from the underlying json. None by default.
- **cascade_delete** - Only applies to ReferenceTo and ReferenceFrom fields. A boolean, false by default, which if true will delete the object the reference points to when this object is deleted.


##Validation

Fam now uses JSON Schema http://json-schema.org to validate documents. Fam's mapper generates schemas dynamically from the class definitions and uses them to validate documents.

You can get the mapper to write out its internal schemata by calling ```mapper.validator.write_out_schemata(directory)```


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fam-1.0.3.tar.gz (23.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fam-1.0.3-py2-none-any.whl (32.3 kB view details)

Uploaded Python 2

File details

Details for the file fam-1.0.3.tar.gz.

File metadata

  • Download URL: fam-1.0.3.tar.gz
  • Upload date:
  • Size: 23.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for fam-1.0.3.tar.gz
Algorithm Hash digest
SHA256 cd8bbab269e66129be7a6e85b5a654628d09cfba3f3d22267714b42c2e1e75f8
MD5 c871ca1576de1999c51544d7178f45be
BLAKE2b-256 152c51fa658ff0503c5ec8f33c337b2551f396aa2af9bd510d7133e546f51d06

See more details on using hashes here.

File details

Details for the file fam-1.0.3-py2-none-any.whl.

File metadata

  • Download URL: fam-1.0.3-py2-none-any.whl
  • Upload date:
  • Size: 32.3 kB
  • Tags: Python 2
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for fam-1.0.3-py2-none-any.whl
Algorithm Hash digest
SHA256 ce5942927dab911588ea79810102a2e218eabb01f589a230c9c4ad243022eb7e
MD5 5d1f2252f7e30e8b1b59ce027c7ed61b
BLAKE2b-256 f060f4795c21880faea9063d34562593a9256b5eba417b28bb34ece3777020fa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page