diracx-db 0.0.1a50__tar.gz → 0.0.2__tar.gz

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 (51) hide show
  1. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/PKG-INFO +4 -1
  2. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/pyproject.toml +2 -0
  3. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/__main__.py +1 -0
  4. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/auth/db.py +69 -2
  5. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/utils/base.py +5 -0
  6. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/.gitignore +0 -0
  7. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/README.md +0 -0
  8. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/__init__.py +0 -0
  9. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/exceptions.py +0 -0
  10. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/os/__init__.py +0 -0
  11. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/os/job_parameters.py +0 -0
  12. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/os/utils.py +0 -0
  13. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/py.typed +0 -0
  14. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/__init__.py +0 -0
  15. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/auth/__init__.py +0 -0
  16. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/auth/schema.py +0 -0
  17. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/dummy/__init__.py +0 -0
  18. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/dummy/db.py +0 -0
  19. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/dummy/schema.py +0 -0
  20. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job/__init__.py +0 -0
  21. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job/db.py +0 -0
  22. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job/schema.py +0 -0
  23. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job_logging/__init__.py +0 -0
  24. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job_logging/db.py +0 -0
  25. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/job_logging/schema.py +0 -0
  26. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/pilot_agents/__init__.py +0 -0
  27. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/pilot_agents/db.py +0 -0
  28. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/pilot_agents/schema.py +0 -0
  29. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/sandbox_metadata/__init__.py +0 -0
  30. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/sandbox_metadata/db.py +0 -0
  31. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/sandbox_metadata/schema.py +0 -0
  32. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/task_queue/__init__.py +0 -0
  33. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/task_queue/db.py +0 -0
  34. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/task_queue/schema.py +0 -0
  35. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/utils/__init__.py +0 -0
  36. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/utils/functions.py +0 -0
  37. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/src/diracx/db/sql/utils/types.py +0 -0
  38. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/auth/test_authorization_flow.py +0 -0
  39. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/auth/test_device_flow.py +0 -0
  40. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/auth/test_refresh_token.py +0 -0
  41. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/jobs/test_job_db.py +0 -0
  42. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/jobs/test_job_logging_db.py +0 -0
  43. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/jobs/test_sandbox_metadata.py +0 -0
  44. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/opensearch/test_connection.py +0 -0
  45. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/opensearch/test_index_template.py +0 -0
  46. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/opensearch/test_search.py +0 -0
  47. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/pilot_agents/__init__.py +0 -0
  48. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/pilot_agents/test_pilot_agents_db.py +0 -0
  49. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/test_dummy_db.py +0 -0
  50. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/test_freeze_time.py +0 -0
  51. {diracx_db-0.0.1a50 → diracx_db-0.0.2}/tests/utils/test_uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diracx-db
3
- Version: 0.0.1a50
3
+ Version: 0.0.2
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -12,8 +12,11 @@ Requires-Python: >=3.11
12
12
  Requires-Dist: diracx-core
13
13
  Requires-Dist: opensearch-py[async]
14
14
  Requires-Dist: pydantic>=2.10
15
+ Requires-Dist: python-dateutil
15
16
  Requires-Dist: sqlalchemy[aiomysql,aiosqlite]>=2
16
17
  Requires-Dist: uuid-utils
17
18
  Provides-Extra: testing
18
19
  Requires-Dist: diracx-testing; extra == 'testing'
19
20
  Requires-Dist: freezegun; extra == 'testing'
21
+ Provides-Extra: types
22
+ Requires-Dist: types-python-dateutil; extra == 'types'
@@ -18,11 +18,13 @@ dependencies = [
18
18
  "pydantic >=2.10",
19
19
  "sqlalchemy[aiomysql,aiosqlite] >= 2",
20
20
  "uuid-utils",
21
+ "python-dateutil",
21
22
  ]
22
23
  dynamic = ["version"]
23
24
 
24
25
  [project.optional-dependencies]
25
26
  testing = ["diracx-testing", "freezegun"]
27
+ types = ["types-python-dateutil"]
26
28
 
27
29
  [project.entry-points."diracx.dbs.sql"]
28
30
  AuthDB = "diracx.db.sql:AuthDB"
@@ -39,6 +39,7 @@ async def init_sql():
39
39
  if db._db_url.startswith("sqlite"):
40
40
  await conn.exec_driver_sql("PRAGMA foreign_keys=ON")
41
41
  await conn.run_sync(db.metadata.create_all)
42
+ await db.post_create(conn)
42
43
 
43
44
 
44
45
  async def init_os():
@@ -1,16 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import secrets
5
+ from datetime import UTC, datetime
6
+ from itertools import pairwise
4
7
 
5
- from sqlalchemy import insert, select, update
8
+ from dateutil.rrule import MONTHLY, rrule
9
+ from sqlalchemy import insert, select, text, update
6
10
  from sqlalchemy.exc import IntegrityError, NoResultFound
11
+ from sqlalchemy.ext.asyncio import AsyncConnection
7
12
  from uuid_utils import UUID, uuid7
8
13
 
9
14
  from diracx.core.exceptions import (
10
15
  AuthorizationError,
11
16
  TokenNotFoundError,
12
17
  )
13
- from diracx.db.sql.utils import BaseSQLDB, hash, substract_date
18
+ from diracx.db.sql.utils import BaseSQLDB, hash, substract_date, uuid7_from_datetime
14
19
 
15
20
  from .schema import (
16
21
  AuthorizationFlows,
@@ -25,10 +30,72 @@ from .schema import Base as AuthDBBase
25
30
  USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ"
26
31
  MAX_RETRY = 5
27
32
 
33
+ logger = logging.getLogger(__name__)
34
+
28
35
 
29
36
  class AuthDB(BaseSQLDB):
30
37
  metadata = AuthDBBase.metadata
31
38
 
39
+ @classmethod
40
+ async def post_create(cls, conn: AsyncConnection) -> None:
41
+ """Create partitions if it is a MySQL DB and it does not have
42
+ it yet and the table does not have any data yet.
43
+ We do this as a post_create step as sqlalchemy does not support
44
+ partition so well.
45
+ """
46
+ if conn.dialect.name == "mysql":
47
+ check_partition_query = text(
48
+ "SELECT PARTITION_NAME FROM information_schema.partitions "
49
+ "WHERE TABLE_NAME = 'RefreshTokens' AND PARTITION_NAME is not NULL"
50
+ )
51
+ partition_names = (await conn.execute(check_partition_query)).all()
52
+
53
+ if not partition_names:
54
+ # Create a monthly partition from today until 2 years
55
+ # The partition are named p_<year>_<month>
56
+ start_date = datetime.now(tz=UTC).replace(
57
+ day=1, hour=0, minute=0, second=0, microsecond=0
58
+ )
59
+ end_date = start_date.replace(year=start_date.year + 2)
60
+
61
+ dates = [
62
+ dt for dt in rrule(MONTHLY, dtstart=start_date, until=end_date)
63
+ ]
64
+
65
+ partition_list = []
66
+ for name, limit in pairwise(dates):
67
+ partition_list.append(
68
+ f"PARTITION p_{name.year}_{name.month} "
69
+ f"VALUES LESS THAN ('{str(uuid7_from_datetime(limit, randomize=False)).replace('-', '')}')"
70
+ )
71
+ partition_list.append("PARTITION p_future VALUES LESS THAN (MAXVALUE)")
72
+
73
+ alter_query = text(
74
+ f"ALTER TABLE RefreshTokens PARTITION BY RANGE COLUMNS (JTI) ({','.join(partition_list)})"
75
+ )
76
+
77
+ check_table_empty_query = text("SELECT * FROM RefreshTokens LIMIT 1")
78
+ refresh_table_content = (
79
+ await conn.execute(check_table_empty_query)
80
+ ).all()
81
+ if refresh_table_content:
82
+ logger.warning(
83
+ "RefreshTokens table not empty. Run the following query yourself"
84
+ )
85
+ logger.warning(alter_query)
86
+ return
87
+
88
+ await conn.execute(alter_query)
89
+
90
+ partition_names = (
91
+ await conn.execute(
92
+ check_partition_query, {"table_name": "RefreshTokens"}
93
+ )
94
+ ).all()
95
+ assert partition_names, (
96
+ f"There should be partitions now {partition_names}"
97
+ )
98
+
32
99
  async def device_flow_validate_user_code(
33
100
  self, user_code: str, max_validity: int
34
101
  ) -> str:
@@ -155,6 +155,11 @@ class BaseSQLDB(metaclass=ABCMeta):
155
155
  raise
156
156
  return db_urls
157
157
 
158
+ @classmethod
159
+ async def post_create(cls, conn: AsyncConnection) -> None:
160
+ """Execute actions after the schema has been created."""
161
+ return
162
+
158
163
  @classmethod
159
164
  def transaction(cls) -> Self:
160
165
  raise NotImplementedError("This should never be called")
File without changes
File without changes