Skip to main content

Django ORM for standalone Python scripts - Use Django's powerful ORM without a full Django project

Project description

Version Python Django License PRs Welcome

๐Ÿš€ SQLORM

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.

   ________  ________  ___       ________  ________  _____ ______
  |\   ____\|\   __  \|\  \     |\   __  \|\   __  \|\   _ \  _   \
  \ \  \___|\ \  \|\  \ \  \    \ \  \|\  \ \  \|\  \ \  \\\__\ \  \
   \ \_____  \ \  \\\  \ \  \    \ \  \\\  \ \   _  _\ \  \\|__| \  \
    \|____|\  \ \  \\\  \ \  \____\ \  \\\  \ \  \\  \\ \  \    \ \  \
      ____\_\  \ \_____  \ \_______\ \_______\ \__\\ _\\ \__\    \ \__\
     |\_________\|___| \__\|_______|\|_______|\|__|\|__|\|__|     \|__|
     \|_________|     \|__|

๐Ÿค” Why SQLORM?

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_APPS and 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

๐Ÿ“ฆ Installation

Install 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 .

With Database Drivers

# Install with PostgreSQL support
pip install git+https://github.com/surajsinghbisht054/sqlorm.git
pip install psycopg2-binary

# Install with MySQL support
pip install git+https://github.com/surajsinghbisht054/sqlorm.git
pip install mysqlclient

# For development (with test dependencies)
git clone https://github.com/surajsinghbisht054/sqlorm.git
cd sqlorm
pip install -e ".[dev]"

Requirements

  • Python 3.8+
  • Django 3.2+

๐Ÿš€ Quick Start

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.

๐Ÿ›  CLI Usage (Migrations)

For production applications, you should use migrations instead of create_tables().

  1. Create a script with your models (e.g., models.py).
  2. Configure with migrations_dir:
    configure(..., migrations_dir='./migrations')
    
  3. Run commands:
# Create migrations
sqlorm makemigrations --models models.py

# Apply migrations
sqlorm migrate --models models.py

๐Ÿ“– Documentation

Table of Contents

  1. Configuration
  2. Defining Models
  3. Field Types
  4. CRUD Operations
  5. Querying
  6. Advanced Features
  7. Raw SQL
  8. Transactions
  9. Multiple Databases
  10. Schema Migrations

Configuration

SQLite (Simplest)

from sqlorm import configure

configure({
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': 'database.sqlite3',  # Or ':memory:' for in-memory DB
})

PostgreSQL

configure({
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': 'mydatabase',
    'USER': 'myuser',
    'PASSWORD': 'mypassword',
    'HOST': 'localhost',
    'PORT': '5432',
})

MySQL

configure({
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'mydatabase',
    'USER': 'myuser',
    'PASSWORD': 'mypassword',
    'HOST': 'localhost',
    'PORT': '3306',
})

From Environment Variables

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 Configuration File

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
}

Defining Models

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'

Field Types

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 database
  • blank=True โ€” Allow empty in forms
  • default=value โ€” Default value
  • unique=True โ€” Must be unique
  • db_index=True โ€” Create database index
  • choices=[...] โ€” Limit to specific values

CRUD Operations

Create

# 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'}
)

Read

# 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()

Update

# 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)

Delete

# Single object
user = User.objects.get(id=1)
user.delete()

# Bulk delete
User.objects.filter(is_active=False).delete()

Querying

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()

Lookup Types

# 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)

Advanced Features

Q Objects (Complex Queries)

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'))

F Expressions (Database Operations)

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'))

Aggregations

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)),
)

Annotations

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}")

Raw SQL

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
)

โš ๏ธ Always use parameterized queries to prevent SQL injection!


Transactions

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 back

Multiple Databases

from 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')

Schema Migrations

SQLORM supports Django's full migration system via the CLI.

1. Setup

Ensure your configure() call includes migrations_dir:

configure(
    {...},
    migrations_dir='./migrations'
)

2. Create Migrations

When you change your models, run:

sqlorm makemigrations --models your_script.py

This will create migration files in your specified migrations_dir.

3. Apply Migrations

To apply changes to the database:

sqlorm migrate --models your_script.py

This tracks applied migrations in the django_migrations table, just like standard Django.


๐Ÿ”„ Migration from 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!


๐Ÿ†š Comparison with Other ORMs

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!


๐Ÿ“ Project Structure

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

๐Ÿงช Running Tests

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# With coverage
pytest tests/ --cov=sqlorm --cov-report=html

๐Ÿค Contributing

Contributions are welcome! Here's how:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure to update tests as appropriate.


๐Ÿ“š Resources


๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


๐Ÿ‘ค Author


โญ If you find SQLORM useful, please give it a star! โญ

Made with โค๏ธ for the Python community

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

django_sqlorm-3.0.0.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

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

django_sqlorm-3.0.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file django_sqlorm-3.0.0.tar.gz.

File metadata

  • Download URL: django_sqlorm-3.0.0.tar.gz
  • Upload date:
  • Size: 16.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for django_sqlorm-3.0.0.tar.gz
Algorithm Hash digest
SHA256 3d954f657516e2f73c8dd7b368b39e057ac89f2d5e2036229d376a8d571ff448
MD5 b4c798d05212b48fb67c0848af9e58c3
BLAKE2b-256 83155cf23b7fd8ed1acceb5a2175d1ac8abe2b16cefa930d999167462e924f43

See more details on using hashes here.

File details

Details for the file django_sqlorm-3.0.0-py3-none-any.whl.

File metadata

  • Download URL: django_sqlorm-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 16.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for django_sqlorm-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e15f4ef583597040d4f35c6e66428d996e4471c3b613a53fa49a59d843742112
MD5 5273f40da319a4c94bc7fc3f86299439
BLAKE2b-256 059b7d2e07a5cda31ed65ed80ccb5cc7fc9ef6ab54d725a6dcabdea0a10a7ffa

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