Skip to content

Commit

Permalink
Merge pull request #596 from tortoise/develop
Browse files Browse the repository at this point in the history
Release 0.16.19
  • Loading branch information
abondar authored Dec 23, 2020
2 parents f3900af + a84895a commit 7693805
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 193 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: ci
on: [ push ]
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -32,7 +32,10 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- uses: dschep/install-poetry-action@v1.3
- name: Install and configure Poetry
uses: snok/install-poetry@v1.1.1
with:
virtualenvs-create: false
- name: Install requirements
run: make deps
- name: Run ci
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.x'
- uses: dschep/install-poetry-action@v1.3
- name: Install and configure Poetry
uses: snok/install-poetry@v1.1.1
with:
virtualenvs-create: false
- name: Install requirements
run: make deps
- name: Build dists
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ Changelog

0.16
====

0.16.19
-------
- Replace set `TZ` environment variable to `TIMEZONE` to avoid affecting global timezone.
- Allow passing module objects to `models_paths` param of `Tortoise.init_models()`. (#561)
- Implement `PydanticMeta.backward_relations`. (#536)
- Allow overriding `PydanticMeta` in `PydanticModelCreator`. (#536)
- Fixed make_native typo to make_naive in timezone module

0.16.18
-------
- Support custom function in update. (#537)
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Contributors
* Bogdan Evstratenko ``@evstratbg``
* Mike Ryan ``@devsetgo``
* Eugene Dubovskoy ``@drjackild``
* Lương Quang Mạnh ``@lqmanh``
* Mykyta Holubakha ``@Hummer12007``

Special Thanks
==============
Expand Down
311 changes: 164 additions & 147 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tortoise-orm"
version = "0.16.18"
version = "0.16.19"
description = "Easy async ORM for python, built with relations in mind"
authors = ["Andrey Bondar <andrey@bondar.ru>", "Nickolas Grigoriadis <nagrigoriadis@gmail.com>", "long2ice <long2ice@gmail.com>"]
license = "Apache-2.0"
Expand Down Expand Up @@ -39,7 +39,7 @@ iso8601 = "^0.1.13"
aiosqlite = "^0.16.0"
pytz = "^2020.4"
ciso8601 = { version = "^2.1.2", markers = "sys_platform != 'win32' and implementation_name == 'cpython'", optional = true }
uvloop = { version = "^0.12.0", markers = "sys_platform != 'win32' and implementation_name == 'cpython'", optional = true }
uvloop = { version = "^0.14.0", markers = "sys_platform != 'win32' and implementation_name == 'cpython'", optional = true }
python-rapidjson = { version = "*", optional = true }
asyncpg = { version = "*", optional = true }
aiomysql = { version = "*", optional = true }
Expand All @@ -59,7 +59,7 @@ darglint = "*"
pylint = "*"
pygments = "*"
bandit = "*"
black = "*"
black = "^20.8b1"
# Test tools
tox = "*"
asynctest = "*"
Expand Down
17 changes: 17 additions & 0 deletions tests/contrib/test_pydantic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import copy

from tests.testmodels import Address, Employee, Event, JSONFields, Reporter, Team, Tournament
from tortoise.contrib import test
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
Expand All @@ -11,6 +13,13 @@ async def setUp(self) -> None:
self.Team_Pydantic = pydantic_model_creator(Team)
self.Address_Pydantic = pydantic_model_creator(Address)

class PydanticMetaOverride:
backward_relations = False

self.Event_Pydantic_non_backward = pydantic_model_creator(
Event, meta_override=PydanticMetaOverride, name="Event_non_backward"
)

self.tournament = await Tournament.create(name="New Tournament")
self.reporter = await Reporter.create(name="The Reporter")
self.event = await Event.create(
Expand All @@ -24,6 +33,14 @@ async def setUp(self) -> None:
await self.event2.participants.add(self.team1, self.team2)
self.maxDiff = None

async def test_backward_relations(self):
event_schema = copy.deepcopy(dict(self.Event_Pydantic.schema()))
event_non_backward_schema = copy.deepcopy(dict(self.Event_Pydantic_non_backward.schema()))
self.assertTrue("address" in event_schema["properties"])
self.assertFalse("address" in event_non_backward_schema["properties"])
del event_schema["properties"]["address"]
self.assertEqual(event_schema["properties"], event_non_backward_schema["properties"])

def test_event_schema(self):
self.assertEqual(
self.Event_Pydantic.schema(),
Expand Down
12 changes: 6 additions & 6 deletions tests/fields/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ async def test_default_timezone(self):
self.assertEqual(obj_get.datetime, now)

async def test_set_timezone(self):
old_tz = os.environ["TZ"]
old_tz = os.environ["TIMEZONE"]
tz = "Asia/Shanghai"
os.environ["TZ"] = tz
os.environ["TIMEZONE"] = tz
now = datetime.now(pytz.timezone(tz))
obj = await testmodels.DatetimeFields.create(datetime=now)
self.assertEqual(obj.datetime.tzinfo.zone, tz)
Expand All @@ -126,13 +126,13 @@ async def test_set_timezone(self):
self.assertEqual(obj_get.datetime.tzinfo.zone, tz)
self.assertEqual(obj_get.datetime, now)

os.environ["TZ"] = old_tz
os.environ["TIMEZONE"] = old_tz

async def test_timezone(self):
old_tz = os.environ["TZ"]
old_tz = os.environ["TIMEZONE"]
old_use_tz = os.environ["USE_TZ"]
tz = "Asia/Shanghai"
os.environ["TZ"] = tz
os.environ["TIMEZONE"] = tz
os.environ["USE_TZ"] = "True"

now = datetime.now(pytz.timezone(tz))
Expand All @@ -142,7 +142,7 @@ async def test_timezone(self):
self.assertEqual(obj.datetime.tzinfo.zone, tz)
self.assertEqual(obj_get.datetime, now)

os.environ["TZ"] = old_tz
os.environ["TIMEZONE"] = old_tz
os.environ["USE_TZ"] = old_use_tz


Expand Down
58 changes: 42 additions & 16 deletions tortoise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from contextvars import ContextVar
from copy import deepcopy
from inspect import isclass
from typing import Coroutine, Dict, List, Optional, Tuple, Type, cast
from types import ModuleType
from typing import Coroutine, Dict, Iterable, List, Optional, Tuple, Type, Union, cast

from pypika import Table

Expand All @@ -23,7 +24,6 @@
)
from tortoise.filters import get_m2m_filters
from tortoise.models import Model
from tortoise.queryset import QuerySet
from tortoise.transactions import current_transaction_map
from tortoise.utils import generate_schema_for_client

Expand Down Expand Up @@ -348,11 +348,16 @@ def _discover_client_class(cls, engine: str) -> Type[BaseDBAsyncClient]:
return client_class

@classmethod
def _discover_models(cls, models_path: str, app_label: str) -> List[Type[Model]]:
try:
module = importlib.import_module(models_path)
except ImportError:
raise ConfigurationError(f'Module "{models_path}" not found')
def _discover_models(
cls, models_path: Union[ModuleType, str], app_label: str
) -> List[Type[Model]]:
if isinstance(models_path, ModuleType):
module = models_path
else:
try:
module = importlib.import_module(models_path)
except ImportError:
raise ConfigurationError(f'Module "{models_path}" not found')
discovered_models = []
possible_models = getattr(module, "__models__", None)
try:
Expand Down Expand Up @@ -388,22 +393,26 @@ async def _init_connections(cls, connections_config: dict, create_db: bool) -> N

@classmethod
def init_models(
cls, models_paths: List[str], app_label: str, _init_relations: bool = True
cls,
models_paths: Iterable[Union[ModuleType, str]],
app_label: str,
_init_relations: bool = True,
) -> None:
"""
Early initialisation of Tortoise ORM Models.
Initialise the relationships between Models.
This does not initialise any database connection.
:param models_paths: A list of model paths to initialise
:param models_paths: Models paths to initialise
:param app_label: The app label, e.g. 'models'
:param _init_relations: Whether to init relations or not
:raises ConfigurationError: If models are invalid.
"""
app_models: List[Type[Model]] = []
for module in models_paths:
app_models += cls._discover_models(module, app_label)
for models_path in models_paths:
app_models += cls._discover_models(models_path, app_label)

cls.apps[app_label] = {model.__name__: model for model in app_models}

Expand Down Expand Up @@ -507,7 +516,7 @@ async def init(
}
},
'use_tz': False,
'timezone': UTC
'timezone': 'UTC'
}
:param config_file:
Expand Down Expand Up @@ -557,9 +566,26 @@ async def init(
use_tz = config.get("use_tz", use_tz) # type: ignore
timezone = config.get("timezone", timezone) # type: ignore

logger.info(
# Mask passwords in logs output
passwords = []
for name, info in connections_config.items():
if isinstance(info, str):
info = expand_db_url(info)
password = info.get("credentials", {}).get("password")
if password:
passwords.append(password)

str_connection_config = str(connections_config)
for password in passwords:
str_connection_config = str_connection_config.replace(
password,
# Show one third of the password at beginning (may be better for debugging purposes)
f"{password[0:len(password) // 3]}***",
)

logger.debug(
"Tortoise-ORM startup\n connections: %s\n apps: %s",
str(connections_config),
str_connection_config,
str(apps_config),
)

Expand Down Expand Up @@ -626,7 +652,7 @@ async def _drop_databases(cls) -> None:
@classmethod
def _init_timezone(cls, use_tz: bool, timezone: str) -> None:
os.environ["USE_TZ"] = str(use_tz)
os.environ["TZ"] = timezone
os.environ["TIMEZONE"] = timezone


def run_async(coro: Coroutine) -> None:
Expand Down Expand Up @@ -655,4 +681,4 @@ async def do_stuff():
loop.run_until_complete(Tortoise.close_connections())


__version__ = "0.16.18"
__version__ = "0.16.19"
46 changes: 30 additions & 16 deletions tortoise/contrib/pydantic/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def pydantic_model_creator(
sort_alphabetically: Optional[bool] = None,
_stack: tuple = (),
exclude_readonly: bool = False,
meta_override: Optional[Type] = None,
) -> Type[PydanticModel]:
"""
Function to build `Pydantic Model <https://pydantic-docs.helpmanual.io/usage/models/>`__ off Tortoise Model.
Expand All @@ -146,6 +147,7 @@ def pydantic_model_creator(
* order of reverse relations (as discovered) +
* order of computed functions (as provided).
:param exclude_readonly: Build a subset model that excludes any readonly fields
:param meta_override: A PydanticMeta class to override model's values.
"""

# Fully qualified class name
Expand Down Expand Up @@ -181,23 +183,26 @@ def get_name() -> str:

# Get settings and defaults
meta = getattr(cls, "PydanticMeta", PydanticMeta)
default_include: Tuple[str, ...] = tuple(getattr(meta, "include", PydanticMeta.include))
default_exclude: Tuple[str, ...] = tuple(getattr(meta, "exclude", PydanticMeta.exclude))
default_computed: Tuple[str, ...] = tuple(getattr(meta, "computed", PydanticMeta.computed))
max_recursion: int = int(getattr(meta, "max_recursion", PydanticMeta.max_recursion))
exclude_raw_fields: bool = bool(
getattr(meta, "exclude_raw_fields", PydanticMeta.exclude_raw_fields)
)

def get_param(attr: str) -> Any:
if meta_override:
return getattr(meta_override, attr, getattr(meta, attr, getattr(PydanticMeta, attr)))
return getattr(meta, attr, getattr(PydanticMeta, attr))

default_include: Tuple[str, ...] = tuple(get_param("include"))
default_exclude: Tuple[str, ...] = tuple(get_param("exclude"))
default_computed: Tuple[str, ...] = tuple(get_param("computed"))

backward_relations: bool = bool(get_param("backward_relations"))

max_recursion: int = int(get_param("max_recursion"))
exclude_raw_fields: bool = bool(get_param("exclude_raw_fields"))
_sort_fields: bool = (
bool(getattr(meta, "sort_alphabetically", PydanticMeta.sort_alphabetically))
bool(get_param("sort_alphabetically"))
if sort_alphabetically is None
else sort_alphabetically
)
_allow_cycles: bool = bool(
getattr(meta, "allow_cycles", PydanticMeta.allow_cycles)
if allow_cycles is None
else allow_cycles
)
_allow_cycles: bool = bool(get_param("allow_cycles") if allow_cycles is None else allow_cycles)

# Update parameters with defaults
include = tuple(include) + default_include
Expand Down Expand Up @@ -248,10 +253,19 @@ def field_map_update(keys: tuple, is_relation=True) -> None:
field_map_update(("pk_field",), is_relation=False)
field_map_update(("data_fields",), is_relation=False)
if not exclude_readonly:
field_map_update(
("fk_fields", "o2o_fields", "m2m_fields", "backward_fk_fields", "backward_o2o_fields")
included_fields: tuple = (
"fk_fields",
"o2o_fields",
"m2m_fields",
)

if backward_relations:
included_fields = (
*included_fields,
"backward_fk_fields",
"backward_o2o_fields",
)

field_map_update(included_fields)
# Add possible computed fields
field_map.update(
{
Expand Down
4 changes: 2 additions & 2 deletions tortoise/timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def get_timezone() -> str:
"""
Get timezone from env set in Tortoise config.
"""
return os.environ.get("TZ") or "UTC"
return os.environ.get("TIMEZONE") or "UTC"


def now() -> datetime:
Expand Down Expand Up @@ -108,7 +108,7 @@ def make_aware(
return value.replace(tzinfo=tz)


def make_native(value: datetime, timezone: Optional[str] = None) -> datetime:
def make_naive(value: datetime, timezone: Optional[str] = None) -> datetime:
"""
Make an aware datetime.datetime naive in a given time zone.
Expand Down

0 comments on commit 7693805

Please sign in to comment.