plain.models 0.49.2__py3-none-any.whl → 0.51.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/aggregates.py +42 -19
  5. plain/models/backends/base/base.py +125 -105
  6. plain/models/backends/base/client.py +11 -3
  7. plain/models/backends/base/creation.py +24 -14
  8. plain/models/backends/base/features.py +10 -4
  9. plain/models/backends/base/introspection.py +37 -20
  10. plain/models/backends/base/operations.py +187 -91
  11. plain/models/backends/base/schema.py +338 -218
  12. plain/models/backends/base/validation.py +13 -4
  13. plain/models/backends/ddl_references.py +85 -43
  14. plain/models/backends/mysql/base.py +29 -26
  15. plain/models/backends/mysql/client.py +7 -2
  16. plain/models/backends/mysql/compiler.py +13 -4
  17. plain/models/backends/mysql/creation.py +5 -2
  18. plain/models/backends/mysql/features.py +24 -22
  19. plain/models/backends/mysql/introspection.py +22 -13
  20. plain/models/backends/mysql/operations.py +107 -40
  21. plain/models/backends/mysql/schema.py +52 -28
  22. plain/models/backends/mysql/validation.py +13 -6
  23. plain/models/backends/postgresql/base.py +41 -34
  24. plain/models/backends/postgresql/client.py +7 -2
  25. plain/models/backends/postgresql/creation.py +10 -5
  26. plain/models/backends/postgresql/introspection.py +15 -8
  27. plain/models/backends/postgresql/operations.py +110 -43
  28. plain/models/backends/postgresql/schema.py +88 -49
  29. plain/models/backends/sqlite3/_functions.py +151 -115
  30. plain/models/backends/sqlite3/base.py +37 -23
  31. plain/models/backends/sqlite3/client.py +7 -1
  32. plain/models/backends/sqlite3/creation.py +9 -5
  33. plain/models/backends/sqlite3/features.py +5 -3
  34. plain/models/backends/sqlite3/introspection.py +32 -16
  35. plain/models/backends/sqlite3/operations.py +126 -43
  36. plain/models/backends/sqlite3/schema.py +127 -92
  37. plain/models/backends/utils.py +52 -29
  38. plain/models/backups/cli.py +8 -6
  39. plain/models/backups/clients.py +16 -7
  40. plain/models/backups/core.py +24 -13
  41. plain/models/base.py +221 -229
  42. plain/models/cli.py +98 -67
  43. plain/models/config.py +1 -1
  44. plain/models/connections.py +23 -7
  45. plain/models/constraints.py +79 -56
  46. plain/models/database_url.py +1 -1
  47. plain/models/db.py +6 -2
  48. plain/models/deletion.py +80 -56
  49. plain/models/entrypoints.py +1 -1
  50. plain/models/enums.py +22 -11
  51. plain/models/exceptions.py +23 -8
  52. plain/models/expressions.py +441 -258
  53. plain/models/fields/__init__.py +272 -217
  54. plain/models/fields/json.py +123 -57
  55. plain/models/fields/mixins.py +12 -8
  56. plain/models/fields/related.py +324 -290
  57. plain/models/fields/related_descriptors.py +33 -24
  58. plain/models/fields/related_lookups.py +24 -12
  59. plain/models/fields/related_managers.py +102 -79
  60. plain/models/fields/reverse_related.py +66 -63
  61. plain/models/forms.py +101 -75
  62. plain/models/functions/comparison.py +71 -18
  63. plain/models/functions/datetime.py +79 -29
  64. plain/models/functions/math.py +43 -10
  65. plain/models/functions/mixins.py +24 -7
  66. plain/models/functions/text.py +104 -25
  67. plain/models/functions/window.py +12 -6
  68. plain/models/indexes.py +57 -32
  69. plain/models/lookups.py +228 -153
  70. plain/models/meta.py +505 -0
  71. plain/models/migrations/autodetector.py +86 -43
  72. plain/models/migrations/exceptions.py +7 -3
  73. plain/models/migrations/executor.py +33 -7
  74. plain/models/migrations/graph.py +79 -50
  75. plain/models/migrations/loader.py +45 -22
  76. plain/models/migrations/migration.py +23 -18
  77. plain/models/migrations/operations/base.py +38 -20
  78. plain/models/migrations/operations/fields.py +95 -48
  79. plain/models/migrations/operations/models.py +246 -142
  80. plain/models/migrations/operations/special.py +82 -25
  81. plain/models/migrations/optimizer.py +7 -2
  82. plain/models/migrations/questioner.py +58 -31
  83. plain/models/migrations/recorder.py +27 -16
  84. plain/models/migrations/serializer.py +50 -39
  85. plain/models/migrations/state.py +232 -156
  86. plain/models/migrations/utils.py +30 -14
  87. plain/models/migrations/writer.py +17 -14
  88. plain/models/options.py +189 -518
  89. plain/models/otel.py +16 -6
  90. plain/models/preflight.py +42 -17
  91. plain/models/query.py +400 -251
  92. plain/models/query_utils.py +109 -69
  93. plain/models/registry.py +40 -21
  94. plain/models/sql/compiler.py +190 -127
  95. plain/models/sql/datastructures.py +38 -25
  96. plain/models/sql/query.py +320 -225
  97. plain/models/sql/subqueries.py +36 -25
  98. plain/models/sql/where.py +54 -29
  99. plain/models/test/pytest.py +15 -11
  100. plain/models/test/utils.py +4 -2
  101. plain/models/transaction.py +20 -7
  102. plain/models/utils.py +17 -6
  103. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
  104. plain_models-0.51.0.dist-info/RECORD +123 -0
  105. plain_models-0.49.2.dist-info/RECORD +0 -122
  106. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
  107. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
  108. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,52 +1,59 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import decimal
3
5
  import functools
4
6
  import logging
5
7
  import time
8
+ from collections.abc import Generator, Iterator
6
9
  from contextlib import contextmanager
7
10
  from hashlib import md5
11
+ from typing import TYPE_CHECKING, Any
8
12
 
9
13
  from plain.models.db import NotSupportedError
10
14
  from plain.models.otel import db_span
11
15
  from plain.utils.dateparse import parse_time
12
16
 
17
+ if TYPE_CHECKING:
18
+ from plain.models.backends.base.base import BaseDatabaseWrapper
19
+
13
20
  logger = logging.getLogger("plain.models.backends")
14
21
 
15
22
 
16
23
  class CursorWrapper:
17
- def __init__(self, cursor, db):
24
+ def __init__(self, cursor: Any, db: Any) -> None:
18
25
  self.cursor = cursor
19
26
  self.db = db
20
27
 
21
28
  WRAP_ERROR_ATTRS = frozenset(["fetchone", "fetchmany", "fetchall", "nextset"])
22
29
 
23
- def __getattr__(self, attr):
30
+ def __getattr__(self, attr: str) -> Any:
24
31
  cursor_attr = getattr(self.cursor, attr)
25
32
  if attr in CursorWrapper.WRAP_ERROR_ATTRS:
26
33
  return self.db.wrap_database_errors(cursor_attr)
27
34
  else:
28
35
  return cursor_attr
29
36
 
30
- def __iter__(self):
37
+ def __iter__(self) -> Iterator[Any]:
31
38
  with self.db.wrap_database_errors:
32
39
  yield from self.cursor
33
40
 
34
- def __enter__(self):
41
+ def __enter__(self) -> CursorWrapper:
35
42
  return self
36
43
 
37
- def __exit__(self, type, value, traceback):
44
+ def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
38
45
  # Close instead of passing through to avoid backend-specific behavior
39
46
  # (#17671). Catch errors liberally because errors in cleanup code
40
47
  # aren't useful.
41
48
  try:
42
- self.close()
49
+ self.close() # type: ignore[attr-defined]
43
50
  except self.db.Database.Error:
44
51
  pass
45
52
 
46
53
  # The following methods cannot be implemented in __getattr__, because the
47
54
  # code must run when the method is invoked, not just when it is accessed.
48
55
 
49
- def callproc(self, procname, params=None, kparams=None):
56
+ def callproc(self, procname: str, params: Any = None, kparams: Any = None) -> Any:
50
57
  # Keyword parameters for callproc aren't supported in PEP 249, but the
51
58
  # database driver may support them (e.g. cx_Oracle).
52
59
  if kparams is not None and not self.db.features.supports_callproc_kwargs:
@@ -64,23 +71,25 @@ class CursorWrapper:
64
71
  params = params or ()
65
72
  return self.cursor.callproc(procname, params, kparams)
66
73
 
67
- def execute(self, sql, params=None):
74
+ def execute(self, sql: str, params: Any = None) -> Any:
68
75
  return self._execute_with_wrappers(
69
76
  sql, params, many=False, executor=self._execute
70
77
  )
71
78
 
72
- def executemany(self, sql, param_list):
79
+ def executemany(self, sql: str, param_list: Any) -> Any:
73
80
  return self._execute_with_wrappers(
74
81
  sql, param_list, many=True, executor=self._executemany
75
82
  )
76
83
 
77
- def _execute_with_wrappers(self, sql, params, many, executor):
78
- context = {"connection": self.db, "cursor": self}
84
+ def _execute_with_wrappers(
85
+ self, sql: str, params: Any, many: bool, executor: Any
86
+ ) -> Any:
87
+ context: dict[str, Any] = {"connection": self.db, "cursor": self}
79
88
  for wrapper in reversed(self.db.execute_wrappers):
80
89
  executor = functools.partial(wrapper, executor)
81
90
  return executor(sql, params, many, context)
82
91
 
83
- def _execute(self, sql, params, *ignored_wrapper_args):
92
+ def _execute(self, sql: str, params: Any, *ignored_wrapper_args: Any) -> Any:
84
93
  # Wrap in an OpenTelemetry span with standard attributes.
85
94
  with db_span(self.db, sql, params=params):
86
95
  self.db.validate_no_broken_transaction()
@@ -90,7 +99,9 @@ class CursorWrapper:
90
99
  else:
91
100
  return self.cursor.execute(sql, params)
92
101
 
93
- def _executemany(self, sql, param_list, *ignored_wrapper_args):
102
+ def _executemany(
103
+ self, sql: str, param_list: Any, *ignored_wrapper_args: Any
104
+ ) -> Any:
94
105
  with db_span(self.db, sql, many=True, params=param_list):
95
106
  self.db.validate_no_broken_transaction()
96
107
  with self.db.wrap_database_errors:
@@ -100,18 +111,22 @@ class CursorWrapper:
100
111
  class CursorDebugWrapper(CursorWrapper):
101
112
  # XXX callproc isn't instrumented at this time.
102
113
 
103
- def execute(self, sql, params=None):
114
+ def execute(self, sql: str, params: Any = None) -> Any:
104
115
  with self.debug_sql(sql, params, use_last_executed_query=True):
105
116
  return super().execute(sql, params)
106
117
 
107
- def executemany(self, sql, param_list):
118
+ def executemany(self, sql: str, param_list: Any) -> Any:
108
119
  with self.debug_sql(sql, param_list, many=True):
109
120
  return super().executemany(sql, param_list)
110
121
 
111
122
  @contextmanager
112
123
  def debug_sql(
113
- self, sql=None, params=None, use_last_executed_query=False, many=False
114
- ):
124
+ self,
125
+ sql: str | None = None,
126
+ params: Any = None,
127
+ use_last_executed_query: bool = False,
128
+ many: bool = False,
129
+ ) -> Generator[None, None, None]:
115
130
  start = time.monotonic()
116
131
  try:
117
132
  yield
@@ -121,7 +136,7 @@ class CursorDebugWrapper(CursorWrapper):
121
136
  if use_last_executed_query:
122
137
  sql = self.db.ops.last_executed_query(self.cursor, sql, params)
123
138
  try:
124
- times = len(params) if many else ""
139
+ times = len(params) if many else "" # type: ignore[arg-type]
125
140
  except TypeError:
126
141
  # params could be an iterator.
127
142
  times = "?"
@@ -145,7 +160,9 @@ class CursorDebugWrapper(CursorWrapper):
145
160
 
146
161
 
147
162
  @contextmanager
148
- def debug_transaction(connection, sql):
163
+ def debug_transaction(
164
+ connection: BaseDatabaseWrapper, sql: str
165
+ ) -> Generator[None, None, None]:
149
166
  start = time.monotonic()
150
167
  try:
151
168
  yield
@@ -171,7 +188,7 @@ def debug_transaction(connection, sql):
171
188
  )
172
189
 
173
190
 
174
- def split_tzname_delta(tzname):
191
+ def split_tzname_delta(tzname: str) -> tuple[str, str | None, str | None]:
175
192
  """
176
193
  Split a time zone name into a 3-tuple of (name, sign, offset).
177
194
  """
@@ -188,13 +205,15 @@ def split_tzname_delta(tzname):
188
205
  ###############################################
189
206
 
190
207
 
191
- def typecast_date(s):
208
+ def typecast_date(s: str | None) -> datetime.date | None:
192
209
  return (
193
210
  datetime.date(*map(int, s.split("-"))) if s else None
194
211
  ) # return None if s is null
195
212
 
196
213
 
197
- def typecast_time(s): # does NOT store time zone information
214
+ def typecast_time(
215
+ s: str | None,
216
+ ) -> datetime.time | None: # does NOT store time zone information
198
217
  if not s:
199
218
  return None
200
219
  hour, minutes, seconds = s.split(":")
@@ -207,7 +226,9 @@ def typecast_time(s): # does NOT store time zone information
207
226
  )
208
227
 
209
228
 
210
- def typecast_timestamp(s): # does NOT store time zone information
229
+ def typecast_timestamp(
230
+ s: str | None,
231
+ ) -> datetime.date | datetime.datetime | None: # does NOT store time zone information
211
232
  # "2005-07-29 15:48:00.590358-05"
212
233
  # "2005-07-29 09:56:00-05"
213
234
  if not s:
@@ -243,7 +264,7 @@ def typecast_timestamp(s): # does NOT store time zone information
243
264
  ###############################################
244
265
 
245
266
 
246
- def split_identifier(identifier):
267
+ def split_identifier(identifier: str) -> tuple[str, str]:
247
268
  """
248
269
  Split an SQL identifier into a two element tuple of (namespace, name).
249
270
 
@@ -257,7 +278,7 @@ def split_identifier(identifier):
257
278
  return namespace.strip('"'), name.strip('"')
258
279
 
259
280
 
260
- def truncate_name(identifier, length=None, hash_len=4):
281
+ def truncate_name(identifier: str, length: int | None = None, hash_len: int = 4) -> str:
261
282
  """
262
283
  Shorten an SQL identifier to a repeatable mangled version with the given
263
284
  length.
@@ -278,7 +299,7 @@ def truncate_name(identifier, length=None, hash_len=4):
278
299
  )
279
300
 
280
301
 
281
- def names_digest(*args, length):
302
+ def names_digest(*args: str, length: int) -> str:
282
303
  """
283
304
  Generate a 32-bit digest of a set of arguments that can be used to shorten
284
305
  identifying names.
@@ -289,7 +310,9 @@ def names_digest(*args, length):
289
310
  return h.hexdigest()[:length]
290
311
 
291
312
 
292
- def format_number(value, max_digits, decimal_places):
313
+ def format_number(
314
+ value: decimal.Decimal | None, max_digits: int | None, decimal_places: int | None
315
+ ) -> str | None:
293
316
  """
294
317
  Format a number into a string with the requisite number of digits and
295
318
  decimal places.
@@ -304,12 +327,12 @@ def format_number(value, max_digits, decimal_places):
304
327
  decimal.Decimal(1).scaleb(-decimal_places), context=context
305
328
  )
306
329
  else:
307
- context.traps[decimal.Rounded] = 1
330
+ context.traps[decimal.Rounded] = 1 # type: ignore[assignment]
308
331
  value = context.create_decimal(value)
309
332
  return f"{value:f}"
310
333
 
311
334
 
312
- def strip_quotes(table_name):
335
+ def strip_quotes(table_name: str) -> str:
313
336
  """
314
337
  Strip quotes off of quoted table names to make them safe for use in index
315
338
  names, sequence names, etc. For example '"USER"."TABLE"' (an Oracle naming
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import time
3
5
  from pathlib import Path
@@ -11,13 +13,13 @@ from .core import DatabaseBackups
11
13
 
12
14
  @register_cli("backups")
13
15
  @click.group("backups")
14
- def cli():
16
+ def cli() -> None:
15
17
  """Local database backups"""
16
18
  pass
17
19
 
18
20
 
19
21
  @cli.command("list")
20
- def list_backups():
22
+ def list_backups() -> None:
21
23
  backups_handler = DatabaseBackups()
22
24
  backups = backups_handler.find_backups()
23
25
  if not backups:
@@ -40,7 +42,7 @@ def list_backups():
40
42
  @cli.command("create")
41
43
  @click.option("--pg-dump", default="pg_dump", envvar="PG_DUMP")
42
44
  @click.argument("backup_name", default="")
43
- def create_backup(backup_name, pg_dump):
45
+ def create_backup(backup_name: str, pg_dump: str) -> None:
44
46
  backups_handler = DatabaseBackups()
45
47
 
46
48
  if not backup_name:
@@ -62,7 +64,7 @@ def create_backup(backup_name, pg_dump):
62
64
  @click.option("--latest", is_flag=True)
63
65
  @click.option("--pg-restore", default="pg_restore", envvar="PG_RESTORE")
64
66
  @click.argument("backup_name", default="")
65
- def restore_backup(backup_name, latest, pg_restore):
67
+ def restore_backup(backup_name: str, latest: bool, pg_restore: str) -> None:
66
68
  backups_handler = DatabaseBackups()
67
69
 
68
70
  if backup_name and latest:
@@ -89,7 +91,7 @@ def restore_backup(backup_name, latest, pg_restore):
89
91
 
90
92
  @cli.command("delete")
91
93
  @click.argument("backup_name")
92
- def delete_backup(backup_name):
94
+ def delete_backup(backup_name: str) -> None:
93
95
  backups_handler = DatabaseBackups()
94
96
  try:
95
97
  backups_handler.delete(backup_name)
@@ -101,7 +103,7 @@ def delete_backup(backup_name):
101
103
 
102
104
  @cli.command("clear")
103
105
  @click.confirmation_option(prompt="Are you sure you want to delete all backups?")
104
- def clear_backups():
106
+ def clear_backups() -> None:
105
107
  backups_handler = DatabaseBackups()
106
108
  backups = backups_handler.find_backups()
107
109
  for backup in backups:
@@ -1,13 +1,20 @@
1
+ from __future__ import annotations
2
+
1
3
  import gzip
2
4
  import os
3
5
  import subprocess
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from plain.models.backends.base.base import BaseDatabaseWrapper
4
11
 
5
12
 
6
13
  class PostgresBackupClient:
7
- def __init__(self, connection):
14
+ def __init__(self, connection: BaseDatabaseWrapper) -> None:
8
15
  self.connection = connection
9
16
 
10
- def get_env(self):
17
+ def get_env(self) -> dict[str, str]:
11
18
  settings_dict = self.connection.settings_dict
12
19
  options = settings_dict.get("OPTIONS", {})
13
20
  env = {}
@@ -27,7 +34,7 @@ class PostgresBackupClient:
27
34
  env["PGSSLKEY"] = str(options.get("sslkey"))
28
35
  return env
29
36
 
30
- def create_backup(self, backup_path, *, pg_dump="pg_dump"):
37
+ def create_backup(self, backup_path: Path, *, pg_dump: str = "pg_dump") -> None:
31
38
  settings_dict = self.connection.settings_dict
32
39
 
33
40
  args = pg_dump.split()
@@ -64,7 +71,9 @@ class PostgresBackupClient:
64
71
  cmd, env={**os.environ, **self.get_env()}, check=True, shell=True
65
72
  )
66
73
 
67
- def restore_backup(self, backup_path, *, pg_restore="pg_restore", psql="psql"):
74
+ def restore_backup(
75
+ self, backup_path: Path, *, pg_restore: str = "pg_restore", psql: str = "psql"
76
+ ) -> None:
68
77
  settings_dict = self.connection.settings_dict
69
78
 
70
79
  host = settings_dict.get("HOST")
@@ -118,17 +127,17 @@ class PostgresBackupClient:
118
127
 
119
128
 
120
129
  class SQLiteBackupClient:
121
- def __init__(self, connection):
130
+ def __init__(self, connection: BaseDatabaseWrapper) -> None:
122
131
  self.connection = connection
123
132
 
124
- def create_backup(self, backup_path):
133
+ def create_backup(self, backup_path: Path) -> None:
125
134
  self.connection.ensure_connection()
126
135
  src_conn = self.connection.connection
127
136
  dump = "\n".join(src_conn.iterdump())
128
137
  with gzip.open(backup_path, "wt") as f:
129
138
  f.write(dump)
130
139
 
131
- def restore_backup(self, backup_path):
140
+ def restore_backup(self, backup_path: Path) -> None:
132
141
  with gzip.open(backup_path, "rt") as f:
133
142
  sql = f.read()
134
143
 
@@ -1,18 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import os
5
+ from collections.abc import Generator
3
6
  from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, cast
4
8
 
5
9
  from plain.runtime import PLAIN_TEMP_PATH
6
10
 
7
- from .. import db_connection
11
+ from .. import db_connection as _db_connection
8
12
  from .clients import PostgresBackupClient, SQLiteBackupClient
9
13
 
14
+ if TYPE_CHECKING:
15
+ from plain.models.backends.base.base import BaseDatabaseWrapper
16
+
17
+ db_connection = cast("BaseDatabaseWrapper", _db_connection)
18
+ else:
19
+ db_connection = _db_connection
20
+
10
21
 
11
22
  class DatabaseBackups:
12
- def __init__(self):
23
+ def __init__(self) -> None:
13
24
  self.path = PLAIN_TEMP_PATH / "backups"
14
25
 
15
- def find_backups(self):
26
+ def find_backups(self) -> list[DatabaseBackup]:
16
27
  if not self.path.exists():
17
28
  return []
18
29
 
@@ -27,20 +38,20 @@ class DatabaseBackups:
27
38
 
28
39
  return backups
29
40
 
30
- def create(self, name, **create_kwargs):
41
+ def create(self, name: str, **create_kwargs: Any) -> Path:
31
42
  backup = DatabaseBackup(name, backups_path=self.path)
32
43
  if backup.exists():
33
44
  raise Exception(f"Backup {name} already exists")
34
45
  backup_dir = backup.create(**create_kwargs)
35
46
  return backup_dir
36
47
 
37
- def restore(self, name, **restore_kwargs):
48
+ def restore(self, name: str, **restore_kwargs: Any) -> None:
38
49
  backup = DatabaseBackup(name, backups_path=self.path)
39
50
  if not backup.exists():
40
51
  raise Exception(f"Backup {name} not found")
41
52
  backup.restore(**restore_kwargs)
42
53
 
43
- def delete(self, name):
54
+ def delete(self, name: str) -> None:
44
55
  backup = DatabaseBackup(name, backups_path=self.path)
45
56
  if not backup.exists():
46
57
  raise Exception(f"Backup {name} not found")
@@ -48,17 +59,17 @@ class DatabaseBackups:
48
59
 
49
60
 
50
61
  class DatabaseBackup:
51
- def __init__(self, name: str, *, backups_path: Path):
62
+ def __init__(self, name: str, *, backups_path: Path) -> None:
52
63
  self.name = name
53
64
  self.path = backups_path / name
54
65
 
55
66
  if not self.name:
56
67
  raise ValueError("Backup name is required")
57
68
 
58
- def exists(self):
69
+ def exists(self) -> bool:
59
70
  return self.path.exists()
60
71
 
61
- def create(self, **create_kwargs):
72
+ def create(self, **create_kwargs: Any) -> Path:
62
73
  self.path.mkdir(parents=True, exist_ok=True)
63
74
 
64
75
  backup_path = self.path / "default.backup"
@@ -75,7 +86,7 @@ class DatabaseBackup:
75
86
 
76
87
  return self.path
77
88
 
78
- def iter_files(self):
89
+ def iter_files(self) -> Generator[Path, None, None]:
79
90
  for backup_file in self.path.iterdir():
80
91
  if not backup_file.is_file():
81
92
  continue
@@ -83,7 +94,7 @@ class DatabaseBackup:
83
94
  continue
84
95
  yield backup_file
85
96
 
86
- def restore(self, **restore_kwargs):
97
+ def restore(self, **restore_kwargs: Any) -> None:
87
98
  for backup_file in self.iter_files():
88
99
  if db_connection.vendor == "postgresql":
89
100
  PostgresBackupClient(db_connection).restore_backup(
@@ -95,12 +106,12 @@ class DatabaseBackup:
95
106
  else:
96
107
  raise Exception("Unsupported database vendor")
97
108
 
98
- def delete(self):
109
+ def delete(self) -> None:
99
110
  for backup_file in self.iter_files():
100
111
  backup_file.unlink()
101
112
 
102
113
  self.path.rmdir()
103
114
 
104
- def updated_at(self):
115
+ def updated_at(self) -> datetime.datetime:
105
116
  mtime = os.path.getmtime(self.path)
106
117
  return datetime.datetime.fromtimestamp(mtime)