diracx-db 0.0.1a48__tar.gz → 0.0.1a50__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.1a48 → diracx_db-0.0.1a50}/PKG-INFO +1 -1
  2. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/auth/schema.py +6 -2
  3. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/utils/__init__.py +15 -11
  4. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/utils/base.py +33 -1
  5. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/auth/test_refresh_token.py +0 -2
  6. diracx_db-0.0.1a50/tests/utils/test_uuid.py +221 -0
  7. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/.gitignore +0 -0
  8. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/README.md +0 -0
  9. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/pyproject.toml +0 -0
  10. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/__init__.py +0 -0
  11. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/__main__.py +0 -0
  12. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/exceptions.py +0 -0
  13. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/os/__init__.py +0 -0
  14. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/os/job_parameters.py +0 -0
  15. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/os/utils.py +0 -0
  16. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/py.typed +0 -0
  17. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/__init__.py +0 -0
  18. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/auth/__init__.py +0 -0
  19. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/auth/db.py +0 -0
  20. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/dummy/__init__.py +0 -0
  21. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/dummy/db.py +0 -0
  22. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/dummy/schema.py +0 -0
  23. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job/__init__.py +0 -0
  24. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job/db.py +0 -0
  25. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job/schema.py +0 -0
  26. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job_logging/__init__.py +0 -0
  27. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job_logging/db.py +0 -0
  28. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/job_logging/schema.py +0 -0
  29. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/pilot_agents/__init__.py +0 -0
  30. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/pilot_agents/db.py +0 -0
  31. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/pilot_agents/schema.py +0 -0
  32. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/sandbox_metadata/__init__.py +0 -0
  33. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/sandbox_metadata/db.py +0 -0
  34. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/sandbox_metadata/schema.py +0 -0
  35. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/task_queue/__init__.py +0 -0
  36. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/task_queue/db.py +0 -0
  37. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/task_queue/schema.py +0 -0
  38. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/utils/functions.py +0 -0
  39. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/src/diracx/db/sql/utils/types.py +0 -0
  40. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/auth/test_authorization_flow.py +0 -0
  41. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/auth/test_device_flow.py +0 -0
  42. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/jobs/test_job_db.py +0 -0
  43. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/jobs/test_job_logging_db.py +0 -0
  44. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/jobs/test_sandbox_metadata.py +0 -0
  45. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/opensearch/test_connection.py +0 -0
  46. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/opensearch/test_index_template.py +0 -0
  47. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/opensearch/test_search.py +0 -0
  48. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/pilot_agents/__init__.py +0 -0
  49. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/pilot_agents/test_pilot_agents_db.py +0 -0
  50. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/test_dummy_db.py +0 -0
  51. {diracx_db-0.0.1a48 → diracx_db-0.0.1a50}/tests/test_freeze_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diracx-db
3
- Version: 0.0.1a48
3
+ Version: 0.0.1a50
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -10,7 +10,12 @@ from sqlalchemy import (
10
10
  )
11
11
  from sqlalchemy.orm import declarative_base
12
12
 
13
- from diracx.db.sql.utils import Column, DateNowColumn, EnumColumn, NullColumn
13
+ from diracx.db.sql.utils import (
14
+ Column,
15
+ DateNowColumn,
16
+ EnumColumn,
17
+ NullColumn,
18
+ )
14
19
 
15
20
  USER_CODE_LENGTH = 8
16
21
 
@@ -92,7 +97,6 @@ class RefreshTokens(Base):
92
97
  status = EnumColumn(
93
98
  "Status", RefreshTokenStatus, server_default=RefreshTokenStatus.CREATED.name
94
99
  )
95
- creation_time = DateNowColumn("CreationTime", index=True)
96
100
  scope = Column("Scope", String(1024))
97
101
 
98
102
  # User attributes bound to the refresh token
@@ -1,16 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .base import (
4
- BaseSQLDB,
5
- SQLDBUnavailableError,
6
- _get_columns,
7
- apply_search_filters,
8
- apply_sort_constraints,
9
- )
10
- from .functions import hash, substract_date, utcnow
11
- from .types import Column, DateNowColumn, EnumBackedBool, EnumColumn, NullColumn
12
-
13
- __all__ = (
3
+ __all__ = [
14
4
  "_get_columns",
15
5
  "utcnow",
16
6
  "Column",
@@ -24,4 +14,18 @@ __all__ = (
24
14
  "substract_date",
25
15
  "hash",
26
16
  "SQLDBUnavailableError",
17
+ "uuid7_from_datetime",
18
+ "uuid7_to_datetime",
19
+ ]
20
+
21
+ from .base import (
22
+ BaseSQLDB,
23
+ SQLDBUnavailableError,
24
+ _get_columns,
25
+ apply_search_filters,
26
+ apply_sort_constraints,
27
+ uuid7_from_datetime,
28
+ uuid7_to_datetime,
27
29
  )
30
+ from .functions import hash, substract_date, utcnow
31
+ from .types import Column, DateNowColumn, EnumBackedBool, EnumColumn, NullColumn
@@ -7,13 +7,15 @@ import re
7
7
  from abc import ABCMeta
8
8
  from collections.abc import AsyncIterator
9
9
  from contextvars import ContextVar
10
- from datetime import datetime
10
+ from datetime import datetime, timezone
11
11
  from typing import Any, Self, cast
12
+ from uuid import UUID as StdUUID # noqa: N811
12
13
 
13
14
  from pydantic import TypeAdapter
14
15
  from sqlalchemy import DateTime, MetaData, func, select
15
16
  from sqlalchemy.exc import OperationalError
16
17
  from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
18
+ from uuid_utils import UUID, uuid7
17
19
 
18
20
  from diracx.core.exceptions import InvalidQueryError
19
21
  from diracx.core.extensions import select_from_extension
@@ -416,3 +418,33 @@ def apply_sort_constraints(column_mapping, stmt, sorts):
416
418
  if sort_columns:
417
419
  stmt = stmt.order_by(*sort_columns)
418
420
  return stmt
421
+
422
+
423
+ def uuid7_to_datetime(uuid: UUID | StdUUID | str) -> datetime:
424
+ """Convert a UUIDv7 to a datetime."""
425
+ if isinstance(uuid, StdUUID):
426
+ # Convert stdlib UUID to uuid_utils.UUID
427
+ uuid = UUID(str(uuid))
428
+ elif not isinstance(uuid, UUID):
429
+ # Convert string or other types to uuid_utils.UUID
430
+ uuid = UUID(uuid)
431
+ if uuid.version != 7:
432
+ raise ValueError(f"UUID {uuid} is not a UUIDv7")
433
+ return datetime.fromtimestamp(uuid.timestamp / 1000.0, tz=timezone.utc)
434
+
435
+
436
+ def uuid7_from_datetime(dt: datetime, *, randomize: bool = True) -> UUID:
437
+ """Generate a UUIDv7 corresponding to the given datetime.
438
+
439
+ If randomize is True, the standard uuid7 function is used resulting in the
440
+ lowest 62-bits being random. If randomize is False, the UUIDv7 will be the
441
+ lowest possible UUIDv7 for the given datetime.
442
+ """
443
+ timestamp = dt.timestamp()
444
+ if randomize:
445
+ uuid = uuid7(int(timestamp), int((timestamp % 1) * 1e9))
446
+ else:
447
+ time_high = int(timestamp * 1000) >> 16
448
+ time_low = int(timestamp * 1000) & 0xFFFF
449
+ uuid = UUID.from_fields((time_high, time_low, 0x7000, 0x80, 0, 0))
450
+ return uuid
@@ -56,7 +56,6 @@ async def test_get(auth_db: AuthDB):
56
56
  refresh_token_details["sub"],
57
57
  refresh_token_details["scope"],
58
58
  )
59
- creation_time = (await auth_db.get_refresh_token(jti))["CreationTime"]
60
59
 
61
60
  # Enrich the dict with the generated refresh token attributes
62
61
  expected_refresh_token = {
@@ -64,7 +63,6 @@ async def test_get(auth_db: AuthDB):
64
63
  "Scope": refresh_token_details["scope"],
65
64
  "JTI": jti,
66
65
  "Status": RefreshTokenStatus.CREATED,
67
- "CreationTime": creation_time,
68
66
  }
69
67
 
70
68
  # Get refresh token details
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timedelta, timezone
4
+ from uuid import UUID as StdUUID # noqa: N811
5
+
6
+ import freezegun
7
+ import pytest
8
+ from uuid_utils import UUID, uuid7
9
+
10
+ from diracx.db.sql.utils import uuid7_from_datetime, uuid7_to_datetime
11
+
12
+
13
+ def frozen_uuid7() -> UUID:
14
+ """Create a UUID7 in a way which respects the freezegun context."""
15
+ timestamp = datetime.now(tz=timezone.utc).timestamp()
16
+ return uuid7(int(timestamp), int((timestamp % 1) * 1e9))
17
+
18
+
19
+ class TestDatetimeToUuid7:
20
+ """Test cases for the datetime_to_uuid7 function."""
21
+
22
+ def test_datetime_to_uuid7_random(self):
23
+ """Test that the datetime_to_uuid7 function returns a UUID7 with the current timestamp."""
24
+ dt = datetime.fromisoformat("2024-01-15T12:30:45.123456+00:00")
25
+ result = uuid7_from_datetime(dt, randomize=True)
26
+ assert str(result)[:15] == "018d0d1a-5183-7"
27
+ assert len(set(str(result)[15:])) > 4
28
+
29
+ def test_datetime_to_uuid7_deterministic(self):
30
+ """Test that the datetime_to_uuid7 function returns a UUID7 with the current timestamp."""
31
+ dt = datetime.fromisoformat("2024-01-15T12:30:45.123456+00:00")
32
+ result = uuid7_from_datetime(dt, randomize=False)
33
+ assert str(result) == "018d0d1a-5183-7000-8000-000000000000"
34
+
35
+
36
+ class TestUuid7ToDatetime:
37
+ """Test cases for the uuid7_to_datetime function."""
38
+
39
+ def test_uuid7_now(self):
40
+ """Test that the uuid7 function returns a UUID7 with the current timestamp."""
41
+ test_uuid = frozen_uuid7()
42
+ result = uuid7_to_datetime(test_uuid)
43
+ assert result.tzinfo == timezone.utc
44
+ assert result - datetime.now(tz=timezone.utc) < timedelta(milliseconds=2)
45
+
46
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
47
+ def test_uuid7_to_datetime_with_uuid_utils_uuid(self):
48
+ """Test conversion with uuid_utils.UUID object."""
49
+ test_uuid = frozen_uuid7()
50
+ result = uuid7_to_datetime(test_uuid)
51
+
52
+ assert isinstance(result, datetime)
53
+ assert result.tzinfo == timezone.utc
54
+ assert result.isoformat() == "2024-01-15T12:30:45.123000+00:00"
55
+
56
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
57
+ def test_uuid7_to_datetime_with_stdlib_uuid(self):
58
+ """Test conversion with standard library UUID object."""
59
+ test_uuid = frozen_uuid7()
60
+ stdlib_uuid = StdUUID(str(test_uuid))
61
+
62
+ result = uuid7_to_datetime(stdlib_uuid)
63
+ assert isinstance(result, datetime)
64
+ assert result.tzinfo == timezone.utc
65
+
66
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
67
+ def test_uuid7_to_datetime_with_string_uuid(self):
68
+ """Test conversion with string UUID that gets converted to uuid_utils.UUID."""
69
+ test_uuid = frozen_uuid7()
70
+ uuid_string = str(test_uuid)
71
+
72
+ result = uuid7_to_datetime(uuid_string)
73
+ assert isinstance(result, datetime)
74
+ assert result.tzinfo == timezone.utc
75
+
76
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
77
+ def test_uuid7_to_datetime_precision(self):
78
+ """Test that the conversion maintains reasonable precision."""
79
+ test_uuid = frozen_uuid7()
80
+ result = uuid7_to_datetime(test_uuid)
81
+
82
+ assert isinstance(result, datetime)
83
+ assert result.tzinfo == timezone.utc
84
+ assert result.isoformat() == "2024-01-15T12:30:45.123000+00:00"
85
+
86
+ @freezegun.freeze_time("1970-01-01 00:00:00.000000")
87
+ def test_uuid7_to_datetime_epoch_boundary(self):
88
+ """Test conversion with UUID7 at epoch boundary."""
89
+ test_uuid = frozen_uuid7()
90
+ result = uuid7_to_datetime(test_uuid)
91
+
92
+ assert isinstance(result, datetime)
93
+ assert result.tzinfo == timezone.utc
94
+ assert result.isoformat() == "1970-01-01T00:00:00+00:00"
95
+
96
+ @freezegun.freeze_time("2100-01-01 00:00:00.000000")
97
+ def test_uuid7_to_datetime_future_timestamp(self):
98
+ """Test conversion with future timestamp."""
99
+ test_uuid = frozen_uuid7()
100
+ result = uuid7_to_datetime(test_uuid)
101
+
102
+ assert isinstance(result, datetime)
103
+ assert result.tzinfo == timezone.utc
104
+ assert result.isoformat() == "2100-01-01T00:00:00+00:00"
105
+
106
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
107
+ def test_uuid7_to_datetime_consistency(self):
108
+ """Test that multiple calls with the same UUID return the same result."""
109
+ test_uuid = frozen_uuid7()
110
+
111
+ result1 = uuid7_to_datetime(test_uuid)
112
+ result2 = uuid7_to_datetime(test_uuid)
113
+
114
+ assert result1.isoformat() == result2.isoformat()
115
+ assert result1 == result2
116
+
117
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
118
+ def test_uuid7_to_datetime_different_input_types_same_result(self):
119
+ """Test that different input types representing the same UUID return the same result."""
120
+ test_uuid = frozen_uuid7()
121
+ uuid_string = str(test_uuid)
122
+ stdlib_uuid = StdUUID(uuid_string)
123
+
124
+ result_from_uuid_utils = uuid7_to_datetime(test_uuid)
125
+ result_from_string = uuid7_to_datetime(uuid_string)
126
+ result_from_stdlib = uuid7_to_datetime(stdlib_uuid)
127
+
128
+ # All results should be identical
129
+ assert result_from_uuid_utils == result_from_string == result_from_stdlib
130
+
131
+ def test_uuid7_to_datetime_invalid_input_type(self):
132
+ """Test that invalid input types raise appropriate errors."""
133
+ with pytest.raises(TypeError):
134
+ uuid7_to_datetime(123) # type: ignore
135
+
136
+ with pytest.raises(TypeError):
137
+ uuid7_to_datetime(123.45) # type: ignore
138
+
139
+ with pytest.raises(TypeError):
140
+ uuid7_to_datetime([]) # type: ignore
141
+
142
+ with pytest.raises(TypeError):
143
+ uuid7_to_datetime({}) # type: ignore
144
+
145
+ def test_uuid7_to_datetime_invalid_uuid_string(self):
146
+ """Test that invalid UUID strings raise appropriate errors."""
147
+ with pytest.raises(ValueError):
148
+ uuid7_to_datetime("not-a-uuid")
149
+
150
+ with pytest.raises(ValueError):
151
+ uuid7_to_datetime("12345")
152
+
153
+ with pytest.raises(ValueError):
154
+ uuid7_to_datetime("")
155
+
156
+ def test_uuid7_to_datetime_non_uuid7_uuid(self):
157
+ """Test behavior with non-UUID7 UUIDs."""
158
+ uuid4 = StdUUID("550e8400-e29b-41d4-a716-446655440000")
159
+
160
+ with pytest.raises(ValueError, match="is not a UUIDv7"):
161
+ uuid7_to_datetime(uuid4)
162
+
163
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
164
+ def test_uuid7_to_datetime_timezone_awareness(self):
165
+ """Test that the returned datetime is always timezone-aware and in UTC."""
166
+ test_uuid = frozen_uuid7()
167
+ result = uuid7_to_datetime(test_uuid)
168
+
169
+ assert result.tzinfo is not None
170
+
171
+ assert result.tzinfo == timezone.utc
172
+
173
+ assert result.tzinfo.utcoffset(result) is not None
174
+
175
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
176
+ def test_uuid7_to_datetime_millisecond_precision(self):
177
+ """Test that the conversion handles millisecond precision correctly."""
178
+ uuids = [frozen_uuid7() for _ in range(5)]
179
+ results = [uuid7_to_datetime(uuid) for uuid in uuids]
180
+
181
+ for result in results:
182
+ assert isinstance(result, datetime)
183
+ assert result.tzinfo == timezone.utc
184
+
185
+ timestamps = [r.timestamp() for r in results]
186
+ assert timestamps == sorted(timestamps)
187
+
188
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
189
+ def test_uuid7_to_datetime_timestamp_property(self):
190
+ """Test that the function correctly uses the timestamp property."""
191
+ test_uuid = frozen_uuid7()
192
+ result = uuid7_to_datetime(test_uuid)
193
+
194
+ expected_timestamp = test_uuid.timestamp / 1000.0
195
+ actual_timestamp = result.timestamp()
196
+
197
+ assert abs(actual_timestamp - expected_timestamp) < 0.001
198
+
199
+ @freezegun.freeze_time("2024-01-15 12:30:45.123456")
200
+ def test_uuid7_to_datetime_deterministic_with_freeze(self):
201
+ """Test that the function works deterministically with freezegun."""
202
+ with freezegun.freeze_time("2024-01-15 12:30:45.123000"):
203
+ uuid1 = frozen_uuid7()
204
+ result1 = uuid7_to_datetime(uuid1)
205
+
206
+ with freezegun.freeze_time("2024-01-15 12:30:45.123000"):
207
+ uuid2 = frozen_uuid7()
208
+ result2 = uuid7_to_datetime(uuid2)
209
+
210
+ time_diff = abs((result1 - result2).total_seconds())
211
+ assert time_diff < 1.0
212
+
213
+ @freezegun.freeze_time("1970-01-01 00:00:00")
214
+ def test_uuid7_to_datetime_edge_case_zero_timestamp(self):
215
+ """Test edge case with very small timestamp values."""
216
+ test_uuid = frozen_uuid7()
217
+ result = uuid7_to_datetime(test_uuid)
218
+
219
+ assert isinstance(result, datetime)
220
+ assert result.tzinfo == timezone.utc
221
+ assert result.isoformat() == "1970-01-01T00:00:00+00:00"
File without changes
File without changes