Skip to content

Commit 5e0f061

Browse files
committed
Merge branch 'master' into develop
2 parents bd9f4ea + a217378 commit 5e0f061

28 files changed

+326
-144
lines changed

CHANGELOG.rst

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ Deprecations:
1515
^^^^^^^^^^^^^
1616
- ``start_transaction`` is deprecated, please use ``@atomic()`` or ``async with in_transaction():`` instead.
1717

18+
19+
0.14.1
20+
-------
21+
- ``ManyToManyField`` is now a function that has the type of the relation for autocomplete,
22+
this allows for better type hinting at less effort.
23+
- Added section on adding better autocomplete for relations in editors.
24+
1825
0.14.0
1926
------
2027
.. caution::

Makefile

+5-4
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,23 @@ endif
4646
bandit -r $(checkfiles)
4747
python setup.py check -mrs
4848

49-
test:
49+
test: deps
5050
$(py_warn) TORTOISE_TEST_DB=sqlite://:memory: py.test
5151

5252
test_sqlite:
5353
$(py_warn) TORTOISE_TEST_DB=sqlite://:memory: py.test --cov-report=
5454

5555
test_postgres:
56-
python -V | grep PyPy || $(py_warn) TORTOISE_TEST_DB="postgres://postgres:$(TORTOISE_POSTGRES_PASS)@127.0.0.1:5432/test_\{\}?minsize=1&maxsize=20" py.test --cov-append --cov-report=
56+
python -V | grep PyPy || $(py_warn) TORTOISE_TEST_DB="postgres://postgres:$(TORTOISE_POSTGRES_PASS)@127.0.0.1:5432/test_\{\}" py.test --cov-append --cov-report=
5757

5858
test_mysql_myisam:
59-
$(py_warn) TORTOISE_TEST_DB="mysql://root:$(TORTOISE_MYSQL_PASS)@127.0.0.1:3306/test_\{\}?minsize=10&maxsize=10&storage_engine=MYISAM" py.test --cov-append --cov-report=
59+
$(py_warn) TORTOISE_TEST_DB="mysql://root:$(TORTOISE_MYSQL_PASS)@127.0.0.1:3306/test_\{\}?storage_engine=MYISAM" py.test --cov-append --cov-report=
6060

6161
test_mysql:
62-
$(py_warn) TORTOISE_TEST_DB="mysql://root:$(TORTOISE_MYSQL_PASS)@127.0.0.1:3306/test_\{\}?minsize=1&maxsize=10" py.test --cov-append
62+
$(py_warn) TORTOISE_TEST_DB="mysql://root:$(TORTOISE_MYSQL_PASS)@127.0.0.1:3306/test_\{\}" py.test --cov-append --cov-report=
6363

6464
_testall: test_sqlite test_postgres test_mysql_myisam test_mysql
65+
coverage report
6566

6667
testall: deps _testall
6768

docs/fields.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ Relational Fields
7777
.. autoclass:: tortoise.fields.ForeignKeyField
7878
:exclude-members: to_db_value, to_python_value
7979

80-
.. autoclass:: tortoise.fields.ManyToManyField
81-
:exclude-members: to_db_value, to_python_value
80+
.. autofunction:: tortoise.fields.ManyToManyField
81+
82+
.. autodata:: tortoise.fields.ForeignKeyRelation
8283

84+
.. autodata:: tortoise.fields.ForeignKeyNullableRelation
8385

8486
Extending A Field
8587
=================

docs/models.rst

+52
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,58 @@ Sync usage requires that you call `fetch_related` before the time, and then you
306306
307307
The reverse lookup of ``team.event_team`` works exactly the same way.
308308

309+
Improving relational type hinting
310+
=================================
311+
312+
Since Tortoise ORM is still a young project, it does not have such widespread support by
313+
various editors who help you writing code using good autocomplete for models and
314+
different relations between them.
315+
However, you can get such autocomplete by doing a little work yourself.
316+
All you need to do is add a few annotations to your models for fields that are responsible
317+
for the relations.
318+
319+
Here is an updated example from :ref:`getting_started`, that will add autocomplete for
320+
all models including fields for the relations between models.
321+
322+
.. code-block:: python3
323+
324+
from tortoise.models import Model
325+
from tortoise import fields
326+
327+
328+
class Tournament(Model):
329+
id = fields.IntField(pk=True)
330+
name = fields.CharField(max_length=255)
331+
332+
events: fields.ReverseRelation["Event"]
333+
334+
def __str__(self):
335+
return self.name
336+
337+
338+
class Event(Model):
339+
id = fields.IntField(pk=True)
340+
name = fields.CharField(max_length=255)
341+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
342+
"models.Tournament", related_name="events"
343+
)
344+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
345+
"models.Team", related_name="events", through="event_team"
346+
)
347+
348+
def __str__(self):
349+
return self.name
350+
351+
352+
class Team(Model):
353+
id = fields.IntField(pk=True)
354+
name = fields.CharField(max_length=255)
355+
356+
events: fields.ManyToManyRelation[Event]
357+
358+
def __str__(self):
359+
return self.name
360+
309361
310362
Reference
311363
=========

docs/query.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Foreign Key
150150

151151
Tortoise ORM provides an API for working with FK relations
152152

153-
.. autoclass:: tortoise.fields.RelationQueryContainer
153+
.. autoclass:: tortoise.fields.ReverseRelation
154154
:members:
155155

156156

@@ -161,7 +161,7 @@ Many to Many
161161

162162
Tortoise ORM provides an API for working with M2M relations
163163

164-
.. autoclass:: tortoise.fields.ManyToManyRelationManager
164+
.. autoclass:: tortoise.fields.ManyToManyRelation
165165
:members:
166166
:inherited-members:
167167

docs/sphinx_autodoc_typehints.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ def get_all_type_hints(obj, name):
212212
pass
213213
except NameError as exc:
214214
try:
215-
rv = get_type_hints(obj, sys.modules['tortoise'].__dict__)
216-
except:
215+
rv = get_type_hints(obj, localns=sys.modules['tortoise'].__dict__)
216+
except Exception as exc:
217217
logger.warning('Cannot resolve forward reference in type annotations of "%s": %s',
218218
name, exc)
219219
rv = obj.__annotations__
@@ -233,9 +233,12 @@ def get_all_type_hints(obj, name):
233233
except (AttributeError, TypeError):
234234
pass
235235
except NameError as exc:
236-
logger.warning('Cannot resolve forward reference in type annotations of "%s": %s',
237-
name, exc)
238-
rv = obj.__annotations__
236+
try:
237+
rv = get_type_hints(obj, localns=sys.modules['tortoise'].__dict__)
238+
except:
239+
logger.warning('Cannot resolve forward reference in type annotations of "%s": %s',
240+
name, exc)
241+
rv = obj.__annotations__
239242

240243
return rv
241244

examples/complex_filtering.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ class Tournament(Model):
1212
id = fields.IntField(pk=True)
1313
name = fields.TextField()
1414

15+
events: fields.ReverseRelation["Event"]
16+
1517
def __str__(self):
1618
return self.name
1719

1820

1921
class Event(Model):
2022
id = fields.IntField(pk=True)
2123
name = fields.TextField()
22-
tournament = fields.ForeignKeyField("models.Tournament", related_name="events")
23-
participants = fields.ManyToManyField(
24+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
25+
"models.Tournament", related_name="events"
26+
)
27+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
2428
"models.Team", related_name="events", through="event_team"
2529
)
2630

@@ -32,6 +36,8 @@ class Team(Model):
3236
id = fields.IntField(pk=True)
3337
name = fields.TextField()
3438

39+
events: fields.ManyToManyRelation[Event]
40+
3541
def __str__(self):
3642
return self.name
3743

examples/complex_prefetching.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ class Tournament(Model):
77
id = fields.IntField(pk=True)
88
name = fields.TextField()
99

10+
events: fields.ReverseRelation["Event"]
11+
1012
def __str__(self):
1113
return self.name
1214

1315

1416
class Event(Model):
1517
id = fields.IntField(pk=True)
1618
name = fields.TextField()
17-
tournament = fields.ForeignKeyField("models.Tournament", related_name="events")
18-
participants = fields.ManyToManyField(
19+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
20+
"models.Tournament", related_name="events"
21+
)
22+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
1923
"models.Team", related_name="events", through="event_team"
2024
)
2125

@@ -27,6 +31,8 @@ class Team(Model):
2731
id = fields.IntField(pk=True)
2832
name = fields.TextField()
2933

34+
events: fields.ManyToManyRelation[Event]
35+
3036
def __str__(self):
3137
return self.name
3238

examples/functions.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ class Tournament(Model):
88
name = fields.TextField()
99
desc = fields.TextField(null=True)
1010

11+
events: fields.ReverseRelation["Event"]
12+
1113
def __str__(self):
1214
return self.name
1315

1416

1517
class Event(Model):
1618
id = fields.IntField(pk=True)
1719
name = fields.TextField()
18-
tournament = fields.ForeignKeyField("models.Tournament", related_name="events")
19-
participants = fields.ManyToManyField(
20+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
21+
"models.Tournament", related_name="events"
22+
)
23+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
2024
"models.Team", related_name="events", through="event_team"
2125
)
2226

@@ -28,6 +32,8 @@ class Team(Model):
2832
id = fields.IntField(pk=True)
2933
name = fields.TextField()
3034

35+
events: fields.ManyToManyRelation[Event]
36+
3137
def __str__(self):
3238
return self.name
3339

examples/relations.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ class Tournament(Model):
1414
id = fields.IntField(pk=True)
1515
name = fields.TextField()
1616

17+
events: fields.ReverseRelation["Event"]
18+
1719
def __str__(self):
1820
return self.name
1921

2022

2123
class Event(Model):
2224
id = fields.IntField(pk=True)
2325
name = fields.TextField()
24-
tournament = fields.ForeignKeyField("models.Tournament", related_name="events")
25-
participants = fields.ManyToManyField(
26+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
27+
"models.Tournament", related_name="events"
28+
)
29+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
2630
"models.Team", related_name="events", through="event_team"
2731
)
2832

@@ -34,6 +38,8 @@ class Team(Model):
3438
id = fields.IntField(pk=True)
3539
name = fields.TextField()
3640

41+
events: fields.ManyToManyRelation[Event]
42+
3743
def __str__(self):
3844
return self.name
3945

examples/relations_recursive.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@
1414

1515
class Employee(Model):
1616
name = fields.CharField(max_length=50)
17-
manager = fields.ForeignKeyField("models.Employee", related_name="team_members", null=True)
18-
talks_to = fields.ManyToManyField("models.Employee", related_name="gets_talked_to")
17+
18+
manager: fields.ForeignKeyNullableRelation["Employee"] = fields.ForeignKeyField(
19+
"models.Employee", related_name="team_members", null=True
20+
)
21+
team_members: fields.ReverseRelation["Employee"]
22+
23+
talks_to: fields.ManyToManyRelation["Employee"] = fields.ManyToManyField(
24+
"models.Employee", related_name="gets_talked_to"
25+
)
26+
gets_talked_to: fields.ManyToManyRelation["Employee"]
1927

2028
def __str__(self):
2129
return self.name

examples/schema_create.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ class Tournament(Model):
1111
name = fields.CharField(max_length=255, description="Tournament name", index=True)
1212
created = fields.DatetimeField(auto_now_add=True, description="Created datetime")
1313

14+
events: fields.ReverseRelation["Event"]
15+
1416
class Meta:
1517
table_description = "What Tournaments we have"
1618

1719

1820
class Event(Model):
1921
id = fields.IntField(pk=True, description="Event ID")
2022
name = fields.CharField(max_length=255, unique=True)
21-
tournament = fields.ForeignKeyField(
23+
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
2224
"models.Tournament", related_name="events", description="FK to tournament"
2325
)
24-
participants = fields.ManyToManyField(
26+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
2527
"models.Team",
2628
related_name="events",
2729
through="event_team",
@@ -38,6 +40,8 @@ class Meta:
3840
class Team(Model):
3941
name = fields.CharField(max_length=50, pk=True, description="The TEAM name (and PK)")
4042

43+
events: fields.ManyToManyRelation[Event]
44+
4145
class Meta:
4246
table_description = "The TEAMS!"
4347

examples/two_databases.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Event(Model):
2929
name = fields.TextField()
3030
tournament_id = fields.IntField()
3131
# Here we make link to events.Team, not models.Team
32-
participants = fields.ManyToManyField(
32+
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
3333
"events.Team", related_name="events", through="event_team"
3434
)
3535

@@ -44,6 +44,8 @@ class Team(Model):
4444
id = fields.IntField(pk=True)
4545
name = fields.TextField()
4646

47+
event_team: fields.ManyToManyRelation[Event]
48+
4749
def __str__(self):
4850
return self.name
4951

tests/models_dup1.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
This is the testing Models — Duplicate 1
33
"""
4+
45
from tortoise import fields
56
from tortoise.models import Model
67

tests/models_dup2.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
This is the testing Models — Duplicate 1
33
"""
4+
45
from tortoise import fields
56
from tortoise.models import Model
67

tests/models_schema_create.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class Meta:
3838

3939
class Team(Model):
4040
name = fields.CharField(max_length=50, pk=True, description="The TEAM name (and PK)")
41-
manager = fields.ForeignKeyField("models.Team", related_name="team_members", null=True)
4241
key = fields.IntField()
42+
manager = fields.ForeignKeyField("models.Team", related_name="team_members", null=True)
4343
talks_to = fields.ManyToManyField("models.Team", related_name="gets_talked_to")
4444

4545
class Meta:
@@ -50,9 +50,11 @@ class Meta:
5050
class SourceFields(Model):
5151
id = fields.IntField(pk=True, source_field="sometable_id")
5252
chars = fields.CharField(max_length=255, source_field="some_chars_table", index=True)
53+
5354
fk = fields.ForeignKeyField(
5455
"models.SourceFields", related_name="team_members", null=True, source_field="fk_sometable"
5556
)
57+
5658
rel_to = fields.ManyToManyField(
5759
"models.SourceFields",
5860
related_name="rel_from",

0 commit comments

Comments
 (0)