diracx-db 0.0.1a39__tar.gz → 0.0.1a41__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 (56) hide show
  1. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/PKG-INFO +1 -1
  2. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/job_logging/db.py +14 -40
  3. diracx_db-0.0.1a41/src/diracx/db/sql/job_logging/schema.py +64 -0
  4. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/PKG-INFO +1 -1
  5. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/jobs/test_job_logging_db.py +19 -13
  6. diracx_db-0.0.1a39/src/diracx/db/sql/job_logging/schema.py +0 -29
  7. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/README.md +0 -0
  8. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/pyproject.toml +0 -0
  9. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/setup.cfg +0 -0
  10. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/__init__.py +0 -0
  11. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/__main__.py +0 -0
  12. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/exceptions.py +0 -0
  13. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/os/__init__.py +0 -0
  14. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/os/job_parameters.py +0 -0
  15. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/os/utils.py +0 -0
  16. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/py.typed +0 -0
  17. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/__init__.py +0 -0
  18. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/auth/__init__.py +0 -0
  19. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/auth/db.py +0 -0
  20. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/auth/schema.py +0 -0
  21. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/dummy/__init__.py +0 -0
  22. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/dummy/db.py +0 -0
  23. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/dummy/schema.py +0 -0
  24. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/job/__init__.py +0 -0
  25. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/job/db.py +0 -0
  26. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/job/schema.py +0 -0
  27. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/job_logging/__init__.py +0 -0
  28. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/pilot_agents/__init__.py +0 -0
  29. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/pilot_agents/db.py +0 -0
  30. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/pilot_agents/schema.py +0 -0
  31. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/sandbox_metadata/__init__.py +0 -0
  32. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/sandbox_metadata/db.py +0 -0
  33. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/sandbox_metadata/schema.py +0 -0
  34. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/task_queue/__init__.py +0 -0
  35. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/task_queue/db.py +0 -0
  36. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/task_queue/schema.py +0 -0
  37. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/utils/__init__.py +0 -0
  38. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/utils/base.py +0 -0
  39. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/utils/functions.py +0 -0
  40. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx/db/sql/utils/types.py +0 -0
  41. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/SOURCES.txt +0 -0
  42. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/dependency_links.txt +0 -0
  43. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/entry_points.txt +0 -0
  44. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/requires.txt +0 -0
  45. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/src/diracx_db.egg-info/top_level.txt +0 -0
  46. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/auth/test_authorization_flow.py +0 -0
  47. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/auth/test_device_flow.py +0 -0
  48. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/auth/test_refresh_token.py +0 -0
  49. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/jobs/test_job_db.py +0 -0
  50. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/jobs/test_sandbox_metadata.py +0 -0
  51. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/opensearch/test_connection.py +0 -0
  52. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/opensearch/test_index_template.py +0 -0
  53. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/opensearch/test_search.py +0 -0
  54. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/pilot_agents/__init__.py +0 -0
  55. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/pilot_agents/test_pilot_agents_db.py +0 -0
  56. {diracx_db-0.0.1a39 → diracx_db-0.0.1a41}/tests/test_dummy_db.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diracx-db
3
- Version: 0.0.1a39
3
+ Version: 0.0.1a41
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -1,28 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- import time
4
- from datetime import timezone
5
- from typing import TYPE_CHECKING
3
+ from collections import defaultdict
4
+ from datetime import datetime, timezone
5
+ from typing import Iterable
6
6
 
7
7
  from sqlalchemy import delete, func, select
8
8
 
9
- if TYPE_CHECKING:
10
- pass
11
-
12
- from collections import defaultdict
13
-
14
- from diracx.core.models import (
15
- JobLoggingRecord,
16
- JobStatusReturn,
17
- )
9
+ from diracx.core.models import JobLoggingRecord, JobStatusReturn
18
10
 
19
11
  from ..utils import BaseSQLDB
20
- from .schema import (
21
- JobLoggingDBBase,
22
- LoggingInfo,
23
- )
24
-
25
- MAGIC_EPOC_NUMBER = 1270000000
12
+ from .schema import JobLoggingDBBase, LoggingInfo
26
13
 
27
14
 
28
15
  class JobLoggingDB(BaseSQLDB):
@@ -35,14 +22,6 @@ class JobLoggingDB(BaseSQLDB):
35
22
  records: list[JobLoggingRecord],
36
23
  ):
37
24
  """Bulk insert entries to the JobLoggingDB table."""
38
-
39
- def get_epoc(date):
40
- return (
41
- time.mktime(date.timetuple())
42
- + date.microsecond / 1000000.0
43
- - MAGIC_EPOC_NUMBER
44
- )
45
-
46
25
  # First, fetch the maximum SeqNums for the given job_ids
47
26
  seqnum_stmt = (
48
27
  select(
@@ -70,7 +49,7 @@ class JobLoggingDB(BaseSQLDB):
70
49
  "MinorStatus": record.minor_status,
71
50
  "ApplicationStatus": record.application_status[:255],
72
51
  "StatusTime": record.date,
73
- "StatusTimeOrder": get_epoc(record.date),
52
+ "StatusTimeOrder": record.date,
74
53
  "StatusSource": record.source[:32],
75
54
  }
76
55
  )
@@ -159,21 +138,16 @@ class JobLoggingDB(BaseSQLDB):
159
138
  stmt = delete(LoggingInfo).where(LoggingInfo.job_id.in_(job_ids))
160
139
  await self.conn.execute(stmt)
161
140
 
162
- async def get_wms_time_stamps(self, job_ids):
141
+ async def get_wms_time_stamps(
142
+ self, job_ids: Iterable[int]
143
+ ) -> dict[int, dict[str, datetime]]:
163
144
  """Get TimeStamps for job MajorState transitions for multiple jobs at once
164
145
  return a {JobID: {State:timestamp}} dictionary.
165
146
  """
166
- result = defaultdict(dict)
147
+ result: defaultdict[int, dict[str, datetime]] = defaultdict(dict)
167
148
  stmt = select(
168
- LoggingInfo.job_id,
169
- LoggingInfo.status,
170
- LoggingInfo.status_time_order,
149
+ LoggingInfo.job_id, LoggingInfo.status, LoggingInfo.status_time_order
171
150
  ).where(LoggingInfo.job_id.in_(job_ids))
172
- rows = await self.conn.execute(stmt)
173
- if not rows.rowcount:
174
- return {}
175
-
176
- for job_id, event, etime in rows:
177
- result[job_id][event] = str(etime + MAGIC_EPOC_NUMBER)
178
-
179
- return result
151
+ for job_id, event, etime in await self.conn.execute(stmt):
152
+ result[job_id][event] = etime
153
+ return dict(result)
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import UTC, datetime
4
+
5
+ from sqlalchemy import Integer, Numeric, PrimaryKeyConstraint, String, TypeDecorator
6
+ from sqlalchemy.orm import declarative_base
7
+
8
+ from ..utils import Column, DateNowColumn
9
+
10
+ JobLoggingDBBase = declarative_base()
11
+
12
+
13
+ class MagicEpochDateTime(TypeDecorator):
14
+ """A SQLAlchemy type that stores a datetime as a numeric value representing the
15
+ seconds elapsed since MAGIC_EPOC_NUMBER. The underlying column is defined as
16
+ Numeric(12,3) which provides a fixed-precision representation.
17
+ """
18
+
19
+ impl = Numeric(12, 3)
20
+ cache_ok = True
21
+
22
+ MAGIC_EPOC_NUMBER = 1270000000
23
+
24
+ def process_bind_param(self, value, dialect):
25
+ """Convert a Python datetime to a numeric value: (timestamp - MAGIC_EPOC_NUMBER).
26
+ The result is rounded to three decimal places.
27
+ """
28
+ if value is None:
29
+ return None
30
+ if isinstance(value, datetime):
31
+ # Convert datetime to seconds since the Unix epoch, subtract our magic epoch,
32
+ # and round to three decimal places.
33
+ epoch_seconds = (
34
+ value.replace(tzinfo=UTC).timestamp() - self.MAGIC_EPOC_NUMBER
35
+ )
36
+ return round(epoch_seconds, 3)
37
+ raise ValueError(
38
+ "Expected a datetime object for MagicEpochDateTime bind parameter."
39
+ )
40
+
41
+ def process_result_value(self, value, dialect):
42
+ """Convert the numeric database value back into a Python datetime by reversing the
43
+ stored difference (adding MAGIC_EPOC_NUMBER).
44
+ """
45
+ if value is None:
46
+ return None
47
+ # Carefully convert from Decimal to datetime to avoid loosing precision
48
+ value += self.MAGIC_EPOC_NUMBER
49
+ value_int = int(value)
50
+ result = datetime.fromtimestamp(value_int, tz=UTC)
51
+ return result.replace(microsecond=int((value - value_int) * 1_000_000))
52
+
53
+
54
+ class LoggingInfo(JobLoggingDBBase):
55
+ __tablename__ = "LoggingInfo"
56
+ job_id = Column("JobID", Integer)
57
+ seq_num = Column("SeqNum", Integer)
58
+ status = Column("Status", String(32), default="")
59
+ minor_status = Column("MinorStatus", String(128), default="")
60
+ application_status = Column("ApplicationStatus", String(255), default="")
61
+ status_time = DateNowColumn("StatusTime")
62
+ status_time_order = Column("StatusTimeOrder", MagicEpochDateTime, default=0)
63
+ source = Column("StatusSource", String(32), default="Unknown")
64
+ __table_args__ = (PrimaryKeyConstraint("JobID", "SeqNum"),)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diracx-db
3
- Version: 0.0.1a39
3
+ Version: 0.0.1a41
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime, timezone
3
+ from datetime import datetime, timedelta, timezone
4
4
 
5
5
  import pytest
6
6
 
@@ -18,11 +18,11 @@ async def job_logging_db():
18
18
 
19
19
 
20
20
  async def test_insert_records(job_logging_db: JobLoggingDB):
21
- async with job_logging_db as job_logging_db:
22
- # Arrange
23
- date = datetime.now(timezone.utc)
21
+ date_1 = datetime.now(timezone.utc) - timedelta(minutes=1)
22
+ date_2 = datetime.now(timezone.utc) - timedelta(seconds=1)
23
+ date_3 = datetime.now(timezone.utc)
24
24
 
25
- # Act
25
+ async with job_logging_db as job_logging_db:
26
26
  records = []
27
27
  for i in range(50):
28
28
  records.append(
@@ -31,7 +31,7 @@ async def test_insert_records(job_logging_db: JobLoggingDB):
31
31
  status=JobStatus.RECEIVED,
32
32
  minor_status="received_minor_status",
33
33
  application_status="application_status",
34
- date=date,
34
+ date=date_1,
35
35
  source="pytest",
36
36
  )
37
37
  )
@@ -41,7 +41,7 @@ async def test_insert_records(job_logging_db: JobLoggingDB):
41
41
  status=JobStatus.SUBMITTING,
42
42
  minor_status="submitted_minor_status",
43
43
  application_status="application_status",
44
- date=date,
44
+ date=date_2,
45
45
  source="pytest",
46
46
  )
47
47
  )
@@ -51,13 +51,13 @@ async def test_insert_records(job_logging_db: JobLoggingDB):
51
51
  status=JobStatus.RUNNING,
52
52
  minor_status="running_minor_status",
53
53
  application_status="application_status",
54
- date=date,
54
+ date=date_3,
55
55
  source="pytest",
56
56
  )
57
57
  )
58
58
  await job_logging_db.insert_records(records)
59
59
 
60
- # Assert
60
+ async with job_logging_db as job_logging_db:
61
61
  res = await job_logging_db.get_records([i for i in range(50)])
62
62
 
63
63
  assert len(res) == 50
@@ -65,26 +65,32 @@ async def test_insert_records(job_logging_db: JobLoggingDB):
65
65
  assert res[0][0].Status == JobStatus.RECEIVED.value
66
66
  assert res[0][0].MinorStatus == "received_minor_status"
67
67
  assert res[0][0].ApplicationStatus == "application_status"
68
- assert res[0][0].StatusTime == date
68
+ assert res[0][0].StatusTime == date_1
69
69
  assert res[0][0].Source == "pytest"
70
70
 
71
71
  # Check the first job - second record
72
72
  assert res[0][1].Status == JobStatus.SUBMITTING.value
73
73
  assert res[0][1].MinorStatus == "submitted_minor_status"
74
74
  assert res[0][1].ApplicationStatus == "application_status"
75
- assert res[0][1].StatusTime == date
75
+ assert res[0][1].StatusTime == date_2
76
76
  assert res[0][1].Source == "pytest"
77
77
 
78
78
  # Check the first job - third record
79
79
  assert res[0][2].Status == JobStatus.RUNNING.value
80
80
  assert res[0][2].MinorStatus == "running_minor_status"
81
81
  assert res[0][2].ApplicationStatus == "application_status"
82
- assert res[0][2].StatusTime == date
82
+ assert res[0][2].StatusTime == date_3
83
83
  assert res[0][2].Source == "pytest"
84
84
 
85
85
  # Check the last job - third record
86
86
  assert res[49][2].Status == JobStatus.RUNNING.value
87
87
  assert res[49][2].MinorStatus == "running_minor_status"
88
88
  assert res[49][2].ApplicationStatus == "application_status"
89
- assert res[49][2].StatusTime == date
89
+ assert res[49][2].StatusTime == date_3
90
90
  assert res[49][2].Source == "pytest"
91
+
92
+ async with job_logging_db as job_logging_db:
93
+ res = await job_logging_db.get_wms_time_stamps([1])
94
+ assert abs(res[1]["Received"] - date_1) < timedelta(microseconds=1000)
95
+ assert abs(res[1]["Submitting"] - date_2) < timedelta(microseconds=1000)
96
+ assert abs(res[1]["Running"] - date_3) < timedelta(microseconds=1000)
@@ -1,29 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from sqlalchemy import (
4
- Integer,
5
- Numeric,
6
- PrimaryKeyConstraint,
7
- String,
8
- )
9
- from sqlalchemy.orm import declarative_base
10
-
11
- from ..utils import Column, DateNowColumn
12
-
13
- JobLoggingDBBase = declarative_base()
14
-
15
-
16
- class LoggingInfo(JobLoggingDBBase):
17
- __tablename__ = "LoggingInfo"
18
- job_id = Column("JobID", Integer)
19
- seq_num = Column("SeqNum", Integer)
20
- status = Column("Status", String(32), default="")
21
- minor_status = Column("MinorStatus", String(128), default="")
22
- application_status = Column("ApplicationStatus", String(255), default="")
23
- status_time = DateNowColumn("StatusTime")
24
- # TODO: Check that this corresponds to the DOUBLE(12,3) type in MySQL
25
- status_time_order = Column(
26
- "StatusTimeOrder", Numeric(precision=12, scale=3), default=0
27
- )
28
- source = Column("StatusSource", String(32), default="Unknown")
29
- __table_args__ = (PrimaryKeyConstraint("JobID", "SeqNum"),)
File without changes
File without changes