diracx-db 0.0.1a39__tar.gz → 0.0.1a40__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.
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/PKG-INFO +1 -1
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/job_logging/db.py +14 -40
- diracx_db-0.0.1a40/src/diracx/db/sql/job_logging/schema.py +64 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/PKG-INFO +1 -1
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/jobs/test_job_logging_db.py +19 -13
- diracx_db-0.0.1a39/src/diracx/db/sql/job_logging/schema.py +0 -29
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/README.md +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/pyproject.toml +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/setup.cfg +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/__main__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/exceptions.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/os/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/os/job_parameters.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/os/utils.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/py.typed +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/auth/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/auth/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/auth/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/dummy/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/dummy/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/dummy/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/job/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/job/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/job/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/job_logging/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/pilot_agents/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/pilot_agents/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/pilot_agents/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/sandbox_metadata/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/sandbox_metadata/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/sandbox_metadata/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/task_queue/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/task_queue/db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/task_queue/schema.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/utils/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/utils/base.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/utils/functions.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx/db/sql/utils/types.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/SOURCES.txt +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/dependency_links.txt +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/entry_points.txt +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/requires.txt +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/src/diracx_db.egg-info/top_level.txt +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/auth/test_authorization_flow.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/auth/test_device_flow.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/auth/test_refresh_token.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/jobs/test_job_db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/jobs/test_sandbox_metadata.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/opensearch/test_connection.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/opensearch/test_index_template.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/opensearch/test_search.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/pilot_agents/__init__.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/pilot_agents/test_pilot_agents_db.py +0 -0
- {diracx_db-0.0.1a39 → diracx_db-0.0.1a40}/tests/test_dummy_db.py +0 -0
@@ -1,28 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
4
|
-
from datetime import timezone
|
5
|
-
from typing import
|
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
|
-
|
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":
|
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(
|
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
|
-
|
173
|
-
|
174
|
-
|
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
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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=
|
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=
|
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=
|
54
|
+
date=date_3,
|
55
55
|
source="pytest",
|
56
56
|
)
|
57
57
|
)
|
58
58
|
await job_logging_db.insert_records(records)
|
59
59
|
|
60
|
-
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|