From cb464bad5f09a58b4fc13a7f16df028981842e39 Mon Sep 17 00:00:00 2001 From: Grigi Date: Thu, 6 Feb 2020 22:41:34 +0200 Subject: [PATCH] Went over the outstanding label:documentation issues --- docs/databases.rst | 47 +++++++++++++++++++++++++++ docs/models.rst | 54 +++++++++++++++++++++++++++++++- tortoise/backends/base/client.py | 8 +++++ tortoise/queryset.py | 8 +++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/docs/databases.rst b/docs/databases.rst index eeed520ce..266b567d5 100644 --- a/docs/databases.rst +++ b/docs/databases.rst @@ -94,6 +94,8 @@ Parameters Duration of inactive connection before assuming that it has gone stale, and force a re-connect. ``schema``: A specific schema to use by default. +``ssl``: + Either ``True`` or a custom SSL context for self-signed certificates. See :ref:`db_ssl` for more info. In case any of ``user``, ``password``, ``host``, ``port`` parameters is missing, we are letting ``asyncpg`` retrieve it from default sources (standard PostgreSQL environment variables or default values). @@ -128,6 +130,51 @@ Parameters Sets TCP NO_DELAY to disable Nagle. ``charset``: Sets the character set in use, defaults to ``utf8mb4`` +``ssl``: + Either ``True`` or a custom SSL context for self-signed certificates. See :ref:`db_ssl` for more info. + +.. _db_ssl: + +Passing in custom SSL Certificates +================================== + +To pass in a custom SSL Cert, one has to use the verbose init structure as the URL parser can't +handle complex objects. + +.. code-block:: python3 + + # Here we create a custom SSL context + import ssl + ctx = ssl.create_default_context() + # And in this example we disable validation... + # Please don't do this. Loot at the official Python ``ssl`` module documentation + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + + # Here we do a verbose init + await Tortoise.init( + config={ + "connections": { + "default": { + "engine": "tortoise.backends.asyncpg", + "credentials": { + "database": None, + "host": "127.0.0.1", + "password": "moo", + "port": 54321, + "user": "postgres", + "ssl": ctx # Here we pass in the SSL context + } + } + }, + "apps": { + "models": { + "models": ["some.models"], + "default_connection": "default", + } + }, + } + ) Base DB client diff --git a/docs/models.rst b/docs/models.rst index 2a75f5116..60d81ac39 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -228,10 +228,62 @@ The ``Meta`` class In event model we got some more fields, that could be interesting for us. ``fields.ForeignKeyField('models.Tournament', related_name='events')`` - Here we create foreign key reference to tournament. We create it by referring to model by it's literal, consisting of app name and model name. `models` is default app name, but you can change it in `class Meta` with `app = 'other'`. + Here we create foreign key reference to tournament. We create it by referring to model by it's literal, consisting of app name and model name. ``models`` is default app name, but you can change it in ``class Meta`` with ``app = 'other'``. ``related_name`` Is keyword argument, that defines field for related query on referenced models, so with that you could fetch all tournaments's events with like this: +The DB-backing field +^^^^^^^^^^^^^^^^^^^^ + +.. note:: + + A ``ForeignKeyField`` is a virtual field, meaning it has no direct DB backing. + Instead it has a field (by default called :samp:`{FKNAME}_id` (that is, just an ``_id`` is appended) + that is the actual DB-backing field. + + This is an important detail as it would allow one to assign/read the actual value directly, + which could be considered an optimization if the entire foreign object isn't needed. + + +Specifying an FK can be done via either passing the object: + +.. code-block:: python3 + + await SomeModel.create(tournament=the_tournament) + # or + somemodel.tournament=the_tournament + +or by directly accessing the DB-backing field: + +.. code-block:: python3 + + await SomeModel.create(tournament_id=the_tournament.pk) + # or + somemodel.tournament_id=the_tournament.pk + + +Querying a relationship is typicall done by appending a double underscore, and then the foreign object's field. Then a normal query attr can be appended. +This can be chained if the next key is also a foreign object: + + :samp:`{FKNAME}__{FOREIGNFIELD}__gt=3` + + or + + :samp:`{FKNAME}__{FOREIGNFK}__{VERYFOREIGNFIELD}__gt=3` + +There is however one major limiatation. We don't want to restrict foreign column names, or have ambiguity (e.g. a foreign object may have a field called ``isnull``) + +Then this would be entierly ambugious: + + :samp:`{FKNAME}__isnull` + +To prevent that we require that direct filters be applied to the DB-backing field of the foreign key: + + :samp:`{FKNAME}_id__isnull` + +Fetching the foreign object +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fetching foreign keys can be done with both async and sync interfaces. Async fetch: diff --git a/tortoise/backends/base/client.py b/tortoise/backends/base/client.py index 6f80b3d02..8f39b1901 100644 --- a/tortoise/backends/base/client.py +++ b/tortoise/backends/base/client.py @@ -122,12 +122,20 @@ async def close(self) -> None: async def db_create(self) -> None: """ Created the database in the server. Typically only called by the test runner. + + Need to have called ``create_connection()``` with parameter ``with_db=False`` set to + use the default connection instead of the configured one, else you would get errors + indicating the database doesn't exist. """ raise NotImplementedError() # pragma: nocoverage async def db_delete(self) -> None: """ Delete the database from the Server. Typically only called by the test runner. + + Need to have called ``create_connection()``` with parameter ``with_db=False`` set to + use the default connection instead of the configured one, else you would get errors + indicating the database is in use. """ raise NotImplementedError() # pragma: nocoverage diff --git a/tortoise/queryset.py b/tortoise/queryset.py index 74adbbfd1..92822d267 100644 --- a/tortoise/queryset.py +++ b/tortoise/queryset.py @@ -412,6 +412,14 @@ def delete(self) -> "DeleteQuery": def update(self, **kwargs: Any) -> "UpdateQuery": """ Update all objects in QuerySet with given kwargs. + + A usage example: + + .. code-block:: py3 + + await Employee.filter(occupation='developer').update(salary=5000) + + Will instead of returning a resultset, update the data in the DB itself. """ return UpdateQuery( db=self._db,