Skip to main content

A simple ORM for PostgreSQL.

Project description

Stellata is a simple PostgreSQL ORM for Python 3.

Connecting

To connect to a database on the default host/port with a threaded connection pool of size 10:

import stellata.database

db = stellata.database.initialize(
    name='database',
    user='user',
    password='password',
    host='localhost',
    port=5432,
    pool_size=10
)

Defining Models

A user model might look something like this:

import stellata.fields
import stellata.model

class User(stellata.model.Model):
    __table__ = 'users'

    id = stellata.fields.UUID(null=False)
    active = stellata.fields.Integer(default=1)
    name = stellata.fields.Text(null=False)
    hash = stellata.fields.Varchar(length=255)
    email = stellata.fields.Text()
    dt = stellata.fields.Timestamp(null=False)

The __table__ property is the name of the corresponding table in the PostgreSQL database. Each property corresponds to a column, and the classes in stellata.fields are used to define the column types.

Field Types

  • BigInteger

  • Boolean

  • Integer

  • Numeric

  • Text

  • Timestamp

  • UUID

  • Varchar

Relations

Like any good ORM, Stellata supports relations among models. Here are two related models, A and B:

import stellata.model
import stellata.fields
import stellata.relations

class A(stellata.model.Model):
    __table__ = 'a'

    id = stellata.fields.UUID()
    foo = stellata.fields.Text()
    bar = stellata.fields.Integer(default=0, null=False)

    b = stellata.relations.HasMany(lambda: B.a_id)

class B(stellata.model.Model):
    __table__ = 'b'

    id = stellata.fields.UUID()
    a_id = stellata.fields.UUID()
    bar = stellata.fields.Integer(null=False)

    a = stellata.relations.BelongsTo(lambda: B.a_id, lambda: A)

Indexes

Indexes make your queries go fast. Let’s add a couple indexes to our table:

import stellata.model
import stellata.fields
import stellata.index
import stellata.relations

class A(stellata.model.Model):
    __table__ = 'a'

    id = stellata.fields.UUID()
    foo = stellata.fields.Text()
    bar = stellata.fields.Integer(default=0, null=False)

    b = stellata.relations.HasMany(lambda: B.A_id, lambda: A)

    primary_key = stellata.index.PrimaryKey(lambda: A.id)
    foo_index = stellata.index.Index(lambda: A.foo, unique=True)

Serialization

Once you have some data, it won’t be long until you want to convert it to JSON. To do so, use:

stellata.model.serialize(model)

This will recursively serialize objects/relations, and you can pass it an object, dictionary, list, etc.

Meta

In some cases, it’s handy to be able to iterate over all of the models you’ve defined. For example, you might want to truncate tables for a unit test. In that case, you can do this:

for model in stellata.model.registered():
    # do something with model

Migration

Once you’ve defined your models, you can sync them with your database by performing a migration.

stellata.schema.migrate(db, execute=True)

Here, db is the handle returned by the stellata.database.initialize call. If you’d like to do a dry run, without actually executing any queries, do:

stellata.schema.migrate(db)

In both cases, this function will return a list of queries needed for the migration.

Resetting

In some development scripts, you might want to clean your database. If you so desire, you can do this:

stellata.schema.drop_tables_and_lose_all_data(db, execute=True)

As its name suggests, this function is very destructive, so don’t do this on a production database.

CRUD Operations

Finally, let’s walk through how to use Stellata to query your database.

Create

Let’s create a new instance of A.

a = A.create(A(foo='bar', bar=5))
a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'
a.foo == 'bar'
a.bar == 4

Or, if we want to create in bulk:

result = A.create([
    A(foo='bar', bar=6),
    A(foo='baz', bar=7)
])

len(result) == 2

If you created a unique index on some fields, you can take advantage of the PostgreSQL ON CONFLICT feature:

A.create(A(foo='baz', bar=9), unique=(A.foo,))

Now, if there’s already a row with foo having a value of baz, then the bar column will be updated to have a value of 9, rather than creating a new row.

Read

To read from the database, we’ll want to use the where method. Let’s get the instance of A we created before:

a = A.where(A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca').get()
a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'

Jeez Rick, what’s that syntax? We’re using operator overloading, Morty. What else can we do?

A.where(A.bar < 5).get()
A.where(A.bar > 1).get()
A.where(A.id << ['2a12f545-c587-4b99-8fd2-57e79f7c8bca', '31be0c81-f5ee-49b9-a624-356402427f76']).get()

That last one is a where in query, in case that wasn’t—burp—obvious. We can also use AND and OR in our queries like so:

A.where((A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca') | (A.bar < 5)).get()
A.where((A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca') & (A.bar > 1)).get()

Other bells and whistles:

A.where(A.bar < 5).order(A.bar, 'asc').limit(5).get()

A common read operation is to find all rows where a column matches some value, so we can use a shorthand:

A.find('2a12f545-c587-4b99-8fd2-57e79f7c8bca')

By default, the id field will be used, but you can also specify your own field:

A.find(5, A.bar)

If given a list, find will return a dictionary keyed on the value of the field you specify:

a = A.find(['2a12f545-c587-4b99-8fd2-57e79f7c8bca', '31be0c81-f5ee-49b9-a624-356402427f76'])
a['2a12f545-c587-4b99-8fd2-57e79f7c8bca'].id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'
a['31be0c81-f5ee-49b9-a624-356402427f76'].id == '31be0c81-f5ee-49b9-a624-356402427f76'

Joins

We can use those relations we set up earlier with joins. Let’s say we create the following:

a = A.create(A(foo='bar', bar=5))
a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'
b = B.create([
    B(a_id='2a12f545-c587-4b99-8fd2-57e79f7c8bca', qux=3)
    B(a_id='2a12f545-c587-4b99-8fd2-57e79f7c8bca', qux=5)
])

Now, we can do this:

a = A.join(A.b).where(A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca').get()
a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'
len(a.b) == 2
a.b[0].qux == 3
a.b[1].qux == 5

Or, the other way:

b = B.join(B.a).where(B.qux << [3, 5]).get()
len(b) == 2
b[0].qux == 3
b[0].a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'
b[1].qux == 5
b[1].a.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca'

By default, joins will be executed via multiple SELECT queries. If you’d prefer to do a JOIN instead, just do this:

a = A.join_with('join').join(A.b).where(A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca').get()

The result is the same as before, but the underlying query was different. Which method you use is entirely up to you, and may vary with different queries.

Update

As you might expect, update queries combine the syntax for creating and reading:

A.where(A.id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca').update(A(bar=7))

Delete

This one is easy now.

A.where(id == '2a12f545-c587-4b99-8fd2-57e79f7c8bca').delete()

Or, if you prefer a single TRUNCATE operation:

A.truncate()

Transactions

Stellata also has support for PostgreSQL transactions:

A.begin()
A.truncate()
A.create([A(bar=1), A(bar=2)])
A.commit()

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

stellata-0.0.2.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

stellata-0.0.2-py3-none-any.whl (26.3 kB view details)

Uploaded Python 3

File details

Details for the file stellata-0.0.2.tar.gz.

File metadata

  • Download URL: stellata-0.0.2.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for stellata-0.0.2.tar.gz
Algorithm Hash digest
SHA256 dfc13bd58fbbb9d1fd5107e0a49a8309e2197422dad8a0b4fba6065f204ffcd2
MD5 e88537fb4acf02f53585bad56eba4784
BLAKE2b-256 d4d7a981a35b4cb82cc10c4f9960e1ba1091cb05edc23964a2900a5dead0b229

See more details on using hashes here.

File details

Details for the file stellata-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for stellata-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 63d8fdcd7f941c04b8232e8bafdd760f8d8dd67863c6b13c0ab1b8e30affc4ef
MD5 5211f6e05f6d0b97cb84c159a4bb5b53
BLAKE2b-256 6640f493407ff86297a914cabd817c6085bb18e2789633b6aca4d1cee778e394

See more details on using hashes here.

Supported by

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