Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,8 @@ python:
- "3.6"
- "3.7"

env:
- TEST_DATABASE_URLS="postgresql://localhost/test_database, mysql://localhost/test_database"

services:
- postgresql
- mysql

install:
- pip install -U -r requirements.txt

before_script:
- psql -c 'create database test_database;' -U postgres
- echo 'create database test_database;' | mysql

script:
- scripts/test

after_script:
- codecov
49 changes: 43 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
# ORM

**IN PROGRESS**

*Seriously, it's in progress - we don't even have foreign key support in here
yet. But it's already looking quite nice.*

**Note**: Use `ipython` to try this from the console, since it supports `await`.

```python
models = orm.Models(default_database=database)
import databases
import orm
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Note(orm.Model):
__tablename__ = "notes"
__database__ = database
__metadata__ = metadata

class Notes(models.Model):
id = orm.Integer(primary_key=True)
text = orm.String(max_length=100, allow_empty=False)
text = orm.String(max_length=100)
completed = orm.Boolean(default=False)

# Create the database
engine = sqlalchemy.create_engine(str(database.url))
metadata.create_all(engine)

# .create()
await Note.objects.create(text="Buy the groceries.", completed=False)
await Note.objects.create(text="Call Mum.", completed=True)
await Note.objects.create(text="Send invoices.", completed=True)

notes = await Notes.query().all()
note = await Notes.create(text="Buy the milk", completed=False)
# .all()
notes = await Note.objects.all()

# .filter()
notes = await Note.objects.filter(completed=True).all()

# exact, iexact, contains, icontains, lt, lte, gt, gte, in
notes = await Note.objects.filter(text__icontains="mum").all()

# .get()
note = await Note.objects.get(id=1)

# .update()
await note.update(completed=True)
await Notes.query(id==note.id).get()

# .delete()
await note.delete()
```
15 changes: 4 additions & 11 deletions orm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
from orm.exceptions import NoMatch, MultipleMatches
from orm.fields import Integer, String
from orm.exceptions import MultipleMatches, NoMatch
from orm.fields import Boolean, Integer, String
from orm.models import Model


__version__ = "0.0.1"
__all__ = [
"NoMatch",
"MultipleMatches",
"Integer",
"String",
"Model"
]
__version__ = "0.1.0"
__all__ = ["NoMatch", "MultipleMatches", "Boolean", "Integer", "String", "Model"]
16 changes: 3 additions & 13 deletions orm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,11 @@ def get_column_type(self):
return sqlalchemy.String(length=self.max_length)


# class Text(ModelField, typesystem.Text):
# def get_column_type(self):
# return sqlalchemy.Text()


class Integer(ModelField, typesystem.Integer):
def get_column_type(self):
return sqlalchemy.Integer()


# class Float(ModelField, typesystem.Float):
# def get_column_type(self):
# return sqlalchemy.Float()
#
#
# class Boolean(ModelField, typesystem.Boolean):
# def get_column_type(self):
# return sqlalchemy.Integer()
class Boolean(ModelField, typesystem.Boolean):
def get_column_type(self):
return sqlalchemy.Boolean()
39 changes: 19 additions & 20 deletions orm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import sqlalchemy
import typesystem

from typesystem.schemas import SchemaMetaclass
from orm.exceptions import NoMatch, MultipleMatches

from orm.exceptions import MultipleMatches, NoMatch


class ModelMetaclass(SchemaMetaclass):
Expand Down Expand Up @@ -63,34 +63,33 @@ def build_select_expression(self):
def filter(self, **kwargs):
filter_clauses = self.filter_clauses
for key, value in kwargs.items():
if '__' in key:
if "__" in key:
key, op = key.split("__")
else:
op = 'exact'
op = "exact"

# Map the operation code onto SQLAlchemy's ColumnElement
# https://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.ColumnElement
op_attr = {
'exact': '__eq__',
'iexact': 'ilike',
'contains': 'like',
'icontains': 'ilike',
'in': 'in_',
'gt': '__gt__',
'gte': '__ge__',
'lt': '__lt__',
'lte': '__le__',
"exact": "__eq__",
"iexact": "ilike",
"contains": "like",
"icontains": "ilike",
"in": "in_",
"gt": "__gt__",
"gte": "__ge__",
"lt": "__lt__",
"lte": "__le__",
}[op]

if op in ['contains', 'icontains']:
value = '%' + value + '%'
if op in ["contains", "icontains"]:
value = "%" + value + "%"

column = self.table.columns[key]
clause = getattr(column, op_attr)(value)
filter_clauses.append(clause)

return self.__class__(
model_cls=self.model_cls,
filter_clauses=filter_clauses
)
return self.__class__(model_cls=self.model_cls, filter_clauses=filter_clauses)

async def all(self, **kwargs):
if kwargs:
Expand All @@ -104,7 +103,7 @@ async def get(self, **kwargs):
if kwargs:
return await self.filter(**kwargs).get()

expr = self.build_select_expression()
expr = self.build_select_expression().limit(2)
rows = await self.database.fetch_all(expr)

if not rows:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
databases[sqlite]
typesystem

# Testing
autoflake
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def get_packages(package):
packages=get_packages(PACKAGE),
package_data={PACKAGE: ["py.typed"]},
data_files=[("", ["LICENSE.md"])],
install_requires=["databases"],
install_requires=["databases", "typesystem"],
classifiers=[
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
Expand Down
11 changes: 5 additions & 6 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import asyncio
import functools
import pytest

import databases
import pytest
import sqlalchemy

import orm


DATABASE_URL = "sqlite:///test.db"
database = databases.Database(DATABASE_URL, force_rollback=True)
metadata = sqlalchemy.MetaData()
Expand All @@ -31,6 +29,7 @@ class Product(orm.Model):
id = orm.Integer(primary_key=True)
name = orm.String(max_length=100)
rating = orm.Integer(minimum=1, maximum=5)
in_stock = orm.Boolean(default=False)


@pytest.fixture(autouse=True, scope="module")
Expand Down Expand Up @@ -115,17 +114,17 @@ async def test_model_filter():
with pytest.raises(orm.NoMatch):
await User.objects.get(name="Jim")

await Product.objects.create(name="T-Shirt", rating=5)
await Product.objects.create(name="T-Shirt", rating=5, in_stock=True)
await Product.objects.create(name="Dress", rating=4)
await Product.objects.create(name="Coat", rating=3)
await Product.objects.create(name="Coat", rating=3, in_stock=True)

product = await Product.objects.get(name__iexact="t-shirt", rating=5)
assert product.pk is not None
assert product.name == "T-Shirt"
assert product.rating == 5

products = await Product.objects.all(rating__gte=4)
products = await Product.objects.all(rating__gte=2, in_stock=True)
assert len(products) == 2

products = await Product.objects.all(name__icontains='T')
products = await Product.objects.all(name__icontains="T")
assert len(products) == 2