A Django-style ORM for PostgreSQL and SQLite with migrations, relationships, and a friendly API.
Project description
🐾 PookieDB ORM
A Django-style Python ORM for PostgreSQL and SQLite — with auto migrations, relationships, a chainable QuerySet API, and a friendly CLI.
Author: Grace Peter Mutiibwa
Version: 0.1.0
License: MIT
Installation
pip install pookiedb
# PostgreSQL support is included via psycopg2-binary
Quick Start
1. Connect to your database
import pookiedb
# SQLite
pookiedb.connect("sqlite:///mydb.sqlite3")
# PostgreSQL
pookiedb.connect("postgresql://postgres:password@localhost:5432/mydb")
# Or using kwargs
pookiedb.connect(engine="postgresql", name="mydb", host="localhost", user="postgres", password="secret")
2. Define models
import pookiedb
class Author(pookiedb.Model):
name = pookiedb.CharField(max_length=100)
email = pookiedb.EmailField(unique=True)
bio = pookiedb.TextField(null=True, blank=True)
joined = pookiedb.DateTimeField(auto_now_add=True)
class Meta:
db_table = "authors"
ordering = ["-joined"]
class Post(pookiedb.Model):
title = pookiedb.CharField(max_length=200)
slug = pookiedb.SlugField(unique=True)
body = pookiedb.TextField()
author = pookiedb.ForeignKey(Author, on_delete=pookiedb.CASCADE)
published = pookiedb.BooleanField(default=False)
metadata = pookiedb.JSONField(null=True)
tags = pookiedb.ManyToManyField("Tag", related_name="posts")
created_at = pookiedb.DateTimeField(auto_now_add=True)
class Meta:
db_table = "posts"
class Tag(pookiedb.Model):
name = pookiedb.CharField(max_length=50, unique=True)
class Meta:
db_table = "tags"
3. Create tables
Author.create_table()
Tag.create_table()
Post.create_table()
QuerySet API
PookieDB's QuerySet is lazy and chainable — queries only hit the database when you iterate or evaluate.
# Create
alice = Author.objects.create(name="Alice", email="alice@example.com")
# Get
author = Author.objects.get(email="alice@example.com")
# Filter (chained)
posts = (
Post.objects
.filter(published=True)
.filter(author=alice)
.order_by("-created_at")
.limit(10)
)
# Exclude
drafts = Post.objects.exclude(published=True)
# Q objects for OR / AND / NOT logic
from pookiedb.queryset.queryset import Q
results = Post.objects.filter(
Q(title__icontains="python") | Q(tags__name="python")
)
# get_or_create / update_or_create
tag, created = Tag.objects.get_or_create(name="django")
# Slicing
first_five = Post.objects.all()[:5]
# values / values_list
names = Author.objects.values("name", "email")
ids = Post.objects.values_list("id", flat=True)
# Aggregates
from pookiedb.queryset.queryset import Sum, Avg, Count
stats = Post.objects.aggregate(total=Count("id"), avg_len=Avg("id"))
# Bulk create
posts = Post.objects.bulk_create([
Post(title="Post 1", slug="post-1", body="...", author=alice),
Post(title="Post 2", slug="post-2", body="...", author=alice),
])
# Raw SQL
authors = Author.objects.raw("SELECT * FROM authors WHERE name ILIKE %s", ["%ali%"])
# Delete
Post.objects.filter(published=False).delete()
# Bulk update
Post.objects.filter(author=alice).bulk_update(published=True)
Relationships
ForeignKey
post = Post.objects.get(id=1)
print(post.author) # lazy-loads the Author
print(post.author.name)
ManyToMany
post = Post.objects.get(id=1)
tag = Tag.objects.get(name="python")
post.tags.add(tag)
post.tags.all() # QuerySet of related Tags
post.tags.remove(tag)
post.tags.set([tag1, tag2])
post.tags.clear()
post.tags.count()
OneToOne
class UserProfile(pookiedb.Model):
user = pookiedb.OneToOneField("User", on_delete=pookiedb.CASCADE)
avatar_url = pookiedb.URLField(null=True)
Field Reference
| Field | Description |
|---|---|
CharField(max_length=N) |
VARCHAR with length limit |
TextField() |
Unlimited text |
IntegerField() |
INTEGER |
BigIntegerField() |
BIGINT |
FloatField() |
REAL / DOUBLE PRECISION |
DecimalField(max_digits, decimal_places) |
NUMERIC |
BooleanField() |
BOOLEAN / INTEGER |
DateField(auto_now, auto_now_add) |
DATE |
DateTimeField(auto_now, auto_now_add) |
TIMESTAMP |
TimeField() |
TIME |
EmailField() |
VARCHAR with email validation |
URLField() |
VARCHAR with URL validation |
SlugField() |
VARCHAR with slug validation |
UUIDField(auto=True) |
UUID / TEXT |
AutoField() |
Auto-increment primary key |
BigAutoField() |
Big auto-increment primary key |
JSONField() |
JSONB (Postgres) / TEXT (SQLite) |
ArrayField(base_field) |
ARRAY (Postgres) / JSON TEXT (SQLite) |
ForeignKey(to, on_delete) |
Many-to-one FK |
OneToOneField(to, on_delete) |
Unique FK |
ManyToManyField(to) |
Join table relationship |
Common field kwargs
CharField(
max_length=100,
null=False, # allow NULL in DB
blank=False, # allow empty value in forms/validation
default=None, # default value (or callable)
unique=False, # UNIQUE constraint
db_index=False, # CREATE INDEX
db_column=None, # override column name
choices=[("draft", "Draft"), ("pub", "Published")],
verbose_name="My Field",
)
Lookup Types
# Exact (default)
filter(name="Alice")
filter(name__exact="Alice")
# Case-insensitive
filter(name__iexact="alice")
filter(name__icontains="ali")
# Wildcards
filter(title__startswith="Hello")
filter(title__endswith="world")
filter(body__contains="pookie")
# Comparison
filter(age__gt=18)
filter(age__gte=18)
filter(age__lt=65)
filter(age__lte=65)
# IN / NULL / RANGE
filter(id__in=[1, 2, 3])
filter(email__isnull=True)
filter(age__range=(18, 65))
filter(name__ne="Bob")
Migrations
Generate a migration
pookiedb makemigrations --settings settings.py
pookiedb makemigrations --name add_slug_field --settings settings.py
Apply migrations
pookiedb migrate --settings settings.py
View migration status
pookiedb showmigrations --settings settings.py
Roll back
pookiedb rollback --steps 1 --settings settings.py
Your settings.py just needs to call pookiedb.connect() and import your models:
# settings.py
import pookiedb
from myapp.models import Author, Post, Tag
pookiedb.connect("sqlite:///mydb.sqlite3")
CLI Commands
pookiedb --help
Commands:
makemigrations Detect model changes and generate a migration file
migrate Apply pending migrations to the database
rollback Roll back the last N migrations
showmigrations List all migrations and their status
shell Interactive Python REPL with pookiedb pre-imported
dbshell Open raw psql / sqlite3 shell
inspectdb Introspect an existing DB and generate model code
Shell
pookiedb shell --settings settings.py
# All your models are available by name
>>> Author.objects.all()
>>> Post.objects.filter(published=True).count()
DBShell
pookiedb dbshell --settings settings.py # opens psql or sqlite3
InspectDB
# Introspect all tables
pookiedb inspectdb --settings settings.py
# Single table
pookiedb inspectdb --table users --settings settings.py
# Write to file
pookiedb inspectdb --output models.py --settings settings.py
Transactions
from pookiedb.db.connection import transaction
with transaction() as conn:
author = Author(name="Bob", email="bob@example.com")
author.save()
post = Post(title="Hello", slug="hello", body="...", author=author)
post.save()
# Auto commits on exit, rolls back on exception
Pagination
from pookiedb.utils import Paginator
paginator = Paginator(Post.objects.filter(published=True), per_page=20)
page = paginator.page(1)
print(page.object_list) # list of Post instances
print(page.has_next()) # True / False
print(page.num_pages) # total pages
Multiple Databases
pookiedb.connect("postgresql://...", alias="primary")
pookiedb.connect("sqlite:///analytics.sqlite3", alias="analytics")
class EventLog(pookiedb.Model):
class Meta:
db_alias = "analytics"
Model Meta options
class Meta:
db_table = "custom_table_name" # override table name
ordering = ["-created_at", "name"] # default ordering
unique_together = [["first_name", "last_name"]]
db_alias = "default" # which DB connection to use
abstract = True # don't create a table
verbose_name = "blog post"
verbose_name_plural = "blog posts"
License
MIT © Grace Peter Mutiibwa
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pookiedb-0.1.1.tar.gz.
File metadata
- Download URL: pookiedb-0.1.1.tar.gz
- Upload date:
- Size: 40.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3d11be8f91c74e0866061f7c2f46b6795fac7642c2265e9a5952f0604ac02e4
|
|
| MD5 |
56d575c814b96e8613c05b12b37426c9
|
|
| BLAKE2b-256 |
7c7d7590f43a62501c8fc8d2e67c1af84be1701543168ea4826d5a1dfba419ec
|
File details
Details for the file pookiedb-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pookiedb-0.1.1-py3-none-any.whl
- Upload date:
- Size: 39.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e22d4a2c26d2d484a9465deb4ebf6af10f86663a0102fdc839a0e76e9d4b0d9e
|
|
| MD5 |
555be30f96293aeb8afc811217c27f26
|
|
| BLAKE2b-256 |
86b1d61ff63b703a85072d3937e8cdee9c24c14d2670bce27c123087bf9aed6e
|