commit 78135453a2337d11a6af2089ff8b1c23d9234278 Author: collerek Date: Sun Aug 2 08:41:38 2020 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d33695b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +p38venv +.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80f90a8 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# ORM + +

+ + Build Status + + + Coverage + + + Package version + +

+ +The `async-orm` package is an async ORM for Python, with support for Postgres, +MySQL, and SQLite. ORM is built with: + +* [SQLAlchemy core][sqlalchemy-core] for query building. +* [`databases`][databases] for cross-database async support. +* [`pydantic`][pydantic] for data validation. + +Because ORM is built on SQLAlchemy core, you can use Alembic to provide +database migrations. + +**ORM is still under development: We recommend pinning any dependencies with `orm~=0.1`** + +**Note**: Use `ipython` to try this from the console, since it supports `await`. + +```python +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 + + id = orm.Integer(primary_key=True) + 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) + +# .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) + +# .delete() +await note.delete() + +# 'pk' always refers to the primary key +note = await Note.objects.get(pk=2) +note.pk # 2 +``` + +ORM supports loading and filtering across foreign keys... + +```python +import databases +import orm +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Album(orm.Model): + __tablename__ = "album" + __metadata__ = metadata + __database__ = database + + id = orm.Integer(primary_key=True) + name = orm.String(max_length=100) + + +class Track(orm.Model): + __tablename__ = "track" + __metadata__ = metadata + __database__ = database + + id = orm.Integer(primary_key=True) + album = orm.ForeignKey(Album) + title = orm.String(max_length=100) + position = orm.Integer() + + +# Create some records to work with. +malibu = await Album.objects.create(name="Malibu") +await Track.objects.create(album=malibu, title="The Bird", position=1) +await Track.objects.create(album=malibu, title="Heart don't stand a chance", position=2) +await Track.objects.create(album=malibu, title="The Waters", position=3) + +fantasies = await Album.objects.create(name="Fantasies") +await Track.objects.create(album=fantasies, title="Help I'm Alive", position=1) +await Track.objects.create(album=fantasies, title="Sick Muse", position=2) + + +# Fetch an instance, without loading a foreign key relationship on it. +track = await Track.objects.get(title="The Bird") + +# We have an album instance, but it only has the primary key populated +print(track.album) # Album(id=1) [sparse] +print(track.album.pk) # 1 +print(track.album.name) # Raises AttributeError + +# Load the relationship from the database +await track.album.load() +assert track.album.name == "Malibu" + +# This time, fetch an instance, loading the foreign key relationship. +track = await Track.objects.select_related("album").get(title="The Bird") +assert track.album.name == "Malibu" + +# Fetch instances, with a filter across an FK relationship. +tracks = Track.objects.filter(album__name="Fantasies") +assert len(tracks) == 2 + +# Fetch instances, with a filter and operator across an FK relationship. +tracks = Track.objects.filter(album__name__iexact="fantasies") +assert len(tracks) == 2 + +# Limit a query +tracks = await Track.objects.limit(1).all() +assert len(tracks) == 1 +``` + +## Data types + +The following keyword arguments are supported on all field types. + +* `primary_key` +* `allow_null` +* `default` +* `index` +* `unique` + +All fields are required unless one of the following is set: + +* `allow_null` - Creates a nullable column. Sets the default to `None`. +* `allow_blank` - Allow empty strings to validate. Sets the default to `""`. +* `default` - Set a default value for the field. + +* `orm.String(max_length)` +* `orm.Text()` +* `orm.Boolean()` +* `orm.Integer()` +* `orm.Float()` +* `orm.Date()` +* `orm.Time()` +* `orm.DateTime()` +* `orm.JSON()` + +[sqlalchemy-core]: https://docs.sqlalchemy.org/en/latest/core/ +[databases]: https://github.com/encode/databases +[pydantic]: https://pydantic-docs.helpmanual.io/ \ No newline at end of file diff --git a/orm/__init__.py b/orm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29