diff --git a/.gitignore b/.gitignore index a6f90a5..348e96d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .DS_Store .idea/ +.vscode +.venv/ +.env # Byte-compiled / optimized / DLL files __pycache__/ @@ -10,6 +13,7 @@ __pycache__/ # Distribution / packaging .Python +.installed.cfg env/ venv/ build/ @@ -23,7 +27,6 @@ parts/ sdist/ var/ *.egg-info/ -.installed.cfg *.egg* *.ini diff --git a/README.md b/README.md index b86a77b..cea9f32 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Overview The complete documentation: http://peewee-async-lib.readthedocs.io + Install ------- @@ -37,6 +38,7 @@ or for MySQL: pip install peewee-async[mysql] ``` + Quickstart ---------- @@ -44,7 +46,6 @@ Create 'test' PostgreSQL database for running this snippet: createdb -E utf-8 test - ```python import asyncio import peewee @@ -99,6 +100,18 @@ with objects.allow_sync(): # Not bad. Watch this, I'm async! ``` + +More examples +------------- + +Build and run with Docker Compose: + +```bash +docker compose -f examples/docker-compose.yaml build +docker compose -f examples/docker-compose.yaml up +``` + + Documentation ------------- @@ -106,28 +119,35 @@ http://peewee-async-lib.readthedocs.io http://peewee-async.readthedocs.io - **DEPRECATED** + Developing ---------- + Install dependencies using pip: + ```bash pip install -e .[develop] ``` Or using [poetry](https://python-poetry.org/docs/): + ```bash poetry install -E develop ``` Run databases: + ```bash docker-compose up -d ``` Run tests: + ```bash pytest tests -v -s ``` + Discuss ------- diff --git a/docker-compose.yml b/docker-compose.yml index 9bf76da..bc1b058 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: postgres: image: postgres ports: - - '5432:5432' + - ${POSTGRES_PORT:-5432}:5432 command: postgres -c log_statement=all environment: - POSTGRES_PASSWORD=postgres @@ -14,7 +14,7 @@ services: mysql: image: mysql ports: - - '3306:3306' + - ${MYSQL_PORT:-3306}:3306 environment: - MYSQL_ROOT_PASSWORD=mysql - MYSQL_DATABASE=mysql diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..85ca5dc --- /dev/null +++ b/examples/README.md @@ -0,0 +1,54 @@ +Examples for peewee-async +========================= + +To run the examples install dependencies first: + +```bash +pip install -r examples/requirements.txt +``` + +Also please run database service and provide credentials in environment variables. +Feel free to use development database services with the default credentials, e.g: + +```bash +docker compose up postgres +``` + +## Example for `aiohttp` server + +The example `aiohttp_example.py` is using older interface with `Manager` class. The `Manager` +will be deprecated in v1.0 but we aim to support it until that milestone. + +Define database connection settings if needed, environment variables used: + +- `POSTGRES_DB` +- `POSTGRES_USER` +- `POSTGRES_PASSWORD` +- `POSTGRES_HOST` +- `POSTGRES_PORT` + +Run this command to create example tables in the database: + +```bash +python -m examples.aiohttp_example +``` + +Run this command to start an example application: + +```bash +gunicorn --bind 127.0.0.1:8080 --log-level INFO --access-logfile - \ + --worker-class aiohttp.GunicornWebWorker --reload \ + examples.aiohttp_example:app +``` + +Application should be up and running: + +```bash +curl 'http://127.0.0.1:8080/?p=1' +``` + +the output should be: + +``` +This is a first post +``` diff --git a/examples/aiohttp_example.py b/examples/aiohttp_example.py new file mode 100644 index 0000000..3539e81 --- /dev/null +++ b/examples/aiohttp_example.py @@ -0,0 +1,104 @@ +import json +import logging +import os +from secrets import token_hex +from datetime import datetime +from aiohttp import web +from peewee import Model, CharField, TextField, DateTimeField +from peewee_async import PooledPostgresqlDatabase, Manager + +logger = logging.getLogger(__name__) + +database = PooledPostgresqlDatabase( + os.environ.get('POSTGRES_DB', 'postgres'), + user=os.environ.get('POSTGRES_USER', 'postgres'), + password=os.environ.get('POSTGRES_PASSWORD', 'postgres'), + host=os.environ.get('POSTGRES_HOST', '127.0.0.1'), + port=int(os.environ.get('POSTGRES_PORT', 5432)), + min_connections=2, + max_connections=10, +) + +objects = Manager(database) + +app = web.Application() + +routes = web.RouteTableDef() + + +class Post(Model): + title = CharField(unique=True) + key = CharField(unique=True, default=lambda: token_hex(8)) + text = TextField() + created_at = DateTimeField(index=True, default=datetime.utcnow) + + class Meta: + database = database + + def __str__(self): + return self.title + + +def add_post(title, text): + with database.atomic(): + Post.create(title=title, text=text) + + +@routes.get('/') +async def get_post_endpoint(request): + query = dict(request.query) + post_id = query.pop('p', 1) + post = await objects.get_or_none(Post, id=post_id) + if post: + return web.Response(text=post.text) + else: + return web.Response(text="Not found", status=404) + + +@routes.post('/') +async def update_post_endpoint(request): + query = dict(request.query) + post_id = query.pop('p', 1) + try: + data = await request.content.read() + data = json.loads(data) + text = data.get('text') + if not text: + raise ValueError("Missing 'text' in data") + except Exception as exc: + return web.Response(text=str(exc), status=400) + + post = await objects.get_or_none(Post, id=post_id) + if post: + post.text = text + await objects.update(post) + return web.Response(text=post.text) + else: + return web.Response(text="Not found", status=404) + + +# Setup application routes + +app.add_routes(routes) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + + print("Initialize tables and add some random posts...") + + try: + with database: + database.create_tables([Post], safe=True) + print("Tables are created.") + except Exception as exc: + print("Error creating tables: {}".format(exc)) + + try: + add_post("Hello, world", "This is a first post") + add_post("Hello, world 2", "This is a second post") + add_post("42", "What is this all about?") + add_post("Let it be!", "Let it be, let it be, let it be, let it be") + print("Done.") + except Exception as exc: + print("Error adding posts: {}".format(exc)) diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 0000000..59a006d --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1,11 @@ +fastapi==0.111.0 +uvicorn==0.29.0 +tornado==6.4 +aiohttp==3.9.5 +gunicorn==22.0.0 + +aiopg~=1.4.0 +aiomysql~=0.2.0 + +peewee~=3.17.3 +# peewee-async~=0.10.0 diff --git a/peewee_async.py b/peewee_async.py index 6d64ed7..fce5f23 100644 --- a/peewee_async.py +++ b/peewee_async.py @@ -698,7 +698,7 @@ def execute_sql(self, *args, **kwargs): return super().execute_sql(*args, **kwargs) async def fetch_results(self, query, cursor): - if isinstance(query, peewee.ModelCompoundSelectQuery): + if isinstance(query, peewee.BaseModelSelect): return await AsyncQueryWrapper.make_for_all_rows(cursor, query) if isinstance(query, peewee.RawQuery): return await AsyncQueryWrapper.make_for_all_rows(cursor, query) diff --git a/pyproject.toml b/pyproject.toml index 67f0554..7362563 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,8 +21,6 @@ pytest-asyncio = { version = "^0.21.1", optional = true } sphinx = { version = "^7.1.2", optional = true } sphinx-rtd-theme = { version = "^1.3.0rc1", optional = true } - - [tool.poetry.extras] postgresql = ["aiopg"] mysql = ["aiomysql", "cryptography"]