Django's Powerful ORM β Now for Standalone Python Scripts!
Use Django's mature, battle-tested ORM in any Python script without the overhead of a full Django project.
________ ________ ___ ________ ________ _____ ______
|\ ____\|\ __ \|\ \ |\ __ \|\ __ \|\ _ \ _ \
\ \ \___|\ \ \|\ \ \ \ \ \ \|\ \ \ \|\ \ \ \\\__\ \ \
\ \_____ \ \ \\\ \ \ \ \ \ \\\ \ \ _ _\ \ \\|__| \ \
\|____|\ \ \ \\\ \ \ \____\ \ \\\ \ \ \\ \\ \ \ \ \ \
____\_\ \ \_____ \ \_______\ \_______\ \__\\ _\\ \__\ \ \__\
|\_________\|___| \__\|_______|\|_______|\|__|\|__|\|__| \|__|
\|_________| \|__|
Django's ORM is amazing, but it comes with a catch β you typically need a full Django project structure to use it. That means:
- β Creating
manage.py,settings.py, and app folders - β Running
django-admin startproject - β Dealing with
INSTALLED_APPSand migrations infrastructure
SQLORM solves this problem! It wraps Django's ORM with a minimal configuration layer, giving you:
- β Zero Django project structure required β just import and go
- β All Django ORM features β querysets, fields, Q objects, F expressions, aggregations
- β All database backends β SQLite, PostgreSQL, MySQL, Oracle
- β Production-ready β Django is battle-tested at scale (Instagram, Spotify, Mozilla)
- β Familiar API β if you know Django, you already know SQLORM
Install from PyPI:
pip install django-sqlormInstall directly from GitHub:
# Basic installation
pip install git+https://github.com/surajsinghbisht054/sqlorm.git
# Or clone and install locally
git clone https://github.com/surajsinghbisht054/sqlorm.git
cd sqlorm
pip install -e .# Install with PostgreSQL support
pip install django-sqlorm[postgresql]
# Install with MySQL support
pip install django-sqlorm[mysql]
# For development (with test dependencies)
git clone https://github.com/surajsinghbisht054/sqlorm.git
cd sqlorm
pip install -e ".[dev]"- Python 3.8+
- Django 3.2+
Here's a complete working example in just a few lines:
from sqlorm import configure, Model, fields, create_tables
# 1. Configure the database (that's it - no settings.py needed!)
configure({
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'todo_app.sqlite3',
})
# 2. Define your models (exactly like Django!)
class Task(Model):
title = fields.CharField(max_length=200)
is_completed = fields.BooleanField(default=False)
created_at = fields.DateTimeField(auto_now_add=True)
# 3. Create the table (for quick start without migrations)
create_tables()
# 4. Use Django ORM as usual! π
task = Task.objects.create(title="Buy groceries")
pending_tasks = Task.objects.filter(is_completed=False)
print(f"Pending tasks: {pending_tasks.count()}")That's it! No manage.py, no startproject, no INSTALLED_APPS. Just Python.
For production applications, you should use migrations instead of create_tables().
- Create a script with your models (e.g.,
models.py). - Configure with
migrations_dir:configure(..., migrations_dir='./migrations')
- Run commands:
# Create migrations
sqlorm makemigrations --models models.py
# Apply migrations
sqlorm migrate --models models.py- Configuration
- Defining Models
- Field Types
- CRUD Operations
- Querying
- Advanced Features
- Raw SQL
- Transactions
- Multiple Databases
- Schema Migrations
from sqlorm import configure
configure({
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'database.sqlite3', # Or ':memory:' for in-memory DB
})configure({
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'myuser',
'PASSWORD': 'mypassword',
'HOST': 'localhost',
'PORT': '5432',
})configure({
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydatabase',
'USER': 'myuser',
'PASSWORD': 'mypassword',
'HOST': 'localhost',
'PORT': '3306',
})import os
from sqlorm.config import configure_from_env
# Using DATABASE_URL (Heroku-style)
os.environ['DATABASE_URL'] = 'postgres://user:pass@localhost:5432/mydb'
configure_from_env()
# Or using individual variables
os.environ['SQLORM_DB_ENGINE'] = 'django.db.backends.postgresql'
os.environ['SQLORM_DB_NAME'] = 'mydb'
os.environ['SQLORM_DB_USER'] = 'user'
os.environ['SQLORM_DB_PASSWORD'] = 'pass'
os.environ['SQLORM_DB_HOST'] = 'localhost'
configure_from_env()from sqlorm import configure_from_file
# JSON file
configure_from_file('config.json')
# YAML file (requires pyyaml)
configure_from_file('config.yaml')config.json:
{
"database": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "mydb.sqlite3"
},
"debug": true
}Models are defined exactly like Django models:
from sqlorm import Model, fields
class Article(Model):
# Text fields
title = fields.CharField(max_length=200)
slug = fields.SlugField(unique=True)
content = fields.TextField()
# Numeric fields
view_count = fields.PositiveIntegerField(default=0)
rating = fields.DecimalField(max_digits=3, decimal_places=2, null=True)
# Boolean fields
is_published = fields.BooleanField(default=False)
# Date/time fields
published_at = fields.DateTimeField(null=True, blank=True)
created_at = fields.DateTimeField(auto_now_add=True)
updated_at = fields.DateTimeField(auto_now=True)
# Choices
STATUS_CHOICES = [
('draft', 'Draft'),
('review', 'Under Review'),
('published', 'Published'),
]
status = fields.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
class Meta:
ordering = ['-created_at']
verbose_name = 'Article'
verbose_name_plural = 'Articles'All Django field types are available:
| Field Type | Description | Example |
|---|---|---|
CharField |
Fixed-length string | fields.CharField(max_length=100) |
TextField |
Unlimited text | fields.TextField() |
IntegerField |
Integer | fields.IntegerField(default=0) |
FloatField |
Floating point | fields.FloatField() |
DecimalField |
Fixed precision | fields.DecimalField(max_digits=10, decimal_places=2) |
BooleanField |
True/False | fields.BooleanField(default=False) |
DateField |
Date | fields.DateField() |
DateTimeField |
Date and time | fields.DateTimeField(auto_now_add=True) |
EmailField |
Email with validation | fields.EmailField(unique=True) |
URLField |
URL with validation | fields.URLField() |
SlugField |
URL-friendly string | fields.SlugField(unique=True) |
UUIDField |
UUID | fields.UUIDField(default=uuid.uuid4) |
JSONField |
JSON data | fields.JSONField(default=dict) |
Common field options:
null=Trueβ Allow NULL in databaseblank=Trueβ Allow empty in formsdefault=valueβ Default valueunique=Trueβ Must be uniquedb_index=Trueβ Create database indexchoices=[...]β Limit to specific values
# Method 1: create()
user = User.objects.create(
name="John Doe",
email="john@example.com"
)
# Method 2: Instantiate and save
user = User(name="Jane Doe", email="jane@example.com")
user.save()
# Method 3: get_or_create
user, created = User.objects.get_or_create(
email="bob@example.com",
defaults={'name': 'Bob Smith'}
)# Get all records
users = User.objects.all()
# Get single record
user = User.objects.get(id=1)
user = User.objects.get(email="john@example.com")
# Get first/last
first = User.objects.first()
last = User.objects.last()
# Count
count = User.objects.count()# Single object
user = User.objects.get(id=1)
user.name = "John Smith"
user.save()
# Bulk update
User.objects.filter(is_active=False).update(is_active=True)# Single object
user = User.objects.get(id=1)
user.delete()
# Bulk delete
User.objects.filter(is_active=False).delete()SQLORM supports the full Django QuerySet API:
# Filtering
User.objects.filter(is_active=True)
User.objects.filter(age__gte=18)
User.objects.filter(name__startswith='J')
User.objects.filter(email__contains='@gmail')
# Excluding
User.objects.exclude(is_active=False)
# Chaining
User.objects.filter(is_active=True).exclude(age__lt=18).order_by('name')
# Ordering
User.objects.order_by('name') # Ascending
User.objects.order_by('-created_at') # Descending
# Limiting
User.objects.all()[:10] # First 10
User.objects.all()[10:20] # 10-20
# Values
User.objects.values('name', 'email')
User.objects.values_list('name', flat=True)
# Distinct
User.objects.values('city').distinct()# Exact match
User.objects.filter(name='John')
User.objects.filter(name__exact='John')
# Case-insensitive
User.objects.filter(name__iexact='john')
# Contains
User.objects.filter(name__contains='oh')
User.objects.filter(name__icontains='OH')
# Starts/ends with
User.objects.filter(name__startswith='J')
User.objects.filter(name__endswith='n')
# Range
User.objects.filter(age__range=(18, 65))
# In list
User.objects.filter(status__in=['active', 'pending'])
# Is null
User.objects.filter(deleted_at__isnull=True)
# Greater/less than
User.objects.filter(age__gt=18)
User.objects.filter(age__gte=18)
User.objects.filter(age__lt=65)
User.objects.filter(age__lte=65)from sqlorm import Q
# OR queries
User.objects.filter(Q(age__lt=18) | Q(age__gt=65))
# AND queries (explicit)
User.objects.filter(Q(is_active=True) & Q(is_verified=True))
# NOT queries
User.objects.filter(~Q(status='banned'))
# Complex combinations
User.objects.filter(
(Q(age__gte=18) & Q(age__lte=65)) | Q(is_verified=True)
).exclude(Q(status='banned'))from sqlorm import F
# Reference other fields
Product.objects.filter(stock__lt=F('reorder_level'))
# Arithmetic
Product.objects.update(price=F('price') * 1.1) # 10% increase
# Annotations
Product.objects.annotate(profit=F('price') - F('cost'))from sqlorm import Count, Sum, Avg, Max, Min
# Single aggregation
Order.objects.aggregate(total=Sum('amount'))
# {'total': Decimal('15420.00')}
# Multiple aggregations
Order.objects.aggregate(
total=Sum('amount'),
average=Avg('amount'),
count=Count('id'),
max_order=Max('amount'),
min_order=Min('amount'),
)
# Conditional aggregation
Order.objects.aggregate(
paid_total=Sum('amount', filter=Q(is_paid=True)),
unpaid_total=Sum('amount', filter=Q(is_paid=False)),
)from sqlorm import Count, Sum
# Add computed fields
users = User.objects.annotate(
order_count=Count('orders'),
total_spent=Sum('orders__amount'),
)
for user in users:
print(f"{user.name}: {user.order_count} orders, ${user.total_spent}")For complex queries that are hard to express with the ORM:
from sqlorm import execute_raw_sql
from sqlorm.connection import execute_raw_sql_dict
# Execute and get tuples
results = execute_raw_sql(
"SELECT name, email FROM users WHERE age > %s",
[18]
)
# Execute and get dictionaries
results = execute_raw_sql_dict(
"SELECT name, email FROM users WHERE age > %s",
[18]
)
for row in results:
print(f"{row['name']}: {row['email']}")
# Insert/Update (no fetch)
execute_raw_sql(
"UPDATE users SET is_active = %s WHERE last_login < %s",
[False, '2024-01-01'],
fetch=False
)from sqlorm import transaction
# Basic transaction
with transaction():
user = User.objects.create(name="John", email="john@example.com")
Profile.objects.create(user_id=user.id, bio="Hello!")
# Both are committed together, or both are rolled back
# Handling errors
try:
with transaction():
User.objects.create(name="Jane", email="jane@example.com")
raise ValueError("Something went wrong!")
except ValueError:
pass # Transaction was automatically rolled backfrom sqlorm import configure
# Configure with alias
configure({
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'primary.sqlite3',
}, alias='default')
configure({
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'analytics',
'HOST': 'analytics-db.example.com',
'USER': 'readonly',
'PASSWORD': 'secret',
}, alias='analytics')
# Use specific database
from sqlorm import get_connection
analytics_conn = get_connection('analytics')SQLORM supports Django's full migration system via the CLI.
Ensure your configure() call includes migrations_dir:
configure(
{...},
migrations_dir='./migrations'
)When you change your models, run:
sqlorm makemigrations --models your_script.pyThis will create migration files in your specified migrations_dir.
To apply changes to the database:
sqlorm migrate --models your_script.pyThis tracks applied migrations in the django_migrations table, just like standard Django.
Already using Django? SQLORM is designed to be 100% compatible:
| Django | SQLORM |
|---|---|
from django.db import models |
from sqlorm import fields |
class User(models.Model): |
class User(Model): |
models.CharField(...) |
fields.CharField(...) |
python manage.py migrate |
sqlorm migrate ... |
User.objects.all() |
User.objects.all() β
Same! |
Your Django knowledge transfers directly!
| Feature | SQLORM | SQLAlchemy | Peewee | Raw Django |
|---|---|---|---|---|
| Django-compatible API | β | β | β | β |
| No project structure | β | β | β | β |
| Battle-tested at scale | β | β | β | |
| Learning curve | Low* | High | Medium | Low |
| Multiple DB backends | β | β | β | β |
| Async support | β |
* If you know Django, you already know SQLORM!
sqlorm/
βββ sqlorm/
β βββ __init__.py # Main exports
β βββ config.py # Configuration management
β βββ base.py # Model base class
β βββ fields.py # Field type proxy
β βββ connection.py # Connection utilities
β βββ exceptions.py # Custom exceptions
βββ tests/
β βββ test_sqlorm.py # Test suite
βββ examples/
β βββ basic_usage.py
β βββ advanced_usage.py
β βββ postgresql_usage.py
β βββ configuration_examples.py
βββ setup.py
βββ pyproject.toml
βββ README.md
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# With coverage
pytest tests/ --cov=sqlorm --cov-report=htmlContributions are welcome! Here's how:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure to update tests as appropriate.
- Django ORM Documentation: https://docs.djangoproject.com/en/stable/topics/db/
- Django QuerySet API: https://docs.djangoproject.com/en/stable/ref/models/querysets/
- Django Field Types: https://docs.djangoproject.com/en/stable/ref/models/fields/
This project is licensed under the MIT License - see the LICENSE file for details.
- Email: surajsinghbisht054@gmail.com
- GitHub: @surajsinghbisht054
β If you find SQLORM useful, please give it a star! β
Made with β€οΈ for the Python community