From 81d1632485656855db70760467c3e0e3bd6cac7e Mon Sep 17 00:00:00 2001 From: Eman Ali Date: Wed, 28 May 2025 18:17:55 +1000 Subject: [PATCH 01/37] DM-51979 don't group campaigns by production in campaigns page show config dicts in collapsible panels to save space display config dicts in tables for readability campaign last updated is the newest of creation and modification dates. display it in campaign card change how step name is retrieved and displayed in the step card tidy campaign card layout first attempt at adding a new entry to collections using htmx, just adding the new form inputs, not there yet make saving new entries work better allow adding new collections, editing the values of existing ones not the keys some tweaks and ui improvements to edit collections dialog allow adding new entries in edit data dialog allow adding new entries in edit child config dialog check for collection prefix based on element type change how WMS task progress is calculated add missing import add token link lint and mypy remove unused template --- src/lsst/cmservice/web_app/app.py | 147 ++++++++++++++++++ .../cmservice/web_app/pages/group_details.py | 25 +-- .../cmservice/web_app/pages/job_details.py | 25 +-- .../cmservice/web_app/static/css/output.css | 71 +++++++++ .../web_app/templates/pages/base.html | 10 +- .../web_app/templates/pages/campaigns.html | 1 + .../web_app/templates/pages/step_details.html | 5 +- .../partials/child_config_collapsible.html | 2 +- .../templates/partials/collections.html | 2 + .../partials/collections_collapsible.html | 1 - .../partials/edit_child_config_form.html | 44 ++++++ .../edit_child_config_form_inputs.html | 17 +- .../partials/edit_child_config_modal.html | 20 +-- .../partials/edit_child_config_response.html | 2 + .../partials/edit_collections_form.html | 46 ++++++ .../edit_collections_form_inputs.html | 28 +++- .../partials/edit_collections_modal.html | 20 +-- .../partials/edit_collections_response.html | 3 + .../partials/edit_data_dict_form.html | 42 +++++ .../partials/edit_data_dict_form_inputs.html | 19 ++- .../partials/edit_data_dict_modal.html | 20 +-- .../partials/edit_data_dict_response.html | 3 + .../web_app/templates/partials/new_field.html | 23 +++ 23 files changed, 476 insertions(+), 100 deletions(-) create mode 100644 src/lsst/cmservice/web_app/templates/partials/edit_child_config_form.html create mode 100644 src/lsst/cmservice/web_app/templates/partials/edit_collections_form.html create mode 100644 src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form.html create mode 100644 src/lsst/cmservice/web_app/templates/partials/new_field.html diff --git a/src/lsst/cmservice/web_app/app.py b/src/lsst/cmservice/web_app/app.py index 485332a22..e36a8cd56 100644 --- a/src/lsst/cmservice/web_app/app.py +++ b/src/lsst/cmservice/web_app/app.py @@ -414,3 +414,150 @@ async def update_element_data_dict( except Exception as e: traceback.print_tb(e.__traceback__) raise HTTPException(status_code=500, detail=f"Error updating data dict: {e}") + + +@web_app.post("/collections/{element_id}/{element_type}/update") +async def update_collections_htmx( + session: Annotated[async_scoped_session, Depends(db_session_dependency)], + element_id: int, + element_type: int, + request: Request, + keys: Annotated[list[str], Form()], + values: Annotated[list[str], Form()], + new_keys: Annotated[list[str], Form()] = [], + new_values: Annotated[list[str], Form()] = [], +) -> HTMLResponse: + element = await get_element(session, element_id, element_type) + if element is None: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + detail=f"Error updating data dict: {LevelEnum(element_type).name} {element_id} Not Found!!", + ) + + collection_dict = {} + for key, value in zip(keys, values): + key = key.strip() + if key: + collection_dict[key] = value + + for key, value in zip(new_keys, new_values): + key = key.strip() + if key: + collection_dict[key] = value + + updated_element = await update_collections(session=session, element=element, collections=collection_dict) + + return templates.TemplateResponse( + name="partials/edit_collections_response.html", + request=request, + context={ + "element": updated_element, + }, + ) + + +@web_app.post("/collections/{element_id}/{element_type}/add-field") +@web_app.post("/data-dict/{element_id}/{element_type}/add-field") +@web_app.post("/child-config/{element_id}/{element_type}/add-field") +async def add_collection_field( + element_id: int, + element_type: int, + request: Request, +) -> HTMLResponse: + import uuid + + field_id = str(uuid.uuid4())[:8] + + return templates.TemplateResponse( + name="partials/new_field.html", + request=request, + context={"element_id": element_id, "field_id": field_id, "element_type": element_type}, + ) + + +@web_app.post("/collections/{element_id}/{element_type}/remove-new-field/{field_id}") +@web_app.post("/data-dict/{element_id}/{element_type}/remove-new-field/{field_id}") +@web_app.post("/child-config/{element_id}/{element_type}/remove-new-field/{field_id}") +async def remove_new_field(element_id: int, element_type: int, field_id: str) -> str: + # This just returns empty content to remove the field from DOM + return "" + + +@web_app.post("/data-dict/{element_id}/{element_type}/update") +async def update_data_dict_htmx( + session: Annotated[async_scoped_session, Depends(db_session_dependency)], + element_id: int, + element_type: int, + request: Request, + keys: Annotated[list[str], Form()] = [], + values: Annotated[list[str], Form()] = [], + new_keys: Annotated[list[str], Form()] = [], + new_values: Annotated[list[str], Form()] = [], +) -> HTMLResponse: + element = await get_element(session, element_id, element_type) + if element is None: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + detail=f"Error updating data dict: {LevelEnum(element_type).name} {element_id} Not Found!!", + ) + + data_dict = {} + for key, value in zip(keys, values): + key = key.strip() + if key: + data_dict[key] = value + + for key, value in zip(new_keys, new_values): + key = key.strip() + if key: + data_dict[key] = value + + updated_element = await update_data_dict(session=session, element=element, data_dict=data_dict) + + return templates.TemplateResponse( + name="partials/edit_data_dict_response.html", + request=request, + context={ + "element": updated_element, + }, + ) + + +@web_app.post("/child-config/{element_id}/{element_type}/update") +async def update_child_config_htmx( + session: Annotated[async_scoped_session, Depends(db_session_dependency)], + element_id: int, + element_type: int, + request: Request, + keys: Annotated[list[str], Form()] = [], + values: Annotated[list[str], Form()] = [], + new_keys: Annotated[list[str], Form()] = [], + new_values: Annotated[list[str], Form()] = [], +) -> HTMLResponse: + element = await get_element(session, element_id, element_type) + if element is None: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + detail=f"Error updating child config: {LevelEnum(element_type).name} {element_id} Not Found!!", + ) + + child_config = {} + for key, value in zip(keys, values): + key = key.strip() + if key: + child_config[key] = value + + for key, value in zip(new_keys, new_values): + key = key.strip() + if key: + child_config[key] = value + + updated_element = await update_child_config(session=session, element=element, child_config=child_config) + + return templates.TemplateResponse( + name="partials/edit_child_config_response.html", + request=request, + context={ + "element": updated_element, + }, + ) diff --git a/src/lsst/cmservice/web_app/pages/group_details.py b/src/lsst/cmservice/web_app/pages/group_details.py index 13a97f1b8..6c54b7c67 100644 --- a/src/lsst/cmservice/web_app/pages/group_details.py +++ b/src/lsst/cmservice/web_app/pages/group_details.py @@ -42,16 +42,21 @@ async def get_group_by_id( if len(wms_report) > 0: aggregated_report_dict["succeeded"] = sum(task["n_succeeded"] for task in wms_report) - aggregated_report_dict["failed"] = wms_report[-1]["n_failed"] - aggregated_report_dict["running"] = wms_report[-1]["n_running"] - aggregated_report_dict["pending"] = wms_report[-1]["n_pending"] + wms_report[-1]["n_ready"] - aggregated_report_dict["other"] = ( - wms_report[-1]["n_unknown"] - + wms_report[-1]["n_misfit"] - + wms_report[-1]["n_unready"] - + wms_report[-1]["n_deleted"] - + wms_report[-1]["n_pruned"] - + wms_report[-1]["n_held"] + aggregated_report_dict["failed"] = sum(task["n_failed"] for task in wms_report) + aggregated_report_dict["running"] = sum(task["n_running"] for task in wms_report) + aggregated_report_dict["pending"] = sum( + (task["n_pending"] + task["n_ready"]) for task in wms_report + ) + aggregated_report_dict["other"] = sum( + ( + task["n_unknown"] + + task["n_misfit"] + + task["n_unready"] + + task["n_deleted"] + + task["n_pruned"] + + task["n_held"] + ) + for task in wms_report ) aggregated_report_dict["expected"] = sum(aggregated_report_dict.values()) diff --git a/src/lsst/cmservice/web_app/pages/job_details.py b/src/lsst/cmservice/web_app/pages/job_details.py index a55b88689..c42619a19 100644 --- a/src/lsst/cmservice/web_app/pages/job_details.py +++ b/src/lsst/cmservice/web_app/pages/job_details.py @@ -35,16 +35,21 @@ async def get_job_by_id( if len(wms_report) > 0: aggregated_report_dict["succeeded"] = sum(task["n_succeeded"] for task in wms_report) - aggregated_report_dict["failed"] = wms_report[-1]["n_failed"] - aggregated_report_dict["running"] = wms_report[-1]["n_running"] - aggregated_report_dict["pending"] = wms_report[-1]["n_pending"] + wms_report[-1]["n_ready"] - aggregated_report_dict["other"] = ( - wms_report[-1]["n_unknown"] - + wms_report[-1]["n_misfit"] - + wms_report[-1]["n_unready"] - + wms_report[-1]["n_deleted"] - + wms_report[-1]["n_pruned"] - + wms_report[-1]["n_held"] + aggregated_report_dict["failed"] = sum(task["n_failed"] for task in wms_report) + aggregated_report_dict["running"] = sum(task["n_running"] for task in wms_report) + aggregated_report_dict["pending"] = sum( + task["n_pending"] + task["n_ready"] for task in wms_report + ) + aggregated_report_dict["other"] = sum( + ( + task["n_unknown"] + + task["n_misfit"] + + task["n_unready"] + + task["n_deleted"] + + task["n_pruned"] + + task["n_held"] + ) + for task in wms_report ) aggregated_report_dict["expected"] = sum(aggregated_report_dict.values()) diff --git a/src/lsst/cmservice/web_app/static/css/output.css b/src/lsst/cmservice/web_app/static/css/output.css index d7031ca3d..d9b782112 100644 --- a/src/lsst/cmservice/web_app/static/css/output.css +++ b/src/lsst/cmservice/web_app/static/css/output.css @@ -708,10 +708,18 @@ video { margin-top: -1px; } +.mb-2 { + margin-bottom: 0.5rem; +} + .mb-3 { margin-bottom: 0.75rem; } +.mb-4 { + margin-bottom: 1rem; +} + .ml-2 { margin-left: 0.5rem; } @@ -799,6 +807,11 @@ video { height: 1.5rem; } +.size-4 { + width: 1rem; + height: 1rem; +} + .h-10 { height: 2.5rem; } @@ -989,6 +1002,10 @@ video { flex-direction: row; } +.flex-col-reverse { + flex-direction: column-reverse; +} + .flex-wrap { flex-wrap: wrap; } @@ -1013,6 +1030,10 @@ video { gap: 0.5rem; } +.gap-3 { + gap: 0.75rem; +} + .gap-5 { gap: 1.25rem; } @@ -1050,6 +1071,12 @@ video { margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); } +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + .divide-x > :not([hidden]) ~ :not([hidden]) { --tw-divide-x-reverse: 0; border-right-width: calc(1px * var(--tw-divide-x-reverse)); @@ -1180,6 +1207,11 @@ video { border-color: rgb(229 231 235 / var(--tw-border-opacity)); } +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + .border-gray-400 { --tw-border-opacity: 1; border-color: rgb(156 163 175 / var(--tw-border-opacity)); @@ -1243,6 +1275,11 @@ video { background-color: rgb(254 226 226 / var(--tw-bg-opacity)); } +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); +} + .bg-red-600 { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); @@ -1447,6 +1484,11 @@ video { line-height: 1.5rem; } +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + .text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -1870,6 +1912,11 @@ video { background-color: rgb(99 102 241 / var(--tw-bg-opacity)); } +.hover\:bg-red-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + .hover\:bg-teal-50:hover { --tw-bg-opacity: 1; background-color: rgb(240 253 250 / var(--tw-bg-opacity)); @@ -1880,10 +1927,24 @@ video { background-color: rgb(20 184 166 / var(--tw-bg-opacity)); } +.hover\:bg-teal-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(13 148 136 / var(--tw-bg-opacity)); +} + .hover\:font-bold:hover { font-weight: 700; } +.hover\:font-semibold:hover { + font-weight: 600; +} + +.hover\:text-gray-50:hover { + --tw-text-opacity: 1; + color: rgb(249 250 251 / var(--tw-text-opacity)); +} + .hover\:text-gray-500:hover { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity)); @@ -1940,6 +2001,16 @@ video { --tw-ring-inset: inset; } +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +.focus\:ring-gray-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity)); +} + .focus\:ring-indigo-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity)); diff --git a/src/lsst/cmservice/web_app/templates/pages/base.html b/src/lsst/cmservice/web_app/templates/pages/base.html index b3c06908e..168576d73 100644 --- a/src/lsst/cmservice/web_app/templates/pages/base.html +++ b/src/lsst/cmservice/web_app/templates/pages/base.html @@ -10,10 +10,18 @@
diff --git a/src/lsst/cmservice/web_app/templates/pages/campaigns.html b/src/lsst/cmservice/web_app/templates/pages/campaigns.html index 87d8b2e98..996e2f067 100644 --- a/src/lsst/cmservice/web_app/templates/pages/campaigns.html +++ b/src/lsst/cmservice/web_app/templates/pages/campaigns.html @@ -30,6 +30,7 @@

Campaigns

+
{% for campaign in all_campaigns %} {% include "partials/campaign_card.html" %} diff --git a/src/lsst/cmservice/web_app/templates/pages/step_details.html b/src/lsst/cmservice/web_app/templates/pages/step_details.html index c10cc4285..3a7ab5fe4 100644 --- a/src/lsst/cmservice/web_app/templates/pages/step_details.html +++ b/src/lsst/cmservice/web_app/templates/pages/step_details.html @@ -162,10 +162,7 @@ document.querySelector("#editDataBtn").addEventListener("click", () => { let step_status = {{step.org_status.value}}; // TODO: revisit status mapping between frontend and backend - if (Object.keys({{step.data}}).length === 0) { - errorMessage.innerText = "Data Config is empty!"; - errorModal.showModal(); - } else if(step_status < 2) { + if(step_status < 2) { document.querySelector("#edit-data-dict-modal").showModal(); } else { errorMessage.innerText = "Cannot update data for a running step!"; diff --git a/src/lsst/cmservice/web_app/templates/partials/child_config_collapsible.html b/src/lsst/cmservice/web_app/templates/partials/child_config_collapsible.html index 8b2d8b168..93557db56 100644 --- a/src/lsst/cmservice/web_app/templates/partials/child_config_collapsible.html +++ b/src/lsst/cmservice/web_app/templates/partials/child_config_collapsible.html @@ -28,7 +28,7 @@ -
+
{% include "partials/child_config.html" %}
diff --git a/src/lsst/cmservice/web_app/templates/partials/collections.html b/src/lsst/cmservice/web_app/templates/partials/collections.html index 5d9f1fd3c..30f5c169c 100644 --- a/src/lsst/cmservice/web_app/templates/partials/collections.html +++ b/src/lsst/cmservice/web_app/templates/partials/collections.html @@ -2,6 +2,7 @@ {% for key, value in element.collections|items %} + {% if key != "" and key.startswith("step_") %} + {% endif %} {% endfor %}
{{ key }} @@ -14,6 +15,7 @@ {% endif %}
diff --git a/src/lsst/cmservice/web_app/templates/partials/collections_collapsible.html b/src/lsst/cmservice/web_app/templates/partials/collections_collapsible.html index bc8083175..17f148064 100644 --- a/src/lsst/cmservice/web_app/templates/partials/collections_collapsible.html +++ b/src/lsst/cmservice/web_app/templates/partials/collections_collapsible.html @@ -28,7 +28,6 @@ -
{% include "partials/collections.html" %}
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form.html b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form.html new file mode 100644 index 000000000..233b3da0d --- /dev/null +++ b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form.html @@ -0,0 +1,44 @@ + +
+ +
+
+ {% include "partials/edit_child_config_form_inputs.html" %} +
+ + +
+ +
+ +
+
+ +
+ +
+ + + +
+
+
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form_inputs.html b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form_inputs.html index 57618acd3..6b9b5690c 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form_inputs.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_form_inputs.html @@ -1,12 +1,15 @@ {% for key, value in element.child_config|items %} -
-
+
+
+ + +
- +
{% endfor %} diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_modal.html b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_modal.html index 27d5dcc6d..257c29d89 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_modal.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_modal.html @@ -4,28 +4,14 @@ role="dialog" aria-modal="true" class="rounded-lg w-1/2"> -
-

Edit Child Config

-
-
- {% include "partials/edit_child_config_form_inputs.html" %} -
-
- - -
-
- +
+ {% include "partials/edit_child_config_form.html" %} +
-
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_response.html b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_response.html index a9b250381..090c41685 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_child_config_response.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_child_config_response.html @@ -3,3 +3,5 @@
{% include "partials/edit_child_config_form_inputs.html" %}
+
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_collections_form.html b/src/lsst/cmservice/web_app/templates/partials/edit_collections_form.html new file mode 100644 index 000000000..433b7a7f7 --- /dev/null +++ b/src/lsst/cmservice/web_app/templates/partials/edit_collections_form.html @@ -0,0 +1,46 @@ + +
+ +
+
+ {% include "partials/edit_collections_form_inputs.html" %} +
+ + +
+ +
+ +
+
+ +
+ +
+ + + +
+
+ + +
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_collections_form_inputs.html b/src/lsst/cmservice/web_app/templates/partials/edit_collections_form_inputs.html index 77ef93633..3604b5b28 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_collections_form_inputs.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_collections_form_inputs.html @@ -1,12 +1,26 @@ +{% set prefix = "" %} +{% if element.level == 2 %} + {% set prefix = "step_" %} +{% elif element.level == 3 %} + {% set prefix = "group_" %} +{% elif element.level == 4 %} + {% set prefix = "job_" %} +{% endif %} + {% for key, value in element.collections|items %} -
-
+{% if key != "" and prefix != "" and key.startswith(prefix) %} +
+
+ + +
- +
+{% endif %} {% endfor %} diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_collections_modal.html b/src/lsst/cmservice/web_app/templates/partials/edit_collections_modal.html index 5c065a270..c6908597c 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_collections_modal.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_collections_modal.html @@ -4,28 +4,14 @@ role="dialog" aria-modal="true" class="rounded-lg w-1/2"> -
-

Edit Collections

-
-
- {% include "partials/edit_collections_form_inputs.html" %} -
-
- - -
-
- +
+ {% include "partials/edit_collections_form.html" %} +
-
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_collections_response.html b/src/lsst/cmservice/web_app/templates/partials/edit_collections_response.html index 2f3bf73e3..c89467fb7 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_collections_response.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_collections_response.html @@ -3,3 +3,6 @@
{% include "partials/edit_collections_form_inputs.html" %}
+ +
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form.html b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form.html new file mode 100644 index 000000000..52cb2efe0 --- /dev/null +++ b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form.html @@ -0,0 +1,42 @@ +
+
+
+ {% include "partials/edit_data_dict_form_inputs.html" %} +
+ + +
+ +
+ +
+
+ +
+ +
+ + + +
+
+
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form_inputs.html b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form_inputs.html index 3a796faef..407855f25 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form_inputs.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_form_inputs.html @@ -1,12 +1,15 @@ {% for key, value in element.data|items %} -
-
-
- +
+
+ + +
+
+
{% endfor %} diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_modal.html b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_modal.html index ebb25765b..915bb74f8 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_modal.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_modal.html @@ -4,28 +4,14 @@ role="dialog" aria-modal="true" class="rounded-lg w-1/2"> -
-

Edit Data

-
-
- {% include "partials/edit_data_dict_form_inputs.html" %} -
-
- - -
-
- +
+ {% include "partials/edit_data_dict_form.html" %} +
-
diff --git a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_response.html b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_response.html index 82589559a..24038d33f 100644 --- a/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_response.html +++ b/src/lsst/cmservice/web_app/templates/partials/edit_data_dict_response.html @@ -3,3 +3,6 @@
{% include "partials/edit_data_dict_form_inputs.html" %}
+ +
+
diff --git a/src/lsst/cmservice/web_app/templates/partials/new_field.html b/src/lsst/cmservice/web_app/templates/partials/new_field.html new file mode 100644 index 000000000..cfb5739a9 --- /dev/null +++ b/src/lsst/cmservice/web_app/templates/partials/new_field.html @@ -0,0 +1,23 @@ +
+
+ +
+
+ + +
+
From 02d67abd0d9b9c0754c118c48fdd1209543e1151 Mon Sep 17 00:00:00 2001 From: Toby Jennings Date: Mon, 16 Jun 2025 09:32:16 -0500 Subject: [PATCH 02/37] feat(test): add testcontainers dependency --- pyproject.toml | 1 + uv.lock | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0a7c1f213..ba5858cb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ dev = [ "types-pyyaml>=6.0.12.20240917", "types-tabulate>=0.9.0.20240106", "respx>=0.22.0", + "testcontainers[postgres]>=4.11.0", ] [project.scripts] diff --git a/uv.lock b/uv.lock index f91f7381d..57a3aa0ca 100644 --- a/uv.lock +++ b/uv.lock @@ -479,6 +479,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + [[package]] name = "docopt" version = "0.6.2" @@ -929,6 +943,7 @@ dev = [ { name = "respx" }, { name = "ruff" }, { name = "sqlalchemy", extra = ["mypy"] }, + { name = "testcontainers" }, { name = "types-pyyaml" }, { name = "types-tabulate" }, ] @@ -984,6 +999,7 @@ dev = [ { name = "respx", specifier = ">=0.22.0" }, { name = "ruff", specifier = ">=0.8.0" }, { name = "sqlalchemy", extras = ["mypy"], specifier = ">=2.0.36" }, + { name = "testcontainers", extras = ["postgres"], specifier = ">=4.11.0" }, { name = "types-pyyaml", specifier = ">=6.0.12.20240917" }, { name = "types-tabulate", specifier = ">=0.9.0.20240106" }, ] @@ -1824,6 +1840,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284, upload-time = "2025-03-17T00:55:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748, upload-time = "2025-03-17T00:55:55.203Z" }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941, upload-time = "2025-03-17T00:55:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -2182,6 +2211,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "testcontainers" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docker" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/ea/5e89a6ddc294a081907189e3ec1e565694d76864a552163527f708d3ced1/testcontainers-4.11.0.tar.gz", hash = "sha256:48faa72d0ba8d141495d5aec08f8cd5d244ed77c1c4089a4ce1df7b5e12caaf3", size = 64426, upload-time = "2025-06-16T10:59:57.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/e8/c54f0624f998f9210bff3df84f58ea47120b58412c4623a53277df4d1e75/testcontainers-4.11.0-py3-none-any.whl", hash = "sha256:36f0e1165ce2b504b1e8b8c9779ca738d3baad57d402e35c22ccf01a371fda0d", size = 108597, upload-time = "2025-06-16T10:59:56.153Z" }, +] + [[package]] name = "text-unidecode" version = "1.3" From f0c8dbc75fd5be9de5336174074a138e796b3883 Mon Sep 17 00:00:00 2001 From: Toby Jennings Date: Thu, 12 Jun 2025 10:39:39 -0500 Subject: [PATCH 03/37] feat(python): Upgrade to python 3.12 - upgrade dependencies --- .github/workflows/ci.yaml | 10 +- .pre-commit-config.yaml | 2 +- .python-version | 2 +- Makefile | 1 + README.md | 3 +- alembic/README.md | 2 - docker/Dockerfile | 4 +- pyproject.toml | 37 ++-- uv.lock | 421 ++++++++------------------------------ 9 files changed, 120 insertions(+), 362 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fa4318e45..96d726f92 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Run pre-commit uses: pre-commit/action@v3.0.1 @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11"] + python-version: ["3.12"] needs: - rebase-checker steps: @@ -49,7 +49,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: "0.6.x" + version: "0.7.x" enable-cache: true python-version: ${{ matrix.python-version }} @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11"] + python-version: ["3.12"] needs: - lint - mypy @@ -75,7 +75,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: "0.6.x" + version: "0.7.x" enable-cache: true python-version: ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aecfc6aba..44c1c0ef0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.9.2 + rev: v0.11.13 hooks: - id: ruff - id: ruff diff --git a/.python-version b/.python-version index 2c0733315..e4fba2183 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 +3.12 diff --git a/Makefile b/Makefile index d6d5066b8..055bc1c80 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ update: update-deps init .PHONY: build build: export BUILDKIT_PROGRESS=plain +build: export COMPOSE_BAKE=true build: docker compose build cmservice docker compose build cmworker diff --git a/README.md b/README.md index 89c3d54db..422aca9d4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # cm-service +![Python](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Flsst-dm%2Fcm-service%2Frefs%2Fheads%2Fmain%2Fpyproject.toml) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) @@ -8,7 +9,7 @@ https://cm-service.lsst.io. ## Developer Quick Start -You can build and run `cm-service` on any system which has Python 3.11 or greater, `uv`, `make`, and Docker w/ the +You can build and run `cm-service` on any system which has Python 3.12 or greater, `uv`, `make`, and Docker w/ the Docker Compose V2 CLI plugin (this includes, in particular, recent MacOS with Docker Desktop). Proceed as follows: diff --git a/alembic/README.md b/alembic/README.md index 8e57ac78d..04bf03826 100644 --- a/alembic/README.md +++ b/alembic/README.md @@ -3,8 +3,6 @@ Database migrations and schema evolution are handled by `alembic`, a database tool that is part of the `sqlalchemy` toolkit ecosystem. -Alembic is included in the project's dependency graph via the Safir package. - ## Running Alembic The `alembic` tool establishes an execution environment via the `env.py` file which diff --git a/docker/Dockerfile b/docker/Dockerfile index d92b48eb2..bba03da78 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 -ARG PYTHON_VERSION="3.11" -ARG UV_VERSION="0.6" +ARG PYTHON_VERSION="3.12" +ARG UV_VERSION="0.7" ARG ASGI_PORT="8080" #============================================================================== diff --git a/pyproject.toml b/pyproject.toml index ba5858cb9..53d38a6d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ name = "lsst-cm-service" -description = "Rubin Observatory campaign management FastAPI service" +description = "Rubin Observatory Campaign Management Service" license = { file = "LICENSE" } readme = "README.md" keywords = ["rubin", "lsst"] @@ -11,13 +11,13 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: POSIX", "Typing :: Typed", ] -requires-python = ">=3.11,<3.13" +requires-python = ">=3.12,<3.13" dynamic = ["version"] dependencies = [ @@ -27,32 +27,33 @@ dependencies = [ "click==8.1.*", "fastapi==0.115.*", "greenlet==3.1.*", - "htcondor==24.0.6; sys_platform == 'linux'", + "htcondor==24.0.7; sys_platform == 'linux'", "jinja2==3.1.*", "numpy==2.1.*", "psycopg2-binary==2.9.*", - "pydantic==2.10.*", - "pydantic-settings==2.7.*", + "pydantic==2.11.*", + "pydantic-settings==2.9.*", "python-multipart==0.0.*", "rich==13.9.*", "structlog==24.4.*", "tabulate==0.9.*", "sqlalchemy[asyncio]==2.0.*", "safir[db]==7.0.*", - "uvicorn[standard]==0.32.*", + "uvicorn[standard]==0.34.*", "panda-client>=1.5.82", "httpx>=0.27.2", "networkx>=3.5", + "sqlmodel>=0.0.24", ] [dependency-groups] lsst = [ - "lsst-ctrl-bps>=29.2025.1500", - "lsst-ctrl-bps-htcondor>=29.2025.1500; sys_platform == 'linux'", - "lsst-ctrl-bps-panda>=29.2025.1500", - "lsst-daf-butler>=29.2025.1500", - "lsst-pipe-base>=29.2025.1500", - "lsst-utils>=29.2025.1500", + "lsst-ctrl-bps>=29.2025.2300", + "lsst-ctrl-bps-htcondor>=29.2025.2300; sys_platform == 'linux'", + "lsst-ctrl-bps-panda>=29.2025.2300", + "lsst-daf-butler>=29.2025.2300", + "lsst-pipe-base>=29.2025.2300", + "lsst-utils>=29.2025.2300", ] dev = [ @@ -60,8 +61,8 @@ dev = [ "asgi-lifespan>=2.1.0", "coverage[toml]>=7.6.7", "greenlet>=3.1.1", - "mypy>=1.13.0", - "pre-commit>=4.0.1", + "mypy>=1.16.0", + "pre-commit>=4.2.0", "pytest>=8.3.3", "pytest-asyncio>=0.24.0", "pytest-cov>=6.0.0", @@ -114,11 +115,7 @@ exclude_lines = [ "def __repr__", "if self.debug:", "if settings.DEBUG", - "raise AssertionError", "raise NotImplementedError", - "except Exception as msg", - "except KeyError as msg", - "except IntegrityError as msg", "if 0:", "if __name__ == .__main__.:", "if TYPE_CHECKING:", @@ -129,7 +126,7 @@ exclude = [ "__init__.py", ] line-length = 110 -target-version = "py311" +target-version = "py312" [tool.ruff.lint] ignore = [ diff --git a/uv.lock b/uv.lock index 57a3aa0ca..34dffae90 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.11, <3.13" +requires-python = "==3.12.*" [[package]] name = "aiokafka" @@ -13,12 +13,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/65/ca/42a962033e6a7926dcb789168bce81d0181ef4ddabce454d830b7e62370e/aiokafka-0.12.0.tar.gz", hash = "sha256:62423895b866f95b5ed8d88335295a37cc5403af64cb7cb0e234f88adc2dff94", size = 564955, upload-time = "2024-10-26T20:53:11.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/7b/8faf3ae26f43b2dcc66f45665bd243c9f736e71df04e45da5836bb7a7be4/aiokafka-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddc5308c43d48af883667e2f950a0a9739ce2c9bfe69a0b55dc234f58b1b42d6", size = 375692, upload-time = "2024-10-26T20:52:27.739Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6c/c1ce38a225dfa04078f29d8734f13e483146cc2e30dbf6d13b75c2aa0724/aiokafka-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff63689cafcd6dd642a15de75b7ae121071d6162cccba16d091bcb28b3886307", size = 372335, upload-time = "2024-10-26T20:52:29.955Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bc/c5d2315e2f04768f585e31e6bd0a1fb9ed054a54c124c17087fdff507a13/aiokafka-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24633931e05a9dc80555a2f845572b6845d2dcb1af12de27837b8602b1b8bc74", size = 1141449, upload-time = "2024-10-26T20:52:33.094Z" }, - { url = "https://files.pythonhosted.org/packages/dc/42/607caffc39b1fb2be288fa2c72e72b352872362699b6e7473189fee065b9/aiokafka-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b2436c7c69384d210e9169fbfe339d9f49dbdcfddd8d51c79b9877de545e33", size = 1155398, upload-time = "2024-10-26T20:52:35.34Z" }, - { url = "https://files.pythonhosted.org/packages/f9/4e/e7a4900180ff18f8468b1a1d6da821f67162409aa86eb53fdcb6bb1c5016/aiokafka-0.12.0-cp311-cp311-win32.whl", hash = "sha256:90511a2c4cf5f343fc2190575041fbc70171654ab0dae64b3bbabd012613bfa7", size = 348578, upload-time = "2024-10-26T20:52:36.63Z" }, - { url = "https://files.pythonhosted.org/packages/12/e6/101e7b13e1a4bce745be927bcecf7d9dddd68c57bbd876e31697e60fdc8d/aiokafka-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:04c8ad27d04d6c53a1859687015a5f4e58b1eb221e8a7342d6c6b04430def53e", size = 368368, upload-time = "2024-10-26T20:52:38.438Z" }, { url = "https://files.pythonhosted.org/packages/53/d4/baf1b2389995c6c312834792329a1993a303ff703ac023250ff977c5923b/aiokafka-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b01947553ff1120fa1cb1a05f2c3e5aa47a5378c720bafd09e6630ba18af02aa", size = 375031, upload-time = "2024-10-26T20:52:40.104Z" }, { url = "https://files.pythonhosted.org/packages/54/ac/653070a4add8beea7aa8209ab396de87c7b4f9628fff15efcdbaea40e973/aiokafka-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e3c8ec1c0606fa645462c7353dc3e4119cade20c4656efa2031682ffaad361c0", size = 370619, upload-time = "2024-10-26T20:52:41.877Z" }, { url = "https://files.pythonhosted.org/packages/80/f2/0ddaaa11876ab78e0f3b30f272c62eea70870e1a52a5afe985c7c1d098e1/aiokafka-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577c1c48b240e9eba57b3d2d806fb3d023a575334fc3953f063179170cc8964f", size = 1192363, upload-time = "2024-10-26T20:52:44.028Z" }, @@ -124,13 +118,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/83/91/124d020cea78e4e4b6db7ff726c2c2e4a5865293d0a4355d13b0312d99f1/astropy-7.1.0.tar.gz", hash = "sha256:c8f254322295b1b8cf24303d6f155bf7efdb6c1282882b966ce3040eff8c53c5", size = 6976116, upload-time = "2025-05-20T13:40:10.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/0d/ed4d605f8dfa8ec236dd2c91489b24d223fa98f41f181437f6d15a98190d/astropy-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e2bc5cb91dc319bcf65ee08c68abea617c52b5d042d245542a4853b07269233", size = 6378504, upload-time = "2025-05-20T13:39:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/de/18/38e92baafca43f654704d7ca8cd0efdd626d9987d9918de08abe8a1cb297/astropy-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fba97e3d99ad48540b6eb7c6f7849e57dd6aaf9d9d48707b227bf39ac77c6368", size = 6303097, upload-time = "2025-05-20T13:39:31.586Z" }, - { url = "https://files.pythonhosted.org/packages/f9/03/cca1b2259ab22dae67a7d7a7b22454206c0fb5384e99b00d2326663fbed6/astropy-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58498f9549a0e94a1f2cd48ddb96c0bed7b0375a6362ee4025f19d6bd114f9a", size = 10080125, upload-time = "2025-05-20T13:39:33.034Z" }, - { url = "https://files.pythonhosted.org/packages/80/a1/87254b4f8ec58308ad745574da66fd205ba59da74cdf7e8319d442bb3161/astropy-7.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7694e0b2ffc5fa60c47a55667bdfda98cbae8df736f60cc57afbe38bc5c98b41", size = 10087022, upload-time = "2025-05-20T13:39:35.315Z" }, - { url = "https://files.pythonhosted.org/packages/d7/4d/96f3806e6cf50dd8bd08ccac668e54d3d66b43bf5574bf27bf5fbad4de74/astropy-7.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83fe4c581bbdf1cbaeab1efc58891e4433d729412847f4d0788c870fee709432", size = 10120923, upload-time = "2025-05-20T13:39:39.781Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c2/0f089b4c387fc6a4b4ac9760b8a5ec3b3552af7ecf4d3a372963cd5d43e2/astropy-7.1.0-cp311-cp311-win32.whl", hash = "sha256:a2d277cf0d34748bfc23bb6b4d1974f058c8541ec92113cb00db06eebe5a2862", size = 6151431, upload-time = "2025-05-20T13:39:41.974Z" }, - { url = "https://files.pythonhosted.org/packages/3f/7e/13b136c020e009daf8481163087f287c634cd44aad600becc6cdafa6c0b1/astropy-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b66b8dbb678fca9a811f24227a953a3c2f7b02c951c5b2dee3ab6853ac6e1b0", size = 6278397, upload-time = "2025-05-20T13:39:43.709Z" }, { url = "https://files.pythonhosted.org/packages/cf/9a/ed2b35b55e28a6317471b61456d2feda7798b2dd3601e17859620e8eae4c/astropy-7.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0fec2f4b5265caab68020eaa320704e7ce9433ae8dbea75c300468fed695437", size = 6381273, upload-time = "2025-05-20T13:39:45.481Z" }, { url = "https://files.pythonhosted.org/packages/5c/45/333bc1072f3b2ac31aec33063bb7122661405a97cb7fec702e95af707bd4/astropy-7.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4bb022f863cf13eefeb406692f58824c0d9bdb1aa36ae786e87c096d8ebdd07", size = 6301716, upload-time = "2025-05-20T13:39:47.339Z" }, { url = "https://files.pythonhosted.org/packages/58/90/bfb7a1b5d9e3401967e351cf31add576cddf7466d2030cc6f4d1d841a18d/astropy-7.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811a4aedd8fbf6d7611d64d40af1f0c1c1e6621e5992e7e1a7b5fec47dc1fa1", size = 10096600, upload-time = "2025-05-20T13:39:49.169Z" }, @@ -164,14 +151,6 @@ version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506, upload-time = "2024-10-20T00:29:27.988Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922, upload-time = "2024-10-20T00:29:29.391Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565, upload-time = "2024-10-20T00:29:30.832Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962, upload-time = "2024-10-20T00:29:33.114Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791, upload-time = "2024-10-20T00:29:34.677Z" }, - { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696, upload-time = "2024-10-20T00:29:36.389Z" }, - { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358, upload-time = "2024-10-20T00:29:37.915Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375, upload-time = "2024-10-20T00:29:39.987Z" }, { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162, upload-time = "2024-10-20T00:29:41.88Z" }, { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025, upload-time = "2024-10-20T00:29:43.352Z" }, { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243, upload-time = "2024-10-20T00:29:44.922Z" }, @@ -255,18 +234,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, @@ -295,19 +262,6 @@ version = "3.4.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, @@ -363,17 +317,6 @@ version = "7.8.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, - { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, - { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, - { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, - { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, - { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, - { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, @@ -385,15 +328,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, - { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, ] -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - [[package]] name = "cryptography" version = "43.0.3" @@ -554,12 +491,6 @@ version = "1.11.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/48/8f/32664a3245247b13702d13d2657ea534daf64e58a3f72a3a2d10598d6916/fastavro-1.11.1.tar.gz", hash = "sha256:bf6acde5ee633a29fb8dfd6dfea13b164722bc3adc05a0e055df080549c1c2f8", size = 1016250, upload-time = "2025-05-18T04:54:31.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/63/f33d6fd50d8711f305f07ad8c7b4a25f2092288f376f484c979dcf277b07/fastavro-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3573340e4564e8962e22f814ac937ffe0d4be5eabbd2250f77738dc47e3c8fe9", size = 957526, upload-time = "2025-05-18T04:54:47.701Z" }, - { url = "https://files.pythonhosted.org/packages/f4/09/a57ad9d8cb9b8affb2e43c29d8fb8cbdc0f1156f8496067a0712c944bacc/fastavro-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7291cf47735b8bd6ff5d9b33120e6e0974f52fd5dff90cd24151b22018e7fd29", size = 3322808, upload-time = "2025-05-18T04:54:50.419Z" }, - { url = "https://files.pythonhosted.org/packages/86/70/d6df59309d3754d6d4b0c7beca45b9b1a957d6725aed8da3aca247db3475/fastavro-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf3bb065d657d5bac8b2cb39945194aa086a9b3354f2da7f89c30e4dc20e08e2", size = 3330870, upload-time = "2025-05-18T04:54:52.406Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ea/122315154d2a799a2787058435ef0d4d289c0e8e575245419436e9b702ca/fastavro-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8758317c85296b848698132efb13bc44a4fbd6017431cc0f26eaeb0d6fa13d35", size = 3343369, upload-time = "2025-05-18T04:54:54.652Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/7800de5fec36d55a818adf3db3b085b1a033c4edd60323cf6ca0754cf8cb/fastavro-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad99d57228f83bf3e2214d183fbf6e2fda97fd649b2bdaf8e9110c36cbb02624", size = 3430629, upload-time = "2025-05-18T04:54:56.513Z" }, - { url = "https://files.pythonhosted.org/packages/48/65/2b74ccfeba9dcc3f7dbe64907307386b4a0af3f71d2846f63254df0f1e1d/fastavro-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:9134090178bdbf9eefd467717ced3dc151e27a7e7bfc728260ce512697efe5a4", size = 451621, upload-time = "2025-05-18T04:54:58.156Z" }, { url = "https://files.pythonhosted.org/packages/99/58/8e789b0a2f532b22e2d090c20d27c88f26a5faadcba4c445c6958ae566cf/fastavro-1.11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8bc238f2637cd5d15238adbe8fb8c58d2e6f1870e0fb28d89508584670bae4b", size = 939583, upload-time = "2025-05-18T04:54:59.853Z" }, { url = "https://files.pythonhosted.org/packages/34/3f/02ed44742b1224fe23c9fc9b9b037fc61769df716c083cf80b59a02b9785/fastavro-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b403933081c83fc4d8a012ee64b86e560a024b1280e3711ee74f2abc904886e8", size = 3257734, upload-time = "2025-05-18T04:55:02.366Z" }, { url = "https://files.pythonhosted.org/packages/cc/bc/9cc8b19eeee9039dd49719f8b4020771e805def262435f823fa8f27ddeea/fastavro-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f6ecb4b5f77aa756d973b7dd1c2fb4e4c95b4832a3c98b059aa96c61870c709", size = 3318218, upload-time = "2025-05-18T04:55:04.352Z" }, @@ -644,15 +575,6 @@ version = "3.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload-time = "2024-09-20T18:21:04.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479, upload-time = "2024-09-20T17:07:22.332Z" }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404, upload-time = "2024-09-20T17:36:45.588Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813, upload-time = "2024-09-20T17:39:19.052Z" }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517, upload-time = "2024-09-20T17:44:24.101Z" }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831, upload-time = "2024-09-20T17:08:40.577Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413, upload-time = "2024-09-20T17:08:31.728Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619, upload-time = "2024-09-20T17:44:14.222Z" }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198, upload-time = "2024-09-20T17:09:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930, upload-time = "2024-09-20T17:25:18.656Z" }, { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload-time = "2024-09-20T17:08:07.301Z" }, { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload-time = "2024-09-20T17:36:47.628Z" }, { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload-time = "2024-09-20T17:39:21.258Z" }, @@ -682,10 +604,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a1/7b/4b5d38f4774477a1af3d3a2e200f54e7ebe6a7405e220d2bc46006b8c07f/hpgeom-1.4.0.tar.gz", hash = "sha256:95f7b355a4ca9e923e7b37f13f14c828670435645c51fb41e92223894532a26e", size = 147819, upload-time = "2024-10-07T20:53:57.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/51/827a48bbe613b5216cf84eb69281c098eee02c9bc324116eef11c8037c17/hpgeom-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5650f5be65f3aecf9b86fce8fe4b22895e0b3480fb3ecfa2ffc3b7fa1138a4e9", size = 66781, upload-time = "2024-10-07T20:53:42.612Z" }, - { url = "https://files.pythonhosted.org/packages/8b/70/ab872cea239c4934fc90e2ea9da5f34a1a9f7bd6b640cda16c5f7ac67a5a/hpgeom-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1429d101db6342febc40fca5eb5d407603fe2af4cda3b3bf9ddb6173b0c058fd", size = 63061, upload-time = "2024-10-07T20:53:43.824Z" }, - { url = "https://files.pythonhosted.org/packages/ba/22/13c4bf82b11cd7bf4afa6c70fdefaaefab99117f59ef0924f8352432f7c5/hpgeom-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91abaf5841215dc8bcccd194f0d7351f7bd7163ec6ff69449abfb20a76250506", size = 154082, upload-time = "2024-10-07T20:53:45.137Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1c/1d2ee336410ca257fcef31952cb0d8d3791c92affc1af05f5b209987cb38/hpgeom-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f3af4974d86e8d922dd5b81badb93e2d9dcc068538bd93eab002a23ebd9f7f2", size = 65698, upload-time = "2024-10-07T20:53:46.238Z" }, { url = "https://files.pythonhosted.org/packages/dc/36/415c1a8483f827360bad8fb111c10244970b3e9c5845e2fdb78ea0dd4091/hpgeom-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e70c81bf01c6b2bbe417a4b164d0effb349259b9ddcaed81a14b02a57a3a13b6", size = 66463, upload-time = "2024-10-07T20:53:47.626Z" }, { url = "https://files.pythonhosted.org/packages/8e/09/5b96961f4d48156685b1a998c6c243cfad562d9304344f70b9ddd82b92b7/hpgeom-1.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c73de4b08abf736b2dadeca4a6428b514df0c9387bc560e7428ef7a2ae5b723", size = 62918, upload-time = "2024-10-07T20:53:48.977Z" }, { url = "https://files.pythonhosted.org/packages/ac/0c/ef330cf846111482179af71f5af311cd381c58a40b7a787017947017a73c/hpgeom-1.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adc5e9a27c5da2219c1ef76c6ddf2ed4f74ea217e3a639d430e84a567de6ec0c", size = 156454, upload-time = "2024-10-07T20:53:50.043Z" }, @@ -694,13 +612,11 @@ wheels = [ [[package]] name = "htcondor" -version = "24.0.6" +version = "24.0.7" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/ee/2ec7969795e9a86cc8c3f5a405aea9bf11bb273fe14138c9383312198668/htcondor-24.0.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e693c3e03c98f56ecf14a952272b559964b3eb4cc7b012a8bd9b36be3aac6738", size = 59109403, upload-time = "2025-04-01T13:56:24.687Z" }, - { url = "https://files.pythonhosted.org/packages/fe/fb/2367d905d587a499c74b343043cbe4afbca62fe707dd4ce212cf7a53ff68/htcondor-24.0.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:27e70cd412fa520f79c0142d2d492894e9b8d1b5857abd9584ddf5b340d85748", size = 60465800, upload-time = "2025-04-01T13:56:29.015Z" }, - { url = "https://files.pythonhosted.org/packages/f6/5d/5e74fe73e88c53adbd846e2c22c0b3d0fe68f382eee833114b8e2a938eb4/htcondor-24.0.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:64bbe60839b1bc607fbc62d4bc177f287ec931933e6ba14f15e3719e0219dbb2", size = 59163697, upload-time = "2025-04-01T13:56:35.288Z" }, - { url = "https://files.pythonhosted.org/packages/8a/03/f3ab909506a3fdfaa19381084a647a574d88c786766b49f6349cd6c85e43/htcondor-24.0.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8b2804748e716847a6b13ef68c10e03a705ab49cd468b5a2f1438e3cd51caa41", size = 60565439, upload-time = "2025-04-01T13:56:39.323Z" }, + { url = "https://files.pythonhosted.org/packages/e0/15/51ab83d2154ea41f781a3d332ad6193e3398d3b874d9c67334482da1770a/htcondor-24.0.7-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3123b1a8de0cca85f5669f3515fca0bb0ec57089274f86764d7aea1abb04f767", size = 59170652, upload-time = "2025-04-22T13:41:50.553Z" }, + { url = "https://files.pythonhosted.org/packages/c2/2e/60053f15322f81b15b0edefc4aefa6a5a2bedf5ab83f4272a51065c57683/htcondor-24.0.7-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3587519f8f0cff6ac952c869a62548789f608939f67f29816f02d6bece3e5164", size = 60569772, upload-time = "2025-04-22T13:41:54.901Z" }, ] [[package]] @@ -722,13 +638,6 @@ version = "0.6.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, @@ -921,6 +830,7 @@ dependencies = [ { name = "rich" }, { name = "safir", extra = ["db"] }, { name = "sqlalchemy", extra = ["asyncio"] }, + { name = "sqlmodel" }, { name = "structlog" }, { name = "tabulate" }, { name = "uvicorn", extra = ["standard"] }, @@ -930,7 +840,7 @@ dependencies = [ dev = [ { name = "aiosqlite" }, { name = "asgi-lifespan" }, - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "greenlet" }, { name = "mypy" }, { name = "pre-commit" }, @@ -964,22 +874,23 @@ requires-dist = [ { name = "click", specifier = "==8.1.*" }, { name = "fastapi", specifier = "==0.115.*" }, { name = "greenlet", specifier = "==3.1.*" }, - { name = "htcondor", marker = "sys_platform == 'linux'", specifier = "==24.0.6" }, + { name = "htcondor", marker = "sys_platform == 'linux'", specifier = "==24.0.7" }, { name = "httpx", specifier = ">=0.27.2" }, { name = "jinja2", specifier = "==3.1.*" }, { name = "networkx", specifier = ">=3.5" }, { name = "numpy", specifier = "==2.1.*" }, { name = "panda-client", specifier = ">=1.5.82" }, { name = "psycopg2-binary", specifier = "==2.9.*" }, - { name = "pydantic", specifier = "==2.10.*" }, - { name = "pydantic-settings", specifier = "==2.7.*" }, + { name = "pydantic", specifier = "==2.11.*" }, + { name = "pydantic-settings", specifier = "==2.9.*" }, { name = "python-multipart", specifier = "==0.0.*" }, { name = "rich", specifier = "==13.9.*" }, { name = "safir", extras = ["db"], specifier = "==7.0.*" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = "==2.0.*" }, + { name = "sqlmodel", specifier = ">=0.0.24" }, { name = "structlog", specifier = "==24.4.*" }, { name = "tabulate", specifier = "==0.9.*" }, - { name = "uvicorn", extras = ["standard"], specifier = "==0.32.*" }, + { name = "uvicorn", extras = ["standard"], specifier = "==0.34.*" }, ] [package.metadata.requires-dev] @@ -988,8 +899,8 @@ dev = [ { name = "asgi-lifespan", specifier = ">=2.1.0" }, { name = "coverage", extras = ["toml"], specifier = ">=7.6.7" }, { name = "greenlet", specifier = ">=3.1.1" }, - { name = "mypy", specifier = ">=1.13.0" }, - { name = "pre-commit", specifier = ">=4.0.1" }, + { name = "mypy", specifier = ">=1.16.0" }, + { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-cov", specifier = ">=6.0.0" }, @@ -1004,17 +915,17 @@ dev = [ { name = "types-tabulate", specifier = ">=0.9.0.20240106" }, ] lsst = [ - { name = "lsst-ctrl-bps", specifier = ">=29.2025.1500" }, - { name = "lsst-ctrl-bps-htcondor", marker = "sys_platform == 'linux'", specifier = ">=29.2025.1500" }, - { name = "lsst-ctrl-bps-panda", specifier = ">=29.2025.1500" }, - { name = "lsst-daf-butler", specifier = ">=29.2025.1500" }, - { name = "lsst-pipe-base", specifier = ">=29.2025.1500" }, - { name = "lsst-utils", specifier = ">=29.2025.1500" }, + { name = "lsst-ctrl-bps", specifier = ">=29.2025.2300" }, + { name = "lsst-ctrl-bps-htcondor", marker = "sys_platform == 'linux'", specifier = ">=29.2025.2300" }, + { name = "lsst-ctrl-bps-panda", specifier = ">=29.2025.2300" }, + { name = "lsst-daf-butler", specifier = ">=29.2025.2300" }, + { name = "lsst-pipe-base", specifier = ">=29.2025.2300" }, + { name = "lsst-utils", specifier = ">=29.2025.2300" }, ] [[package]] name = "lsst-ctrl-bps" -version = "29.2025.2200" +version = "29.2025.2400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy" }, @@ -1027,14 +938,14 @@ dependencies = [ { name = "networkx" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/8a/67bc88d17cd2a629ba009207128bf8fb0eadded52dc10aed065d19fc6fc3/lsst_ctrl_bps-29.2025.2200.tar.gz", hash = "sha256:559106d7f8ead51e82bc1bc7aa32b88829eadf470f3cf9ad897be7d8eded84d2", size = 136684, upload-time = "2025-05-29T09:14:57.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/55/709e97410c5c0a161f304c09df09a617359b1719371ebf45103380f489fd/lsst_ctrl_bps-29.2025.2400.tar.gz", hash = "sha256:d668ff38067c5cd50e1a8e7f6e260ae4aa53f86e11d8b62096274594e803d505", size = 136707, upload-time = "2025-06-12T10:08:35.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/48/dcba14d085fb27054c6d5e64a7423ad81f3faf12f36b152dd24165591316/lsst_ctrl_bps-29.2025.2200-py3-none-any.whl", hash = "sha256:fc4574fc26563c7fd2b53e9a1a37192364dc81943d47838e284e73d553c1436e", size = 115251, upload-time = "2025-05-29T09:14:56.259Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c0/1e6c42e59acee2a52ad689a00d0128e33c9c2bfbff8ed3ff2d65e7afb6ff/lsst_ctrl_bps-29.2025.2400-py3-none-any.whl", hash = "sha256:c1c34c95415442a6633af1a1ca15980ac8ef22b6b9a13f5d8bb9a78ea368d939", size = 115251, upload-time = "2025-06-12T10:08:34.097Z" }, ] [[package]] name = "lsst-ctrl-bps-htcondor" -version = "29.2025.2200" +version = "29.2025.2400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "htcondor" }, @@ -1042,14 +953,14 @@ dependencies = [ { name = "lsst-daf-butler" }, { name = "lsst-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/cc/1f3b822ae15ddcd2d59cd9a28055a3e7a257fa731f33dc5e81d41c797074/lsst_ctrl_bps_htcondor-29.2025.2200.tar.gz", hash = "sha256:39e7aa4f23232e63390243eac9b0bcb18cc1f8689559d08bbe82781a08c901a6", size = 94355, upload-time = "2025-05-29T09:14:48.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/92/6e78fb6e3855546d994606ac22cb7abe8c323819bab9a655a1dcca7e224c/lsst_ctrl_bps_htcondor-29.2025.2400.tar.gz", hash = "sha256:ea0e2230a637e4598c57eff2cb1e4cc22739c73c04d81b7704a139797bda7846", size = 94905, upload-time = "2025-06-12T10:08:28.724Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/d8/72d0247c9bf9ff08e015b34e8b51fa2eedb5c2737caed59530093f14c086/lsst_ctrl_bps_htcondor-29.2025.2200-py3-none-any.whl", hash = "sha256:85bf3e163da6bbc898fced1c2ac2d6ed07b9b4527ea499ff94122697ab6009ec", size = 68134, upload-time = "2025-05-29T09:14:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/66/90/5e7f668368cd8618ac0daf10625cf6cdd3ab18011f5bae9e1ad98cd08014/lsst_ctrl_bps_htcondor-29.2025.2400-py3-none-any.whl", hash = "sha256:bf9a0306855e8e6eda7e75f99647817538062e70c451a55c59d7c7b34d5eb6f0", size = 68132, upload-time = "2025-06-12T10:08:27.172Z" }, ] [[package]] name = "lsst-ctrl-bps-panda" -version = "29.2025.2200" +version = "29.2025.2400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1064,9 +975,9 @@ dependencies = [ { name = "panda-client" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/be/d0bef2c8fded218e6218f1ebbd9b9af04b9b3df22df52830c39b8949c047/lsst_ctrl_bps_panda-29.2025.2200.tar.gz", hash = "sha256:bfb041d627f2fa7a994e7f95d569fdf13864bb16c0b264e86e835ad482bf7819", size = 45808, upload-time = "2025-05-29T09:15:03.255Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/3d/9e2531637efd705ffcaa793fc943e08f37ae44482a13b7b45645ea2bb2b8/lsst_ctrl_bps_panda-29.2025.2400.tar.gz", hash = "sha256:2d3b9e3b6ba0d8967a9983b00a1c24cec169b4fefa148169aa5da4131a3d9e0d", size = 45825, upload-time = "2025-06-12T10:08:37.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/46/37245d7b6d92d5deea8e48a40fab1e3a9b0825b759df3aa7b299ade3eee8/lsst_ctrl_bps_panda-29.2025.2200-py3-none-any.whl", hash = "sha256:c5de2c175973e0b27d4d2b6232c72dbb0e20bfc5334983f8aa10041e2d7db3fb", size = 54672, upload-time = "2025-05-29T09:15:02.107Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/b76eae078cd368c694a9a28949ee99e9306a8d30049400da4d9fd9ed907c/lsst_ctrl_bps_panda-29.2025.2400-py3-none-any.whl", hash = "sha256:1ac055d6aac368f4bed5a4b16f524bf00b45d597b6e620dd1d12f3d71848fa71", size = 54673, upload-time = "2025-06-12T10:08:36.203Z" }, ] [[package]] @@ -1091,7 +1002,7 @@ wheels = [ [[package]] name = "lsst-daf-butler" -version = "29.2025.2100" +version = "29.2025.2400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy" }, @@ -1107,9 +1018,9 @@ dependencies = [ { name = "pyyaml" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/97/54d290ed0e9dd50bc7a4c4ca6d5d4e25110a7c9c8f14b1c262261f85335e/lsst_daf_butler-29.2025.2100.tar.gz", hash = "sha256:12fed854432b6dceadc26026fda80ecc17e6ab0ca6d8daaa26898dd881b67558", size = 1297120, upload-time = "2025-05-22T09:33:00.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/1d/3903b3a724a118960cd41692d8915f4a4225ea8f76d9b492fde38bba2137/lsst_daf_butler-29.2025.2400.tar.gz", hash = "sha256:c51d1698cd817fa93807f61747e77635286631cf3f94588657a9f7ba719ac874", size = 1302541, upload-time = "2025-06-12T10:23:11.709Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/b7/fa7ae7ed1a89213fec1a83791a67d91d5bc4200a5f6a0ef20c72462922f4/lsst_daf_butler-29.2025.2100-py3-none-any.whl", hash = "sha256:870f87d8a10afdb0b3972b37b4ea57f468d577acc2642db436e4bda8dd4b371b", size = 1400845, upload-time = "2025-05-22T09:32:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/88/e5/77fe839a573f320f89549ef4ba574a0b29545a6352400f43b88a3e12b48f/lsst_daf_butler-29.2025.2400-py3-none-any.whl", hash = "sha256:7fd6ef9dab6f8530092990b4fa17385cde28edd9c0d07357e4e8c5e9fcd6eec6", size = 1409162, upload-time = "2025-06-12T10:23:09.686Z" }, ] [[package]] @@ -1142,7 +1053,7 @@ wheels = [ [[package]] name = "lsst-pipe-base" -version = "29.2025.2100" +version = "29.2025.2400" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy" }, @@ -1157,9 +1068,9 @@ dependencies = [ { name = "pyyaml" }, { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/0d/1e9fc34b2f00e492afebd08ce46d8c3439d583b8374fe7521bb3e5887469/lsst_pipe_base-29.2025.2100.tar.gz", hash = "sha256:2169d5fef2f0d932277129350c517c5301feb7ac69eeb366819257323d5cfcff", size = 427504, upload-time = "2025-05-22T09:22:27.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/80/7dee458b479bf85df7c8a064e96788ee6ff7df7f9bde65ac4e8c5b1f576b/lsst_pipe_base-29.2025.2400.tar.gz", hash = "sha256:38c98a0bd121f0a5abe0d57c78f94c4f3accea52168bec3e2d5faaf4b7191472", size = 428447, upload-time = "2025-06-12T10:12:36.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/9e/71006a02463dd3fb9a5d89022c2a6c78bf269694f149b861e3d855d3f05c/lsst_pipe_base-29.2025.2100-py3-none-any.whl", hash = "sha256:89eef4d8a8540e7f9672ada5ae36a8a0bb7868ccb49b110c295285a0a9bec79c", size = 397894, upload-time = "2025-05-22T09:22:25.719Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9e/724d6e28712fa1e7fd79c0fed9d9a6049fae984be2bc2d77d092f042d89f/lsst_pipe_base-29.2025.2400-py3-none-any.whl", hash = "sha256:3aea896b370049ec33c7ee77efbd3224f2698accb46856a1e26f4b16e0355e70", size = 398644, upload-time = "2025-06-12T10:12:34.54Z" }, ] [[package]] @@ -1190,10 +1101,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/b4db75732e5fcdf48011a29615cce666043a330de0d2711492906f5ff3b6/lsst_sphgeom-29.2025.1600.tar.gz", hash = "sha256:0a15d3da8e3e13633662eca094398e5e09069e6f5df77002ddf33b03d2102890", size = 161055, upload-time = "2025-04-17T09:23:06.509Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/82/9248f48efb83a27d8a14a6f952f1d460b47f0242d0db3c2fe3b50c14c787/lsst_sphgeom-29.2025.1600-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ca6617be1beeddcb991c2fdf8d7c9fae314a3a3ba6ec23db8cac4616a8d20c91", size = 576871, upload-time = "2025-04-17T09:22:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/dd/77/0c2ab7d131bc9bfa44005f7a4eee649181f1312f7df442e1c6136f8aa1e6/lsst_sphgeom-29.2025.1600-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f85de4ec1cafe6fb0f5109fad73340624b692a764d556b5ee88d4e90433b8a64", size = 553020, upload-time = "2025-04-17T09:22:51.691Z" }, - { url = "https://files.pythonhosted.org/packages/00/25/3d59c1203690ff1af8700c1831bc8490943e0251600509af686739d3de77/lsst_sphgeom-29.2025.1600-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d84faf0553805feb84107293852de9da32c7040ee58c16d45b3a5cfc55ad84", size = 703582, upload-time = "2025-04-17T09:22:53.273Z" }, - { url = "https://files.pythonhosted.org/packages/65/9e/22b16d61695bbe47eb3e4c92c5524ff0fb24b139bb6c36f9bc78b8159373/lsst_sphgeom-29.2025.1600-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f937f6385f0ce41efa141e579b0d3b039a9041e72a069068bfcceda7f7694d44", size = 758359, upload-time = "2025-04-17T09:22:54.966Z" }, { url = "https://files.pythonhosted.org/packages/21/93/68141319f816785b784ea3a059f3877fe475a9a7d097a8c9e809a150a8a8/lsst_sphgeom-29.2025.1600-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b148ec4345d171060e42326acf9443f9a294d75405eba2944b60ef026e69ce2", size = 589382, upload-time = "2025-04-17T09:22:56.098Z" }, { url = "https://files.pythonhosted.org/packages/37/d0/58cbb495b527e46f793e4e08679135e59aa1d60aa42578ef4fed59f17ac6/lsst_sphgeom-29.2025.1600-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d5b631a8bcd184c13173cfb836155212e7ce9d63b01f263c6fa4d2af8d6e907", size = 562716, upload-time = "2025-04-17T09:22:57.237Z" }, { url = "https://files.pythonhosted.org/packages/32/54/56a5bfff88f28098bf5dfd82df2d5f760db3e15273120b7d666f8da35bbf/lsst_sphgeom-29.2025.1600-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07bb22da075fdac9f5c87edb110af382c2f8dd3990fd0b812cb4675a7dd7b2c", size = 694419, upload-time = "2025-04-17T09:22:58.856Z" }, @@ -1202,7 +1109,7 @@ wheels = [ [[package]] name = "lsst-utils" -version = "29.2025.2100" +version = "29.2025.2300" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy" }, @@ -1212,9 +1119,9 @@ dependencies = [ { name = "pyyaml" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/f3/ae2a16d8419c903e709cf385464cbd039319061d7b900b198554e0f5ad0c/lsst_utils-29.2025.2100.tar.gz", hash = "sha256:01d23c814871a8c46bc7b8fab7c309909774d6f2e1529f250c8b64624400a779", size = 89615, upload-time = "2025-05-22T09:23:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/da/9818c34d51e9900098a1d7c405c56adbc5306a7a071dd6aae766fb66ebcd/lsst_utils-29.2025.2300.tar.gz", hash = "sha256:ecb94c444424eb31cc160957c7fe62cca07e70b837d6546fa68eebdfa59d6dd7", size = 89620, upload-time = "2025-06-05T09:58:31.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d6/cf65b4b1260364dcf1d3b211be151a17fc15d0dbe3bac43561c350ba88de/lsst_utils-29.2025.2100-py3-none-any.whl", hash = "sha256:1b4953746f1b9bffae9d8a42844cba85040a6764cdd137a623e566918bbaf328", size = 72858, upload-time = "2025-05-22T09:23:39.009Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/bcb9752cd3c7f7ace826f02ad33d2614542182a1ef64185280fb7df1080f/lsst_utils-29.2025.2300-py3-none-any.whl", hash = "sha256:9df9a3ed24a9ec54ccaee48937f3fb32ced9253f69f0fbf9369ab7916ff8fa5d", size = 72858, upload-time = "2025-06-05T09:58:30.656Z" }, ] [[package]] @@ -1247,16 +1154,6 @@ version = "3.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, @@ -1289,12 +1186,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, - { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, - { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, - { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, - { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, @@ -1337,16 +1228,6 @@ version = "2.1.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090, upload-time = "2024-11-02T17:48:55.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/81/c8167192eba5247593cd9d305ac236847c2912ff39e11402e72ae28a4985/numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", size = 21156252, upload-time = "2024-11-02T17:34:01.372Z" }, - { url = "https://files.pythonhosted.org/packages/da/74/5a60003fc3d8a718d830b08b654d0eea2d2db0806bab8f3c2aca7e18e010/numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", size = 13784119, upload-time = "2024-11-02T17:34:23.809Z" }, - { url = "https://files.pythonhosted.org/packages/47/7c/864cb966b96fce5e63fcf25e1e4d957fe5725a635e5f11fe03f39dd9d6b5/numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", size = 5352978, upload-time = "2024-11-02T17:34:34.001Z" }, - { url = "https://files.pythonhosted.org/packages/09/ac/61d07930a4993dd9691a6432de16d93bbe6aa4b1c12a5e573d468eefc1ca/numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", size = 6892570, upload-time = "2024-11-02T17:34:45.401Z" }, - { url = "https://files.pythonhosted.org/packages/27/2f/21b94664f23af2bb52030653697c685022119e0dc93d6097c3cb45bce5f9/numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", size = 13896715, upload-time = "2024-11-02T17:35:06.564Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f0/80811e836484262b236c684a75dfc4ba0424bc670e765afaa911468d9f39/numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", size = 16339644, upload-time = "2024-11-02T17:35:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/fa/81/ce213159a1ed8eb7d88a2a6ef4fbdb9e4ffd0c76b866c350eb4e3c37e640/numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", size = 16712217, upload-time = "2024-11-02T17:35:56.703Z" }, - { url = "https://files.pythonhosted.org/packages/7d/84/4de0b87d5a72f45556b2a8ee9fc8801e8518ec867fc68260c1f5dcb3903f/numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", size = 14399053, upload-time = "2024-11-02T17:36:22.3Z" }, - { url = "https://files.pythonhosted.org/packages/7e/1c/e5fabb9ad849f9d798b44458fd12a318d27592d4bc1448e269dec070ff04/numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", size = 6534741, upload-time = "2024-11-02T17:36:33.552Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/a9a4b538e28f854bfb62e1dea3c8fea12e90216a276c7777ae5345ff29a7/numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", size = 12869487, upload-time = "2024-11-02T17:36:52.909Z" }, { url = "https://files.pythonhosted.org/packages/8a/f0/385eb9970309643cbca4fc6eebc8bb16e560de129c91258dfaa18498da8b/numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", size = 20849658, upload-time = "2024-11-02T17:37:23.919Z" }, { url = "https://files.pythonhosted.org/packages/54/4a/765b4607f0fecbb239638d610d04ec0a0ded9b4951c56dc68cef79026abf/numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", size = 13492258, upload-time = "2024-11-02T17:37:45.252Z" }, { url = "https://files.pythonhosted.org/packages/bd/a7/2332679479c70b68dccbf4a8eb9c9b5ee383164b161bee9284ac141fbd33/numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", size = 5090249, upload-time = "2024-11-02T17:37:54.252Z" }, @@ -1486,18 +1367,6 @@ version = "2.9.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397, upload-time = "2024-10-16T11:19:40.033Z" }, - { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806, upload-time = "2024-10-16T11:19:43.5Z" }, - { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370, upload-time = "2024-10-16T11:19:46.986Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780, upload-time = "2024-10-16T11:19:50.242Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583, upload-time = "2024-10-16T11:19:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831, upload-time = "2024-10-16T11:19:57.762Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822, upload-time = "2024-10-16T11:20:04.693Z" }, - { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975, upload-time = "2024-10-16T11:20:11.401Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320, upload-time = "2024-10-16T11:20:17.959Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617, upload-time = "2024-10-16T11:20:24.711Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618, upload-time = "2024-10-16T11:20:27.718Z" }, - { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816, upload-time = "2024-10-16T11:20:30.777Z" }, { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, @@ -1518,15 +1387,6 @@ version = "20.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload-time = "2025-04-27T12:34:23.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/a2/b7930824181ceadd0c63c1042d01fa4ef63eee233934826a7a2a9af6e463/pyarrow-20.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0", size = 30856035, upload-time = "2025-04-27T12:28:40.78Z" }, - { url = "https://files.pythonhosted.org/packages/9b/18/c765770227d7f5bdfa8a69f64b49194352325c66a5c3bb5e332dfd5867d9/pyarrow-20.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb", size = 32309552, upload-time = "2025-04-27T12:28:47.051Z" }, - { url = "https://files.pythonhosted.org/packages/44/fb/dfb2dfdd3e488bb14f822d7335653092dde150cffc2da97de6e7500681f9/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232", size = 41334704, upload-time = "2025-04-27T12:28:55.064Z" }, - { url = "https://files.pythonhosted.org/packages/58/0d/08a95878d38808051a953e887332d4a76bc06c6ee04351918ee1155407eb/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f", size = 42399836, upload-time = "2025-04-27T12:29:02.13Z" }, - { url = "https://files.pythonhosted.org/packages/f3/cd/efa271234dfe38f0271561086eedcad7bc0f2ddd1efba423916ff0883684/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab", size = 40711789, upload-time = "2025-04-27T12:29:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/46/1f/7f02009bc7fc8955c391defee5348f510e589a020e4b40ca05edcb847854/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62", size = 42301124, upload-time = "2025-04-27T12:29:17.187Z" }, - { url = "https://files.pythonhosted.org/packages/4f/92/692c562be4504c262089e86757a9048739fe1acb4024f92d39615e7bab3f/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c", size = 42916060, upload-time = "2025-04-27T12:29:24.253Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ec/9f5c7e7c828d8e0a3c7ef50ee62eca38a7de2fa6eb1b8fa43685c9414fef/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3", size = 44547640, upload-time = "2025-04-27T12:29:32.782Z" }, - { url = "https://files.pythonhosted.org/packages/54/96/46613131b4727f10fd2ffa6d0d6f02efcc09a0e7374eff3b5771548aa95b/pyarrow-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc", size = 25781491, upload-time = "2025-04-27T12:29:38.464Z" }, { url = "https://files.pythonhosted.org/packages/a1/d6/0c10e0d54f6c13eb464ee9b67a68b8c71bcf2f67760ef5b6fbcddd2ab05f/pyarrow-20.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:75a51a5b0eef32727a247707d4755322cb970be7e935172b6a3a9f9ae98404ba", size = 30815067, upload-time = "2025-04-27T12:29:44.384Z" }, { url = "https://files.pythonhosted.org/packages/7e/e2/04e9874abe4094a06fd8b0cbb0f1312d8dd7d707f144c2ec1e5e8f452ffa/pyarrow-20.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:211d5e84cecc640c7a3ab900f930aaff5cd2702177e0d562d426fb7c4f737781", size = 32297128, upload-time = "2025-04-27T12:29:52.038Z" }, { url = "https://files.pythonhosted.org/packages/31/fd/c565e5dcc906a3b471a83273039cb75cb79aad4a2d4a12f76cc5ae90a4b8/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba3cf4182828be7a896cbd232aa8dd6a31bd1f9e32776cc3796c012855e1199", size = 41334890, upload-time = "2025-04-27T12:29:59.452Z" }, @@ -1549,68 +1409,56 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, ] [[package]] name = "pydantic-settings" -version = "2.7.1" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920, upload-time = "2024-12-31T11:27:44.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718, upload-time = "2024-12-31T11:27:43.201Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] [[package]] @@ -1712,7 +1560,7 @@ name = "pytest-cov" version = "6.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } @@ -1859,15 +1707,6 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, @@ -1951,20 +1790,6 @@ version = "0.25.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/e1/df13fe3ddbbea43567e07437f097863b20c99318ae1f58a0fe389f763738/rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d", size = 373341, upload-time = "2025-05-21T12:43:02.978Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/deef4d30fcbcbfef3b6d82d17c64490d5c94585a2310544ce8e2d3024f83/rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255", size = 359111, upload-time = "2025-05-21T12:43:05.128Z" }, - { url = "https://files.pythonhosted.org/packages/bb/7e/39f1f4431b03e96ebaf159e29a0f82a77259d8f38b2dd474721eb3a8ac9b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2", size = 386112, upload-time = "2025-05-21T12:43:07.13Z" }, - { url = "https://files.pythonhosted.org/packages/db/e7/847068a48d63aec2ae695a1646089620b3b03f8ccf9f02c122ebaf778f3c/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0", size = 400362, upload-time = "2025-05-21T12:43:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3d/9441d5db4343d0cee759a7ab4d67420a476cebb032081763de934719727b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f", size = 522214, upload-time = "2025-05-21T12:43:10.694Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/2cc5b30d95f9f1a432c79c7a2f65d85e52812a8f6cbf8768724571710786/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7", size = 411491, upload-time = "2025-05-21T12:43:12.739Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6c/44695c1f035077a017dd472b6a3253553780837af2fac9b6ac25f6a5cb4d/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd", size = 386978, upload-time = "2025-05-21T12:43:14.25Z" }, - { url = "https://files.pythonhosted.org/packages/b1/74/b4357090bb1096db5392157b4e7ed8bb2417dc7799200fcbaee633a032c9/rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65", size = 420662, upload-time = "2025-05-21T12:43:15.8Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/8cadbebf47b96e59dfe8b35868e5c38a42272699324e95ed522da09d3a40/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f", size = 563385, upload-time = "2025-05-21T12:43:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ea/92960bb7f0e7a57a5ab233662f12152085c7dc0d5468534c65991a3d48c9/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d", size = 592047, upload-time = "2025-05-21T12:43:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/61/ad/71aabc93df0d05dabcb4b0c749277881f8e74548582d96aa1bf24379493a/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042", size = 557863, upload-time = "2025-05-21T12:43:21.69Z" }, - { url = "https://files.pythonhosted.org/packages/93/0f/89df0067c41f122b90b76f3660028a466eb287cbe38efec3ea70e637ca78/rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc", size = 219627, upload-time = "2025-05-21T12:43:23.311Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8d/93b1a4c1baa903d0229374d9e7aa3466d751f1d65e268c52e6039c6e338e/rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4", size = 231603, upload-time = "2025-05-21T12:43:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/cb/11/392605e5247bead2f23e6888e77229fbd714ac241ebbebb39a1e822c8815/rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4", size = 223967, upload-time = "2025-05-21T12:43:26.566Z" }, { url = "https://files.pythonhosted.org/packages/7f/81/28ab0408391b1dc57393653b6a0cf2014cc282cc2909e4615e63e58262be/rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c", size = 364647, upload-time = "2025-05-21T12:43:28.559Z" }, { url = "https://files.pythonhosted.org/packages/2c/9a/7797f04cad0d5e56310e1238434f71fc6939d0bc517192a18bb99a72a95f/rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b", size = 350454, upload-time = "2025-05-21T12:43:30.615Z" }, { url = "https://files.pythonhosted.org/packages/69/3c/93d2ef941b04898011e5d6eaa56a1acf46a3b4c9f4b3ad1bbcbafa0bee1f/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa", size = 389665, upload-time = "2025-05-21T12:43:32.629Z" }, @@ -1979,17 +1804,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/aa/dc3620dd8db84454aaf9374bd318f1aa02578bba5e567f5bf6b79492aca4/rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe", size = 222713, upload-time = "2025-05-21T12:43:49.897Z" }, { url = "https://files.pythonhosted.org/packages/a3/7f/7cef485269a50ed5b4e9bae145f512d2a111ca638ae70cc101f661b4defd/rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192", size = 235280, upload-time = "2025-05-21T12:43:51.893Z" }, { url = "https://files.pythonhosted.org/packages/99/f2/c2d64f6564f32af913bf5f3f7ae41c7c263c5ae4c4e8f1a17af8af66cd46/rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728", size = 225399, upload-time = "2025-05-21T12:43:53.351Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/48f3df0715a585cbf5d34919c9c757a4c92c1a9eba059f2d334e72471f70/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954", size = 374208, upload-time = "2025-05-21T12:45:26.306Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/9b01bb11ce01ec03d05e627249cc2c06039d6aa24ea5a22a39c312167c10/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba", size = 359262, upload-time = "2025-05-21T12:45:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/a9/eb/5395621618f723ebd5116c53282052943a726dba111b49cd2071f785b665/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b", size = 387366, upload-time = "2025-05-21T12:45:30.42Z" }, - { url = "https://files.pythonhosted.org/packages/68/73/3d51442bdb246db619d75039a50ea1cf8b5b4ee250c3e5cd5c3af5981cd4/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038", size = 400759, upload-time = "2025-05-21T12:45:32.516Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4c/3a32d5955d7e6cb117314597bc0f2224efc798428318b13073efe306512a/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9", size = 523128, upload-time = "2025-05-21T12:45:34.396Z" }, - { url = "https://files.pythonhosted.org/packages/be/95/1ffccd3b0bb901ae60b1dd4b1be2ab98bb4eb834cd9b15199888f5702f7b/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1", size = 411597, upload-time = "2025-05-21T12:45:36.164Z" }, - { url = "https://files.pythonhosted.org/packages/ef/6d/6e6cd310180689db8b0d2de7f7d1eabf3fb013f239e156ae0d5a1a85c27f/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762", size = 388053, upload-time = "2025-05-21T12:45:38.45Z" }, - { url = "https://files.pythonhosted.org/packages/4a/87/ec4186b1fe6365ced6fa470960e68fc7804bafbe7c0cf5a36237aa240efa/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e", size = 421821, upload-time = "2025-05-21T12:45:40.732Z" }, - { url = "https://files.pythonhosted.org/packages/7a/60/84f821f6bf4e0e710acc5039d91f8f594fae0d93fc368704920d8971680d/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692", size = 564534, upload-time = "2025-05-21T12:45:42.672Z" }, - { url = "https://files.pythonhosted.org/packages/41/3a/bc654eb15d3b38f9330fe0f545016ba154d89cdabc6177b0295910cd0ebe/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf", size = 592674, upload-time = "2025-05-21T12:45:44.533Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781, upload-time = "2025-05-21T12:45:46.281Z" }, ] [[package]] @@ -2129,14 +1943,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232, upload-time = "2025-05-14T17:48:20.444Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897, upload-time = "2025-05-14T17:48:21.634Z" }, - { url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313, upload-time = "2025-05-14T17:51:56.205Z" }, - { url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807, upload-time = "2025-05-14T17:55:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632, upload-time = "2025-05-14T17:51:59.384Z" }, - { url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642, upload-time = "2025-05-14T17:55:29.901Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475, upload-time = "2025-05-14T17:56:02.095Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903, upload-time = "2025-05-14T17:56:03.499Z" }, { url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" }, { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" }, { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" }, @@ -2156,6 +1962,19 @@ mypy = [ { name = "mypy" }, ] +[[package]] +name = "sqlmodel" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780, upload-time = "2025-03-07T05:43:32.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622, upload-time = "2025-03-07T05:43:30.37Z" }, +] + [[package]] name = "starlette" version = "0.46.2" @@ -2245,35 +2064,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, ] -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, -] - [[package]] name = "tomlkit" version = "0.13.2" @@ -2310,6 +2100,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -2339,15 +2141,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.32.1" +version = "0.34.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630, upload-time = "2024-11-20T19:41:13.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828, upload-time = "2024-11-20T19:41:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, ] [package.optional-dependencies] @@ -2367,12 +2169,6 @@ version = "0.21.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, @@ -2404,19 +2200,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/f4/41b591f59021786ef517e1cdc3b510383551846703e03f204827854a96f8/watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827", size = 405336, upload-time = "2025-04-08T10:34:59.359Z" }, - { url = "https://files.pythonhosted.org/packages/ae/06/93789c135be4d6d0e4f63e96eea56dc54050b243eacc28439a26482b5235/watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4", size = 395977, upload-time = "2025-04-08T10:35:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/1cd89bd83728ca37054512d4d35ab69b5f12b8aa2ac9be3b0276b3bf06cc/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d", size = 455232, upload-time = "2025-04-08T10:35:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/d8a4d44ffe960517e487c9c04f77b06b8abf05eb680bed71c82b5f2cad62/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63", size = 459151, upload-time = "2025-04-08T10:35:03.358Z" }, - { url = "https://files.pythonhosted.org/packages/6c/da/267a1546f26465dead1719caaba3ce660657f83c9d9c052ba98fb8856e13/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418", size = 489054, upload-time = "2025-04-08T10:35:04.561Z" }, - { url = "https://files.pythonhosted.org/packages/b1/31/33850dfd5c6efb6f27d2465cc4c6b27c5a6f5ed53c6fa63b7263cf5f60f6/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9", size = 523955, upload-time = "2025-04-08T10:35:05.786Z" }, - { url = "https://files.pythonhosted.org/packages/09/84/b7d7b67856efb183a421f1416b44ca975cb2ea6c4544827955dfb01f7dc2/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6", size = 502234, upload-time = "2025-04-08T10:35:07.187Z" }, - { url = "https://files.pythonhosted.org/packages/71/87/6dc5ec6882a2254cfdd8b0718b684504e737273903b65d7338efaba08b52/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25", size = 454750, upload-time = "2025-04-08T10:35:08.859Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6c/3786c50213451a0ad15170d091570d4a6554976cf0df19878002fc96075a/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5", size = 631591, upload-time = "2025-04-08T10:35:10.64Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b3/1427425ade4e359a0deacce01a47a26024b2ccdb53098f9d64d497f6684c/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01", size = 625370, upload-time = "2025-04-08T10:35:12.412Z" }, - { url = "https://files.pythonhosted.org/packages/15/ba/f60e053b0b5b8145d682672024aa91370a29c5c921a88977eb565de34086/watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246", size = 277791, upload-time = "2025-04-08T10:35:13.719Z" }, - { url = "https://files.pythonhosted.org/packages/50/ed/7603c4e164225c12c0d4e8700b64bb00e01a6c4eeea372292a3856be33a4/watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096", size = 291622, upload-time = "2025-04-08T10:35:15.071Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c2/99bb7c96b4450e36877fde33690ded286ff555b5a5c1d925855d556968a1/watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed", size = 283699, upload-time = "2025-04-08T10:35:16.732Z" }, { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511, upload-time = "2025-04-08T10:35:17.956Z" }, { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715, upload-time = "2025-04-08T10:35:19.202Z" }, { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138, upload-time = "2025-04-08T10:35:20.586Z" }, @@ -2456,17 +2239,6 @@ version = "15.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, @@ -2487,17 +2259,6 @@ version = "1.17.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" }, { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, From b699bf0b5d1074bbf4e9312bf1ce27b19fb31518 Mon Sep 17 00:00:00 2001 From: Toby Jennings Date: Thu, 12 Jun 2025 16:16:55 -0500 Subject: [PATCH 04/37] feat(type): Refactor db session typing - use py3.12 generic type for AnyAsynSession - apply consistent type to uses of AsyncSession - refactor typevar and typealias --- src/lsst/cmservice/cli/wrappers.py | 53 +++++----- .../cmservice/client/script_dependencies.py | 5 - .../cmservice/client/step_dependencies.py | 5 - src/lsst/cmservice/common/daemon.py | 6 +- src/lsst/cmservice/common/errors.py | 6 +- src/lsst/cmservice/common/graph.py | 11 ++- src/lsst/cmservice/common/types.py | 6 ++ src/lsst/cmservice/daemon.py | 35 +++---- src/lsst/cmservice/db/campaign.py | 14 +-- src/lsst/cmservice/db/element.py | 39 ++++---- src/lsst/cmservice/db/group.py | 22 ++--- src/lsst/cmservice/db/handler.py | 27 +++--- src/lsst/cmservice/db/job.py | 24 ++--- src/lsst/cmservice/db/node.py | 96 +++++++++---------- src/lsst/cmservice/db/queue.py | 21 ++-- src/lsst/cmservice/db/row.py | 68 +++++++------ src/lsst/cmservice/db/script.py | 20 ++-- src/lsst/cmservice/db/script_dependency.py | 6 +- src/lsst/cmservice/db/session.py | 92 +++++++++++++++++- src/lsst/cmservice/db/spec_block.py | 10 +- src/lsst/cmservice/db/specification.py | 10 +- src/lsst/cmservice/db/step.py | 16 ++-- src/lsst/cmservice/db/step_dependency.py | 6 +- .../cmservice/handlers/element_handler.py | 49 +++++----- src/lsst/cmservice/handlers/elements.py | 29 +++--- src/lsst/cmservice/handlers/functions.py | 50 +++++----- src/lsst/cmservice/handlers/interface.py | 66 ++++++------- src/lsst/cmservice/handlers/job_handler.py | 5 +- src/lsst/cmservice/handlers/jobs.py | 37 +++---- src/lsst/cmservice/handlers/script_handler.py | 79 +++++++-------- src/lsst/cmservice/handlers/scripts.py | 49 +++++----- src/lsst/cmservice/main.py | 7 +- src/lsst/cmservice/routers/actions.py | 14 +-- src/lsst/cmservice/routers/campaigns.py | 10 +- src/lsst/cmservice/routers/groups.py | 8 +- src/lsst/cmservice/routers/jobs.py | 8 +- src/lsst/cmservice/routers/loaders.py | 22 ++--- src/lsst/cmservice/routers/queues.py | 16 ++-- src/lsst/cmservice/routers/scripts.py | 10 +- src/lsst/cmservice/routers/wrappers.py | 72 +++++++------- src/lsst/cmservice/web_app/app.py | 31 +++--- src/lsst/cmservice/web_app/pages/campaigns.py | 10 +- .../cmservice/web_app/pages/group_details.py | 10 +- .../cmservice/web_app/pages/job_details.py | 8 +- .../cmservice/web_app/pages/script_details.py | 12 +-- .../cmservice/web_app/pages/step_details.py | 11 +-- src/lsst/cmservice/web_app/pages/steps.py | 8 +- src/lsst/cmservice/web_app/utils/utils.py | 15 +-- tests/db/test_handlers.py | 7 +- tests/db/util_functions.py | 33 +++---- 50 files changed, 681 insertions(+), 593 deletions(-) create mode 100644 src/lsst/cmservice/common/types.py diff --git a/src/lsst/cmservice/cli/wrappers.py b/src/lsst/cmservice/cli/wrappers.py index 7b23e112b..99e751c97 100644 --- a/src/lsst/cmservice/cli/wrappers.py +++ b/src/lsst/cmservice/cli/wrappers.py @@ -11,7 +11,7 @@ import json from collections.abc import Callable, Sequence from enum import Enum -from typing import Any, TypeAlias +from typing import Any import click import yaml @@ -20,7 +20,7 @@ from ..client.client import CMClient from ..common.enums import StatusEnum -from ..db import Job, Script, SpecBlock, Specification +from ..db import ElementMixin, Job, RowMixin, Script, SpecBlock, Specification from . import options @@ -127,7 +127,7 @@ def output_dict( def get_list_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that gets all the rows from a table and attaches that function to the cli. @@ -143,7 +143,7 @@ def get_list_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -170,7 +170,7 @@ def get_rows( def get_row_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that gets a row from a table and attaches that function to the cli. @@ -183,7 +183,7 @@ def get_row_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -212,7 +212,7 @@ def get_row( def get_row_by_name_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that gets a row from a table and attaches that function to the cli. @@ -225,7 +225,7 @@ def get_row_by_name_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -254,7 +254,7 @@ def get_row_by_name( def get_row_by_fullname_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that gets a row from a table and attaches that function to the cli. @@ -267,7 +267,7 @@ def get_row_by_fullname_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -296,7 +296,7 @@ def get_row_by_fullname( def get_create_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], create_options: list[Callable], ) -> Callable: """Return a function that creates a new row in the table @@ -310,7 +310,7 @@ def get_create_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class create_options: list[Callable] @@ -342,7 +342,7 @@ def create( def get_update_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], update_options: list[Callable], ) -> Callable: """Return a function that updates a row in the table @@ -356,7 +356,7 @@ def get_update_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class update_options: list[Callable] @@ -512,7 +512,7 @@ def get_resolved_collections_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -693,7 +693,7 @@ def get_spec_aliases( def get_update_status_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that updates the status of row in the table and attaches that function to the cli. @@ -706,7 +706,7 @@ def get_update_status_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -967,9 +967,6 @@ def get_action_run_check_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin - Underlying database class - Returns ------- the_function: Callable @@ -996,7 +993,7 @@ def run_check( def get_action_accept_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that marks a row in the table as accepted and attaches that function to the cli. @@ -1009,7 +1006,7 @@ def get_action_accept_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -1038,7 +1035,7 @@ def accept( def get_action_reject_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that marks a row in the table as rejected and attaches that function to the cli. @@ -1051,7 +1048,7 @@ def get_action_reject_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -1080,7 +1077,7 @@ def reject( def get_action_reset_command( group_command: Callable, sub_client_name: str, - db_class: TypeAlias, + db_class: type[RowMixin], ) -> Callable: """Return a function that resets the status of a row in the table and attaches that function to the cli. @@ -1093,7 +1090,7 @@ def get_action_reset_command( sub_client_name: str Name of python API sub-client to use - db_class: TypeAlias = db.RowMixin + db_class: type Underlying database class Returns @@ -1125,7 +1122,7 @@ def reset( def get_element_parent_command( group_command: Callable, sub_client_name: str, - db_parent_class: TypeAlias, + db_parent_class: type[ElementMixin], ) -> Callable: """Return a function that gets the parent of an element @@ -1137,7 +1134,7 @@ def get_element_parent_command( sub_client_name: str Name of python API sub-client to use - db_parent_class: TypeAlias = db.RowMixin + db_parent_class: type Underlying parent database class Returns diff --git a/src/lsst/cmservice/client/script_dependencies.py b/src/lsst/cmservice/client/script_dependencies.py index 95c2b75e1..1f66e3df0 100644 --- a/src/lsst/cmservice/client/script_dependencies.py +++ b/src/lsst/cmservice/client/script_dependencies.py @@ -50,9 +50,4 @@ def client(self) -> httpx.Client: f"{router_string}/create", ) - update = wrappers.update_row_function( - ResponseModelClass, - f"{router_string}/update", - ) - delete = wrappers.delete_row_function(f"{router_string}/delete") diff --git a/src/lsst/cmservice/client/step_dependencies.py b/src/lsst/cmservice/client/step_dependencies.py index 39e74fc72..eb73bfdf3 100644 --- a/src/lsst/cmservice/client/step_dependencies.py +++ b/src/lsst/cmservice/client/step_dependencies.py @@ -50,9 +50,4 @@ def client(self) -> httpx.Client: f"{router_string}/create", ) - update = wrappers.update_row_function( - ResponseModelClass, - f"{router_string}/update", - ) - delete = wrappers.delete_row_function(f"{router_string}/delete") diff --git a/src/lsst/cmservice/common/daemon.py b/src/lsst/cmservice/common/daemon.py index 0a8cb5855..81089f78c 100644 --- a/src/lsst/cmservice/common/daemon.py +++ b/src/lsst/cmservice/common/daemon.py @@ -1,10 +1,10 @@ from datetime import datetime, timedelta -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.future import select from ..common import notification, timestamp from ..common.enums import StatusEnum +from ..common.types import AnyAsyncSession from ..config import config from ..db.node import NodeMixin from ..db.queue import Queue @@ -15,7 +15,7 @@ logger = LOGGER.bind(module=__name__) -async def check_due_date(session: async_scoped_session, node: NodeMixin, time_next_check: datetime) -> None: +async def check_due_date(session: AnyAsyncSession, node: NodeMixin, time_next_check: datetime) -> None: """For a provided due date, check if the queue entry is overdue""" due_date = node.metadata_.get("due_date", None) @@ -27,7 +27,7 @@ async def check_due_date(session: async_scoped_session, node: NodeMixin, time_ne await notification.send_notification(for_status=StatusEnum.overdue, for_campaign=campaign) -async def daemon_iteration(session: async_scoped_session) -> None: +async def daemon_iteration(session: AnyAsyncSession) -> None: iteration_start = timestamp.now_utc() processed_nodes = 0 queue_entries = await session.execute( diff --git a/src/lsst/cmservice/common/errors.py b/src/lsst/cmservice/common/errors.py index a52489f88..d4b6c6662 100644 --- a/src/lsst/cmservice/common/errors.py +++ b/src/lsst/cmservice/common/errors.py @@ -1,11 +1,9 @@ """cm-service specific error types""" -from typing import Any, TypeVar +from typing import Any from sqlalchemy.exc import IntegrityError -T = TypeVar("T") - class CMCheckError(KeyError): """Raised when script checking fails""" @@ -123,7 +121,7 @@ class CMYamlParseError(KeyError): """Raised when parsing a yaml file fails""" -def test_type_and_raise(object: Any, expected_type: type[T], var_name: str) -> T: +def test_type_and_raise[T](object: Any, expected_type: type[T], var_name: str) -> T: if not isinstance(object, expected_type): raise CMBadParameterTypeError(f"{var_name} expected type {expected_type} got {type(object)}") return object diff --git a/src/lsst/cmservice/common/graph.py b/src/lsst/cmservice/common/graph.py index c994d60ba..f2a1e71e8 100644 --- a/src/lsst/cmservice/common/graph.py +++ b/src/lsst/cmservice/common/graph.py @@ -1,18 +1,19 @@ from collections.abc import Mapping, Sequence -from typing import TypeVar import networkx as nx -from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session from ..db import Script, ScriptDependency, Step, StepDependency from ..parsing.string import parse_element_fullname +from .types import AnyAsyncSession -A = TypeVar("A", async_scoped_session, AsyncSession) -N = TypeVar("N", type[Step], type[Script]) +type AnyGraphEdge = StepDependency | ScriptDependency +type AnyGraphNode = Step | Script async def graph_from_edge_list( - edges: Sequence[StepDependency | ScriptDependency], node_type: N, session: A + edges: Sequence[AnyGraphEdge], + node_type: type[AnyGraphNode], + session: AnyAsyncSession, ) -> nx.DiGraph: """Given a sequence of edge-tuples, create a directed graph for these edges with nodes derived from database lookups of the related objects. diff --git a/src/lsst/cmservice/common/types.py b/src/lsst/cmservice/common/types.py new file mode 100644 index 000000000..7f8ef633d --- /dev/null +++ b/src/lsst/cmservice/common/types.py @@ -0,0 +1,6 @@ +from sqlalchemy.ext.asyncio import AsyncSession as AsyncSessionSA +from sqlalchemy.ext.asyncio import async_scoped_session +from sqlmodel.ext.asyncio.session import AsyncSession + +type AnyAsyncSession = AsyncSession | AsyncSessionSA | async_scoped_session +"""A type union of async database sessions the application may use""" diff --git a/src/lsst/cmservice/daemon.py b/src/lsst/cmservice/daemon.py index 3c242cd3f..4d226ad97 100644 --- a/src/lsst/cmservice/daemon.py +++ b/src/lsst/cmservice/daemon.py @@ -6,7 +6,6 @@ import uvicorn from anyio import current_time, sleep_until from fastapi import FastAPI -from safir.database import create_async_session, create_database_engine from safir.logging import configure_uvicorn_logging from . import __version__ @@ -15,6 +14,7 @@ from .common.logging import LOGGER from .common.panda import get_panda_token from .config import config +from .db.session import db_session_dependency from .routers.healthz import health_router configure_uvicorn_logging(config.logging.level) @@ -31,10 +31,14 @@ async def lifespan(app: FastAPI) -> AsyncGenerator: os.environ |= config.panda.model_dump(by_alias=True, exclude_none=True) os.environ |= config.htcondor.model_dump(by_alias=True, exclude_none=True) app.state.tasks = set() + # Dependency inits before app starts running + await db_session_dependency.initialize() + assert db_session_dependency.engine is not None daemon = create_task(main_loop(app=app), name="daemon") app.state.tasks.add(daemon) yield # stop + await db_session_dependency.aclose() async def main_loop(app: FastAPI) -> None: @@ -43,24 +47,21 @@ async def main_loop(app: FastAPI) -> None: With a database session, perform a single daemon interation and then sleep until the next daemon appointment. """ - engine = create_database_engine(config.db.url, config.db.password) - sleep_time = config.daemon.processing_interval - async with engine.begin(): - session = await create_async_session(engine, logger) - logger.info("Daemon starting.") - _iteration_count = 0 - - while True: - _iteration_count += 1 - logger.info("Daemon starting iteration.") - await daemon_iteration(session) - _iteration_time = current_time() - logger.info(f"Daemon completed {_iteration_count} iterations at {_iteration_time}.") - _next_wakeup = _iteration_time + sleep_time - logger.info(f"Daemon next iteration at {_next_wakeup}.") - await sleep_until(_next_wakeup) + session = await anext(db_session_dependency()) + logger.info("Daemon starting.") + _iteration_count = 0 + + while True: + _iteration_count += 1 + logger.info("Daemon starting iteration.") + await daemon_iteration(session) + _iteration_time = current_time() + logger.info(f"Daemon completed {_iteration_count} iterations at {_iteration_time}.") + _next_wakeup = _iteration_time + sleep_time + logger.info(f"Daemon next iteration at {_next_wakeup}.") + await sleep_until(_next_wakeup) def main() -> None: diff --git a/src/lsst/cmservice/db/campaign.py b/src/lsst/cmservice/db/campaign.py index 559f3a471..0c7d299da 100644 --- a/src/lsst/cmservice/db/campaign.py +++ b/src/lsst/cmservice/db/campaign.py @@ -5,7 +5,6 @@ from sqlalchemy import JSON from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -22,6 +21,7 @@ from .specification import Specification if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .job import Job from .script import Script from .step import Step @@ -94,7 +94,7 @@ def level(self) -> LevelEnum: async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Campaign: """Maps self to self.get_campaign() for consistency""" assert session # For mypy @@ -105,7 +105,7 @@ def __repr__(self) -> str: async def children( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Iterable: """Maps self.s_ to self.children() for consistency""" await session.refresh(self, attribute_names=["s_"]) @@ -113,7 +113,7 @@ async def children( async def get_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedWmsTaskReportDict: the_dict = MergedWmsTaskReportDict(reports={}) @@ -125,7 +125,7 @@ async def get_wms_reports( async def get_tasks( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedTaskSetDict: the_dict = MergedTaskSetDict(reports={}) @@ -136,7 +136,7 @@ async def get_tasks( async def get_products( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedProductSetDict: the_dict = MergedProductSetDict(reports={}) @@ -148,7 +148,7 @@ async def get_products( @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: name = kwargs["name"] diff --git a/src/lsst/cmservice/db/element.py b/src/lsst/cmservice/db/element.py index d96c6b7b7..6e9d82d5c 100644 --- a/src/lsst/cmservice/db/element.py +++ b/src/lsst/cmservice/db/element.py @@ -3,8 +3,6 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any -from sqlalchemy.ext.asyncio import async_scoped_session - from ..common.enums import LevelEnum, NodeTypeEnum, StatusEnum from ..common.errors import CMBadStateTransitionError, CMTooManyActiveScriptsError from ..config import config @@ -14,6 +12,7 @@ from .node import NodeMixin if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .job import Job from .script import Script @@ -37,7 +36,7 @@ def node_type(self) -> NodeTypeEnum: async def get_scripts( self, - session: async_scoped_session, + session: AnyAsyncSession, script_name: str | None = None, *, remaining_only: bool = False, @@ -47,7 +46,7 @@ async def get_scripts( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script_name: str | None @@ -78,7 +77,7 @@ async def get_scripts( async def get_jobs( self, - session: async_scoped_session, + session: AnyAsyncSession, *, remaining_only: bool = False, skip_superseded: bool = True, @@ -87,7 +86,7 @@ async def get_jobs( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager remaining_only: bool @@ -113,7 +112,7 @@ async def get_jobs( async def get_all_scripts( self, - session: async_scoped_session, + session: AnyAsyncSession, *, remaining_only: bool = False, skip_superseded: bool = True, @@ -122,7 +121,7 @@ async def get_all_scripts( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager remaining_only: bool @@ -153,7 +152,7 @@ async def get_all_scripts( async def children( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Iterable: """Maps to [] for consistency""" assert session # for mypy @@ -161,7 +160,7 @@ async def children( async def retry_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script_name: str, *, fake_reset: bool = True, @@ -170,7 +169,7 @@ async def retry_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script_name: str @@ -198,7 +197,7 @@ async def retry_script( async def estimate_sleep_time( self, - session: async_scoped_session, + session: AnyAsyncSession, minimum_sleep_time: int = 10, ) -> int: """Estimate how long to sleep before calling process again. @@ -208,7 +207,7 @@ async def estimate_sleep_time( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -234,14 +233,14 @@ async def estimate_sleep_time( async def get_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedWmsTaskReportDict: """Get the WmwTaskReports associated to this element Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -253,7 +252,7 @@ async def get_wms_reports( async def get_tasks( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedTaskSetDict: """Get the TaskSet associated to this element @@ -270,14 +269,14 @@ async def get_tasks( async def get_products( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedProductSetDict: """Get the ProductSet associated to this element Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -289,7 +288,7 @@ async def get_products( async def review( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> StatusEnum: """Run review() function on this Element @@ -298,7 +297,7 @@ async def review( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/db/group.py b/src/lsst/cmservice/db/group.py index eca383bd7..30780d1c2 100644 --- a/src/lsst/cmservice/db/group.py +++ b/src/lsst/cmservice/db/group.py @@ -4,7 +4,6 @@ from sqlalchemy import JSON from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.exc import IntegrityError -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey, UniqueConstraint @@ -18,6 +17,7 @@ CMTooFewAcceptedJobsError, CMTooManyActiveScriptsError, ) +from ..common.types import AnyAsyncSession from ..models.merged_product_set import MergedProductSetDict from ..models.merged_task_set import MergedTaskSetDict from ..models.merged_wms_task_report import MergedWmsTaskReportDict @@ -85,7 +85,7 @@ def level(self) -> LevelEnum: async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> "Campaign": """Maps self.c_ to self.get_campaign() for consistency""" await session.refresh(self, attribute_names=["c_"]) @@ -96,7 +96,7 @@ def __repr__(self) -> str: async def children( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Iterable: """Maps self.g_ to self.children() for consistency""" await session.refresh(self, attribute_names=["jobs_"]) @@ -104,7 +104,7 @@ async def children( async def get_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedWmsTaskReportDict: the_dict = MergedWmsTaskReportDict(reports={}) @@ -116,7 +116,7 @@ async def get_wms_reports( async def get_tasks( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedTaskSetDict: the_dict = MergedTaskSetDict(reports={}) @@ -127,7 +127,7 @@ async def get_tasks( async def get_products( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedProductSetDict: the_dict = MergedProductSetDict(reports={}) @@ -139,7 +139,7 @@ async def get_products( @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: try: @@ -175,7 +175,7 @@ async def get_create_kwargs( async def rescue_job( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> "Job": """Create a rescue `Job` @@ -183,7 +183,7 @@ async def rescue_job( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -208,13 +208,13 @@ async def rescue_job( async def mark_job_rescued( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> list["Job"]: """Mark jobs as `rescued` once one of their siblings is `accepted` Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/db/handler.py b/src/lsst/cmservice/db/handler.py index 1a213ded0..9753df891 100644 --- a/src/lsst/cmservice/db/handler.py +++ b/src/lsst/cmservice/db/handler.py @@ -3,8 +3,6 @@ import types from typing import TYPE_CHECKING, Any, ClassVar -from sqlalchemy.ext.asyncio import async_scoped_session - from lsst.utils import doImport from lsst.utils.introspection import get_full_type_name @@ -13,6 +11,7 @@ from ..common.logging import LOGGER if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .element import ElementMixin from .node import NodeMixin from .script import Script @@ -93,7 +92,7 @@ def get_handler_class_name(self) -> str: async def process( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -101,7 +100,7 @@ async def process( Parameters ---------- - session : async_scoped_session + session : A DB session manager node: NodeMixin @@ -121,7 +120,7 @@ async def process( async def run_check( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -129,7 +128,7 @@ async def run_check( Parameters ---------- - session : async_scoped_session + session : A DB session manager node: NodeMixin @@ -149,7 +148,7 @@ async def run_check( async def reset( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, to_status: StatusEnum, *, @@ -159,7 +158,7 @@ async def reset( Parameters ---------- - session : async_scoped_session + session : A DB session manager node: NodeMixin @@ -180,7 +179,7 @@ async def reset( async def reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, to_status: StatusEnum, *, @@ -190,7 +189,7 @@ async def reset_script( Parameters ---------- - session : async_scoped_session + session : A DB session manager node: NodeMixin @@ -211,7 +210,7 @@ async def reset_script( async def review( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> StatusEnum: @@ -219,7 +218,7 @@ async def review( Parameters ---------- - session : async_scoped_session + session : A DB session manager element: ElementMixin @@ -234,7 +233,7 @@ async def review( async def review_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -243,7 +242,7 @@ async def review_script( Parameters ---------- - session : async_scoped_session + session : A DB session manager script: Script diff --git a/src/lsst/cmservice/db/job.py b/src/lsst/cmservice/db/job.py index 45a6ab523..53ba44c66 100644 --- a/src/lsst/cmservice/db/job.py +++ b/src/lsst/cmservice/db/job.py @@ -6,7 +6,6 @@ from sqlalchemy import JSON, and_, select from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.exc import IntegrityError -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -27,6 +26,7 @@ from .step import Step if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .campaign import Campaign from .pipetask_error import PipetaskError from .product_set import ProductSet @@ -118,7 +118,7 @@ def level(self) -> LevelEnum: async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Campaign: """Maps self.c_ to self.get_campaign() for consistency""" await session.refresh(self, attribute_names=["c_"]) @@ -126,13 +126,13 @@ async def get_campaign( async def get_siblings( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Sequence[Job]: """Get the sibling Jobs Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -152,7 +152,7 @@ async def get_siblings( async def get_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedWmsTaskReportDict: await session.refresh(self, attribute_names=["wms_reports_"]) @@ -163,7 +163,7 @@ async def get_wms_reports( async def get_tasks( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedTaskSetDict: await session.refresh(self, attribute_names=["tasks_"]) @@ -172,7 +172,7 @@ async def get_tasks( async def get_products( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedProductSetDict: await session.refresh(self, attribute_names=["products_"]) @@ -181,7 +181,7 @@ async def get_products( async def get_errors( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Sequence[PipetaskError]: await session.refresh(self, attribute_names=["errors_"]) return self.errors_ @@ -192,7 +192,7 @@ def __repr__(self) -> str: @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: try: @@ -230,14 +230,14 @@ async def get_create_kwargs( async def copy_job( self, - session: async_scoped_session, + session: AnyAsyncSession, parent: ElementMixin, ) -> Job: """Copy a Job Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager parent : ElementMixin @@ -298,7 +298,7 @@ async def copy_job( async def get_jobs( self, - session: async_scoped_session, + session: AnyAsyncSession, *, remaining_only: bool = False, skip_superseded: bool = True, diff --git a/src/lsst/cmservice/db/node.py b/src/lsst/cmservice/db/node.py index 212f9abf3..244e2f21d 100644 --- a/src/lsst/cmservice/db/node.py +++ b/src/lsst/cmservice/db/node.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any from sqlalchemy.exc import IntegrityError -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.orm.collections import InstrumentedList from ..common import timestamp @@ -27,6 +26,7 @@ from .specification import Specification if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .campaign import Campaign from .element import ElementMixin @@ -60,13 +60,13 @@ class NodeMixin(RowMixin): async def get_spec_block( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> SpecBlock: """Get the `SpecBlock` object associated to a particular row Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -81,13 +81,13 @@ async def get_spec_block( async def get_specification( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Specification: """Get the `Specification` object associated to a particular row Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -101,13 +101,13 @@ async def get_specification( async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Campaign: """Get the parent `Campaign` Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -119,13 +119,13 @@ async def get_campaign( async def get_parent( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> ElementMixin: """Get the parent `Element` Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -140,7 +140,7 @@ async def get_parent( async def get_handler( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Handler: """Get the Handler object associated with a particular row @@ -149,7 +149,7 @@ async def get_handler( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -167,7 +167,7 @@ async def get_handler( async def resolve_collections( self, - session: async_scoped_session, + session: AnyAsyncSession, *, throw_overrides: bool = True, ) -> dict: @@ -181,7 +181,7 @@ async def resolve_collections( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager throw_overrides : bool @@ -221,7 +221,7 @@ async def resolve_collections( async def get_collections( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> dict: """Get the collection configuration associated with a particular row. @@ -230,7 +230,7 @@ async def get_collections( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -262,7 +262,7 @@ async def get_collections( async def get_child_config( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> dict: """Get the child configuration associated with a particular row. @@ -271,7 +271,7 @@ async def get_child_config( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -291,7 +291,7 @@ async def get_child_config( async def data_dict( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> dict: """Get the data configuration associated to a particular row @@ -300,7 +300,7 @@ async def data_dict( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -326,7 +326,7 @@ async def data_dict( async def get_spec_aliases( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> dict: """Get the spec_aliases associated with a particular node @@ -335,7 +335,7 @@ async def get_spec_aliases( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -359,14 +359,14 @@ async def get_spec_aliases( async def update_child_config( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> NodeMixin: """Update the child configuration associated with this Node Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs: Any @@ -402,7 +402,7 @@ async def update_child_config( async def update_collections( self, - session: async_scoped_session, + session: AnyAsyncSession, *, force: bool = False, **kwargs: Any, @@ -411,7 +411,7 @@ async def update_collections( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs: Any @@ -446,14 +446,14 @@ async def update_collections( async def update_spec_aliases( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> NodeMixin: """Update the spec_alisases configuration associated with this Node Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs: Any @@ -489,7 +489,7 @@ async def update_spec_aliases( async def update_metadata_dict( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> NodeMixin: """Update the metadata configuration associated with this Node. @@ -499,7 +499,7 @@ async def update_metadata_dict( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs: Any @@ -534,7 +534,7 @@ async def update_metadata_dict( async def update_data_dict( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> NodeMixin: """Update the data configuration associated with this Node. @@ -544,7 +544,7 @@ async def update_data_dict( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs: Any @@ -601,14 +601,14 @@ async def update_data_dict( async def check_prerequisites( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> bool: """Check if the prerequisties for processing a particular node are completed. Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -628,13 +628,13 @@ async def check_prerequisites( async def reject( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> NodeMixin: """Set a node as rejected Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -656,13 +656,13 @@ async def reject( async def accept( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> NodeMixin: """Set a node as accepted Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -690,7 +690,7 @@ async def accept( async def reset( self, - session: async_scoped_session, + session: AnyAsyncSession, *, fake_reset: bool = False, ) -> NodeMixin: @@ -698,7 +698,7 @@ async def reset( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fake_reset: bool @@ -718,7 +718,7 @@ async def reset( async def _clean_up_node( self, - session: async_scoped_session, + session: AnyAsyncSession, *, fake_reset: bool = False, ) -> NodeMixin: @@ -726,7 +726,7 @@ async def _clean_up_node( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fake_reset: bool @@ -741,7 +741,7 @@ async def _clean_up_node( async def process( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> tuple[bool, StatusEnum]: """Process this `Node` as much as possible @@ -750,7 +750,7 @@ async def process( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -766,7 +766,7 @@ async def process( async def run_check( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> tuple[bool, StatusEnum]: """Check on this Nodes's status @@ -775,7 +775,7 @@ async def run_check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -790,7 +790,7 @@ async def run_check( async def estimate_sleep_time( self, - session: async_scoped_session, + session: AnyAsyncSession, minimum_sleep_time: int = 10, ) -> int: """Estimate how long to sleep before calling process again. @@ -800,7 +800,7 @@ async def estimate_sleep_time( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -815,7 +815,7 @@ async def estimate_sleep_time( async def update_mtime( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> None: """Update the mtime attribute in an element's hierarchy.""" mtime = timestamp.element_time() diff --git a/src/lsst/cmservice/db/queue.py b/src/lsst/cmservice/db/queue.py index d93feed14..9e776b470 100644 --- a/src/lsst/cmservice/db/queue.py +++ b/src/lsst/cmservice/db/queue.py @@ -1,11 +1,10 @@ from __future__ import annotations from datetime import UTC, datetime, timedelta -from typing import Any +from typing import TYPE_CHECKING, Any from sqlalchemy import JSON, and_, select from sqlalchemy.dialects.postgresql import JSONB, TIMESTAMP -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -22,6 +21,10 @@ from .script import Script from .step import Step +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + + logger = LOGGER.bind(module=__name__) @@ -71,13 +74,13 @@ class Queue(Base, NodeMixin): async def get_node( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> NodeMixin: """Get the parent `Node` Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -108,14 +111,14 @@ async def get_node( @classmethod async def get_queue_item( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> Queue: """Get the queue row corresponding to a partiuclar node Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Keywords -------- @@ -157,7 +160,7 @@ async def get_queue_item( @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: fullname = kwargs["fullname"] @@ -200,7 +203,7 @@ async def get_create_kwargs( async def node_sleep_time( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> int: """Check how long to sleep based on what is running""" node = await self.get_node(session) @@ -224,7 +227,7 @@ def waiting( async def process_node( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> bool: # pragma: no cover """Process associated node and update queue row""" node = await self.get_node(session) diff --git a/src/lsst/cmservice/db/row.py b/src/lsst/cmservice/db/row.py index 3210da197..16dce5b25 100644 --- a/src/lsst/cmservice/db/row.py +++ b/src/lsst/cmservice/db/row.py @@ -1,10 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any from sqlalchemy import select from sqlalchemy.exc import IntegrityError -from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session from ..common.enums import StatusEnum from ..common.errors import ( @@ -21,8 +20,7 @@ if TYPE_CHECKING: from collections.abc import Sequence - T = TypeVar("T", bound="RowMixin") - A = TypeVar("A", AsyncSession, async_scoped_session) + from ..common.types import AnyAsyncSession DELETABLE_STATES = [ StatusEnum.failed, @@ -38,23 +36,23 @@ class RowMixin: Defines an interface to manipulate any sort of table. """ - id: Any # Primary Key, typically an int - name: Any # Human-readable name for row fullname: Any # Human-readable unique name for row - + id: Any # Primary Key, typically an int class_string: str # Name to use for help functions and descriptions + col_names_for_table: list + name: Any # Human-readable name for row @classmethod - async def get_rows( + async def get_rows[T: RowMixin]( cls: type[T], - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> Sequence[T]: """Get rows associated to a particular table Parameters ---------- - session : async_scoped_session + session : A DB session manager Keywords @@ -109,16 +107,16 @@ async def get_rows( return results.all() @classmethod - async def get_row( + async def get_row[T: RowMixin]( cls: type[T], - session: A, + session: AnyAsyncSession, row_id: int, ) -> T: """Get a single row, matching row.id == row_id Parameters ---------- - session : async_scoped_session + session : A DB session manager row_id: int @@ -135,16 +133,16 @@ async def get_row( return result @classmethod - async def get_row_by_name( + async def get_row_by_name[T: RowMixin]( cls: type[T], - session: async_scoped_session, + session: AnyAsyncSession, name: str, ) -> T: """Get a single row, with row.name == name Parameters ---------- - session : async_scoped_session + session : A DB session manager name : str @@ -163,16 +161,16 @@ async def get_row_by_name( return row @classmethod - async def get_row_by_fullname( + async def get_row_by_fullname[T: RowMixin]( cls: type[T], - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, ) -> T: """Get a single row, with row.fullname == fullname Parameters ---------- - session : async_scoped_session + session : A DB session manager fullname : str @@ -193,14 +191,14 @@ async def get_row_by_fullname( @classmethod async def delete_row( cls, - session: async_scoped_session, + session: AnyAsyncSession, row_id: int, ) -> None: """Delete a single row, matching row.id == row_id Parameters ---------- - session : async_scoped_session + session : A DB session manager row_id: int @@ -231,14 +229,14 @@ async def delete_row( @classmethod async def _delete_hook( cls, - session: async_scoped_session, + session: AnyAsyncSession, row_id: int, ) -> None: """Hook called during delete_row Parameters ---------- - session : async_scoped_session + session : A DB session manager row_id: int @@ -250,9 +248,9 @@ async def _delete_hook( return @classmethod - async def update_row( + async def update_row[T: RowMixin]( cls: type[T], - session: async_scoped_session, + session: AnyAsyncSession, row_id: int, **kwargs: Any, ) -> T: @@ -260,7 +258,7 @@ async def update_row( Parameters ---------- - session : async_scoped_session + session : A DB session manager row_id: int @@ -311,16 +309,16 @@ async def update_row( return row @classmethod - async def create_row( + async def create_row[T: RowMixin]( cls: type[T], - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> T: """Create a single row Parameters ---------- - session : async_scoped_session + session : A DB session manager kwargs: Any @@ -346,8 +344,8 @@ async def create_row( @classmethod async def get_create_kwargs( - cls: type[T], - session: async_scoped_session, + cls, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: """Get additional keywords needed to create a row @@ -358,7 +356,7 @@ async def get_create_kwargs( Parameters ---------- - session : async_scoped_session + session : A DB session manager kwargs: Any @@ -371,16 +369,16 @@ async def get_create_kwargs( """ return kwargs - async def update_values( + async def update_values[T: RowMixin]( self: T, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> T: """Update values in a row Parameters ---------- - session : async_scoped_session + session : A DB session manager kwargs: Any diff --git a/src/lsst/cmservice/db/script.py b/src/lsst/cmservice/db/script.py index 26187b555..82646a1ef 100644 --- a/src/lsst/cmservice/db/script.py +++ b/src/lsst/cmservice/db/script.py @@ -4,7 +4,6 @@ from sqlalchemy import JSON from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -25,6 +24,7 @@ from .step import Step if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .script_error import ScriptError @@ -111,14 +111,14 @@ def level(self) -> LevelEnum: async def get_script_errors( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> list[ScriptError]: await session.refresh(self, attribute_names=["errors_"]) return self.errors_ async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Campaign: """Maps self.get_parent().c_ to self.get_campaign() for consistency""" parent = await self.get_parent(session) @@ -131,13 +131,13 @@ def node_type(self) -> NodeTypeEnum: async def get_parent( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> ElementMixin: """Get the parent `Element` Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns @@ -165,7 +165,7 @@ async def get_parent( @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: try: @@ -229,7 +229,7 @@ async def get_create_kwargs( async def reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, to_status: StatusEnum, *, fake_reset: bool = False, @@ -243,7 +243,7 @@ async def reset_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager to_status : StatusEnum @@ -262,7 +262,7 @@ async def reset_script( async def review( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> StatusEnum: """Run review() function on this Script @@ -272,7 +272,7 @@ async def review( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/db/script_dependency.py b/src/lsst/cmservice/db/script_dependency.py index 2ade4c254..e9ac58f75 100644 --- a/src/lsst/cmservice/db/script_dependency.py +++ b/src/lsst/cmservice/db/script_dependency.py @@ -4,7 +4,6 @@ from uuid import UUID import sqlalchemy.dialects.postgresql as sapg -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -13,6 +12,7 @@ from .row import RowMixin if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .script import Script @@ -41,13 +41,13 @@ def __repr__(self) -> str: async def is_done( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> bool: """Check if this dependency is completed Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/db/session.py b/src/lsst/cmservice/db/session.py index 1141f03e1..294fe755d 100644 --- a/src/lsst/cmservice/db/session.py +++ b/src/lsst/cmservice/db/session.py @@ -1,9 +1,93 @@ """Module to create and handle async database sessions""" -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session +from collections.abc import AsyncGenerator +# from pydantic import SecretStr #noqa: ERA001 +from sqlalchemy import make_url +from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine +from sqlmodel.ext.asyncio.session import AsyncSession -async def get_async_scoped_session() -> async_scoped_session: - """Provides an async session from the safir session maker.""" +from ..config import config + + +class DatabaseSessionDependency: + """A database session manager class designed to manage an async sqlalchemy + engine and produce sessions. + + A module-level instance of this class is created, and when called, a new + async session is yielded. + """ + + def __init__(self) -> None: + self.engine: AsyncEngine | None = None + self.sessionmaker: async_sessionmaker[AsyncSession] | None = None + + async def initialize( + self, + *, + isolation_level: str | None = None, + use_async: bool = True, + echo: bool = False, + ) -> None: + """Initialize the session dependency. + + Parameters + ---------- + url + Database connection URL, not including the password. + password + Database connection password. + isolation_level + If specified, sets a non-default isolation level for the database + engine. + use_async + If true (default), the database drivername will be forced to an + async form. + """ + if isinstance(config.db.url, str): + url = make_url(config.db.url) + if use_async and url.drivername == "postgresql": + url = url.set(drivername="postgresql+asyncpg") + # FIXME use SecretStr for password + # if isinstance(config.db.password, SecretStr): + # password = config.db.password.get_secret_value() #noqa: ERA001 + if config.db.password is not None: + url = url.set(password=config.db.password) + if self.engine: + await self.engine.dispose() + self.engine = create_async_engine( + url=url, + echo=config.db.echo, + # TODO add pool-level configs + ) + self.sessionmaker = async_sessionmaker(self.engine, class_=AsyncSession, expire_on_commit=False) + + async def __call__(self) -> AsyncGenerator[AsyncSession]: + """Yields a database session. + + Yields + ------- + sqlmodel.ext.asyncio.AsyncSession + The newly-created session. + """ + if not self.sessionmaker: + raise RuntimeError("Async sessionmaker is not initialized") + + async with self.sessionmaker() as session: + yield session + + async def aclose(self) -> None: + """Shut down the database engine.""" + if self.engine: + self.sessionmaker = None + await self.engine.dispose() + self._engine = None + + +db_session_dependency = DatabaseSessionDependency() +"""A module-level instance of the session manager""" + + +# FIXME not sure why this pattern +async def get_async_session() -> AsyncSession: return await anext(db_session_dependency()) diff --git a/src/lsst/cmservice/db/spec_block.py b/src/lsst/cmservice/db/spec_block.py index 57a1856e7..5a135c645 100644 --- a/src/lsst/cmservice/db/spec_block.py +++ b/src/lsst/cmservice/db/spec_block.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any from sqlalchemy import JSON -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import Mapped, mapped_column @@ -11,6 +10,9 @@ from .handler import Handler from .row import RowMixin +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + class SpecBlock(Base, RowMixin): """Database table to manage blocks that are used to build campaigns @@ -45,7 +47,7 @@ def __repr__(self) -> str: @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: handler = kwargs["handler"] @@ -64,7 +66,7 @@ async def get_create_kwargs( @classmethod async def _delete_hook( cls, - session: async_scoped_session, + session: AnyAsyncSession, row_id: int, ) -> None: Handler.remove_from_cache(row_id) diff --git a/src/lsst/cmservice/db/specification.py b/src/lsst/cmservice/db/specification.py index 6a7eee6d2..824cacc02 100644 --- a/src/lsst/cmservice/db/specification.py +++ b/src/lsst/cmservice/db/specification.py @@ -1,7 +1,8 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from sqlalchemy import JSON -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import Mapped, mapped_column @@ -10,6 +11,9 @@ from .row import RowMixin from .spec_block import SpecBlock +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + class Specification(Base, RowMixin): """Database table to manage mapping and grouping SpecBlocks @@ -38,14 +42,14 @@ def __repr__(self) -> str: async def get_block( self, - session: async_scoped_session, + session: AnyAsyncSession, spec_block_name: str, ) -> SpecBlock: """Get a SpecBlock associated to this Specification Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager spec_block_name: str diff --git a/src/lsst/cmservice/db/step.py b/src/lsst/cmservice/db/step.py index 5581ca682..d58e31ec5 100644 --- a/src/lsst/cmservice/db/step.py +++ b/src/lsst/cmservice/db/step.py @@ -5,7 +5,6 @@ from sqlalchemy import JSON from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.ext.mutable import MutableDict from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey, UniqueConstraint @@ -22,6 +21,7 @@ from .spec_block import SpecBlock if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .group import Group from .job import Job from .script import Script @@ -94,7 +94,7 @@ def __repr__(self) -> str: async def get_campaign( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Campaign: """Maps self.c_ to self.get_campaign() for consistency""" await session.refresh(self, attribute_names=["parent_"]) @@ -102,7 +102,7 @@ async def get_campaign( async def children( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> Iterable: """Maps self.g_ to self.children() for consistency""" await session.refresh(self, attribute_names=["g_"]) @@ -110,7 +110,7 @@ async def children( async def get_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedWmsTaskReportDict: the_dict = MergedWmsTaskReportDict(reports={}) @@ -122,7 +122,7 @@ async def get_wms_reports( async def get_tasks( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedTaskSetDict: the_dict = MergedTaskSetDict(reports={}) @@ -133,7 +133,7 @@ async def get_tasks( async def get_products( self, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> MergedProductSetDict: the_dict = MergedProductSetDict(reports={}) @@ -144,7 +144,7 @@ async def get_products( async def get_all_prereqs( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> list[Step]: all_prereqs: list[Step] = [] await session.refresh(self, attribute_names=["prereqs_"]) @@ -158,7 +158,7 @@ async def get_all_prereqs( @classmethod async def get_create_kwargs( cls, - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> dict: try: diff --git a/src/lsst/cmservice/db/step_dependency.py b/src/lsst/cmservice/db/step_dependency.py index 835214b90..063606b2a 100644 --- a/src/lsst/cmservice/db/step_dependency.py +++ b/src/lsst/cmservice/db/step_dependency.py @@ -4,7 +4,6 @@ from uuid import UUID import sqlalchemy.dialects.postgresql as sapg -from sqlalchemy.ext.asyncio import async_scoped_session from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -13,6 +12,7 @@ from .row import RowMixin if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from .step import Step @@ -41,13 +41,13 @@ def __repr__(self) -> str: async def is_done( self, - session: async_scoped_session, + session: AnyAsyncSession, ) -> bool: """Check if this dependency is completed Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/handlers/element_handler.py b/src/lsst/cmservice/handlers/element_handler.py index aae1cfdd4..73527f015 100644 --- a/src/lsst/cmservice/handlers/element_handler.py +++ b/src/lsst/cmservice/handlers/element_handler.py @@ -4,8 +4,6 @@ from typing import TYPE_CHECKING, Any from uuid import UUID, uuid5 -from sqlalchemy.ext.asyncio import async_scoped_session - from ..common.enums import LevelEnum, StatusEnum from ..common.errors import CMYamlParseError, test_type_and_raise from ..common.notification import send_notification @@ -18,6 +16,9 @@ from ..db.script_dependency import ScriptDependency from .functions import render_campaign_steps +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + class ElementHandler(Handler): """SubClass of Handler to deal with generic 'Element' operations, @@ -26,7 +27,7 @@ class ElementHandler(Handler): @staticmethod async def _add_prerequisite( - session: async_scoped_session, + session: AnyAsyncSession, script_id: int, prereq_id: int, namespace: UUID | None = None, @@ -35,7 +36,7 @@ async def _add_prerequisite( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script_id: int @@ -59,7 +60,7 @@ async def _add_prerequisite( async def process( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -67,7 +68,7 @@ async def process( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager node: NodeMixin @@ -116,7 +117,7 @@ async def process( async def run_check( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -126,7 +127,7 @@ async def run_check( async def prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, ) -> tuple[bool, StatusEnum]: """Prepare `Element` for processing @@ -135,7 +136,7 @@ async def prepare( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -218,7 +219,7 @@ async def prepare( async def continue_processing( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -228,7 +229,7 @@ async def continue_processing( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -253,7 +254,7 @@ async def continue_processing( async def review( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> StatusEnum: @@ -265,7 +266,7 @@ async def review( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -282,7 +283,7 @@ async def review( async def _run_script_checks( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> bool: @@ -290,7 +291,7 @@ async def _run_script_checks( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -320,7 +321,7 @@ async def _run_script_checks( async def _run_job_checks( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> bool: @@ -328,7 +329,7 @@ async def _run_job_checks( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -357,7 +358,7 @@ async def _run_job_checks( async def check( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -366,7 +367,7 @@ async def check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -424,7 +425,7 @@ async def check( async def _post_check( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> StatusEnum: @@ -432,7 +433,7 @@ async def _post_check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin @@ -451,7 +452,7 @@ class CampaignHandler(ElementHandler): async def prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, ) -> tuple[bool, StatusEnum]: if TYPE_CHECKING: @@ -466,7 +467,7 @@ async def prepare( async def _post_check( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> StatusEnum: @@ -474,7 +475,7 @@ async def _post_check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element: ElementMixin diff --git a/src/lsst/cmservice/handlers/elements.py b/src/lsst/cmservice/handlers/elements.py index 9237b5430..567cc871b 100644 --- a/src/lsst/cmservice/handlers/elements.py +++ b/src/lsst/cmservice/handlers/elements.py @@ -2,11 +2,10 @@ from collections.abc import AsyncGenerator from functools import partial -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np from anyio import to_thread -from sqlalchemy.ext.asyncio import async_scoped_session from ..common.butler import BUTLER_FACTORY from ..common.enums import StatusEnum @@ -20,6 +19,10 @@ from ..db.script import Script from .script_handler import FunctionHandler +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + + logger = LOGGER.bind(module=__name__) @@ -32,7 +35,7 @@ class RunElementScriptHandler(FunctionHandler): async def _do_run( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -49,7 +52,7 @@ async def _do_run( async def _do_check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -78,7 +81,7 @@ class RunJobsScriptHandler(RunElementScriptHandler): async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -102,7 +105,7 @@ async def _do_prepare( async def review_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -126,7 +129,7 @@ class Splitter: @classmethod async def split( cls, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -135,7 +138,7 @@ async def split( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager script: Script @@ -157,7 +160,7 @@ class NoSplit(Splitter): @classmethod async def split( cls, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -177,7 +180,7 @@ class SplitByVals(Splitter): @classmethod async def split( cls, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -203,7 +206,7 @@ class SplitByQuery(Splitter): @classmethod async def split( cls, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -281,7 +284,7 @@ class RunGroupsScriptHandler(RunElementScriptHandler): async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -320,7 +323,7 @@ class RunStepsScriptHandler(RunElementScriptHandler): async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, diff --git a/src/lsst/cmservice/handlers/functions.py b/src/lsst/cmservice/handlers/functions.py index ad25ccb32..ff6c93e51 100644 --- a/src/lsst/cmservice/handlers/functions.py +++ b/src/lsst/cmservice/handlers/functions.py @@ -9,7 +9,6 @@ from anyio import Path from pydantic.v1.utils import deep_update from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session from lsst.ctrl.bps.bps_reports import compile_job_summary from lsst.ctrl.bps.wms_service import WmsRunReport, WmsStates @@ -17,6 +16,7 @@ from ..common.enums import DEFAULT_NAMESPACE, StatusEnum from ..common.errors import CMMissingFullnameError, CMYamlParseError from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession from ..config import config from ..db.campaign import Campaign from ..db.element import ElementMixin @@ -24,7 +24,7 @@ from ..db.pipetask_error import PipetaskError from ..db.pipetask_error_type import PipetaskErrorType from ..db.product_set import ProductSet -from ..db.session import get_async_scoped_session +from ..db.session import get_async_session from ..db.spec_block import SpecBlock from ..db.specification import Specification from ..db.step import Step @@ -36,7 +36,7 @@ async def upsert_spec_block( - session: async_scoped_session, + session: AnyAsyncSession, config_values: dict, loaded_specs: dict, *, @@ -48,7 +48,7 @@ async def upsert_spec_block( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager config_values: dict @@ -127,7 +127,7 @@ async def upsert_spec_block( async def upsert_specification( - session: async_scoped_session, + session: AnyAsyncSession, config_values: dict, *, allow_update: bool = False, @@ -138,7 +138,7 @@ async def upsert_specification( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager config_values: dict @@ -166,7 +166,7 @@ async def upsert_specification( async def load_specification( - session: async_scoped_session, + session: AnyAsyncSession, yaml_file: str | Path | deque, loaded_specs: dict | None = None, *, @@ -178,7 +178,7 @@ async def load_specification( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager yaml_file: str | anyio.Path @@ -259,13 +259,13 @@ async def load_specification( async def add_step_prerequisite( - session: async_scoped_session, depend_id: int, prereq_id: int, namespace: UUID | None = None + session: AnyAsyncSession, depend_id: int, prereq_id: int, namespace: UUID | None = None ) -> StepDependency: """Create and return a StepDependency Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager depend_id: int @@ -288,7 +288,7 @@ async def add_step_prerequisite( async def add_steps( - session: async_scoped_session, + session: AnyAsyncSession, campaign: Campaign, step_config_list: list[dict[str, dict]] | None, ) -> Campaign: @@ -296,7 +296,7 @@ async def add_steps( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager campaign: Campaign @@ -386,14 +386,14 @@ async def force_accept_node( node: int, db_class: type[ElementMixin], output_collection: str | None = None, - session: async_scoped_session | None = None, + session: AnyAsyncSession | None = None, ) -> None: """Force accept a node by bypassing state transition checks and setting node and node's scripts to accepted. """ local_session = False if session is None: - session = await get_async_scoped_session() + session = await get_async_session() local_session = True the_node = await db_class.get_row(session, node) @@ -431,7 +431,7 @@ async def force_accept_node( async def render_campaign_steps( campaign: Campaign | int, - session: async_scoped_session | None = None, + session: AnyAsyncSession | None = None, ) -> None: """Render the steps for a campaign. @@ -444,7 +444,7 @@ async def render_campaign_steps( """ local_session = False if session is None: - session = await get_async_scoped_session() + session = await get_async_session() local_session = True if isinstance(campaign, int): campaign = await Campaign.get_row(session, campaign) @@ -466,7 +466,7 @@ async def render_campaign_steps( async def match_pipetask_error( - session: async_scoped_session, + session: AnyAsyncSession, task_name: str, diagnostic_message: str, ) -> PipetaskErrorType | None: @@ -474,7 +474,7 @@ async def match_pipetask_error( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager task_name: str @@ -495,7 +495,7 @@ async def match_pipetask_error( async def load_manifest_report( - session: async_scoped_session, + session: AnyAsyncSession, job_name: str, yaml_file: str | Path, fake_status: StatusEnum | None = None, @@ -506,7 +506,7 @@ async def load_manifest_report( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager job_name: str @@ -715,7 +715,7 @@ def status_from_bps_report( async def load_wms_reports( - session: async_scoped_session, + session: AnyAsyncSession, job: Job, wms_run_report: WmsRunReport | None, ) -> Job: # pragma: no cover @@ -725,7 +725,7 @@ async def load_wms_reports( Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager job_name: str @@ -764,14 +764,14 @@ async def load_wms_reports( async def load_error_types( - session: async_scoped_session, + session: AnyAsyncSession, yaml_file: str | Path, ) -> list[PipetaskErrorType]: """Parse and load error types Parameters ---------- - session: async_scoped_session + session: AnyAsyncSession DB session manager yaml_file: str | anyio.Path @@ -810,7 +810,7 @@ async def load_error_types( async def compute_job_status( - session: async_scoped_session, + session: AnyAsyncSession, job: Job, ) -> StatusEnum: await session.refresh( diff --git a/src/lsst/cmservice/handlers/interface.py b/src/lsst/cmservice/handlers/interface.py index 7c0cb8e1c..8a69b98c1 100644 --- a/src/lsst/cmservice/handlers/interface.py +++ b/src/lsst/cmservice/handlers/interface.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Any from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db from ..common.enums import LevelEnum, NodeTypeEnum, StatusEnum, TableEnum @@ -13,6 +12,7 @@ test_type_and_raise, ) from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession from . import functions TABLE_DICT: dict[TableEnum, type[db.RowMixin]] = { @@ -64,7 +64,7 @@ def get_table( async def get_row_by_table_and_id( - session: async_scoped_session, + session: AnyAsyncSession, row_id: int, table_enum: TableEnum, ) -> db.RowMixin: @@ -72,7 +72,7 @@ async def get_row_by_table_and_id( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager row_id: int @@ -105,7 +105,7 @@ async def get_row_by_table_and_id( async def get_node_by_level_and_id( - session: async_scoped_session, + session: AnyAsyncSession, element_id: int, level: LevelEnum, ) -> db.NodeMixin: @@ -113,7 +113,7 @@ async def get_node_by_level_and_id( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager element_id: int @@ -164,14 +164,14 @@ def get_node_type_by_fullname( async def get_element_by_fullname( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, ) -> db.ElementMixin: """Get a `Element` from the DB Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -208,14 +208,14 @@ async def get_element_by_fullname( async def get_node_by_fullname( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, ) -> db.NodeMixin: """Get a `Node` from the DB Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -241,7 +241,7 @@ async def get_node_by_fullname( async def process_script( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, fake_status: StatusEnum | None = None, ) -> tuple[bool, StatusEnum]: @@ -249,7 +249,7 @@ async def process_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -276,7 +276,7 @@ async def process_script( async def process_element( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, fake_status: StatusEnum | None = None, ) -> tuple[bool, StatusEnum]: @@ -284,7 +284,7 @@ async def process_element( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -313,7 +313,7 @@ async def process_element( async def process( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, fake_status: StatusEnum | None = None, ) -> tuple[bool, StatusEnum]: @@ -321,7 +321,7 @@ async def process( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -356,7 +356,7 @@ async def process( async def reset_script( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, status: StatusEnum, *, @@ -374,7 +374,7 @@ async def reset_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -403,7 +403,7 @@ async def reset_script( async def rescue_job( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, ) -> db.Job: """Run a rescue on a `Job` @@ -414,7 +414,7 @@ async def rescue_job( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -441,7 +441,7 @@ async def rescue_job( async def mark_job_rescued( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, ) -> list[db.Job]: """Mark a `Job` as rescued @@ -452,7 +452,7 @@ async def mark_job_rescued( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -481,14 +481,14 @@ async def mark_job_rescued( async def create_campaign( - session: async_scoped_session, + session: AnyAsyncSession, **kwargs: Any, ) -> db.Campaign: """Create a new Campaign Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager kwargs : Any @@ -504,7 +504,7 @@ async def create_campaign( async def load_and_create_campaign( - session: async_scoped_session, + session: AnyAsyncSession, yaml_file: str, name: str, spec_block_assoc_name: str | None = None, @@ -514,7 +514,7 @@ async def load_and_create_campaign( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager yaml_file: str @@ -552,7 +552,7 @@ async def load_and_create_campaign( async def add_steps( - session: async_scoped_session, + session: AnyAsyncSession, fullname: str, child_configs: list[dict[str, Any]], ) -> db.Campaign: @@ -560,7 +560,7 @@ async def add_steps( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager fullname: str @@ -586,14 +586,14 @@ async def add_steps( async def load_error_types( - session: async_scoped_session, + session: AnyAsyncSession, yaml_file: str, ) -> list[db.PipetaskErrorType]: """Load a set of `PipetaskErrorType`s from a yaml file Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager yaml_file: str, @@ -609,7 +609,7 @@ async def load_error_types( async def load_manifest_report( - session: async_scoped_session, + session: AnyAsyncSession, yaml_file: str, fullname: str, *, @@ -619,7 +619,7 @@ async def load_manifest_report( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager yaml_file: str, @@ -641,7 +641,7 @@ async def load_manifest_report( async def match_pipetask_errors( - session: async_scoped_session, + session: AnyAsyncSession, *, rematch: bool = False, ) -> list[db.PipetaskError]: @@ -651,7 +651,7 @@ async def match_pipetask_errors( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager rematch: bool diff --git a/src/lsst/cmservice/handlers/job_handler.py b/src/lsst/cmservice/handlers/job_handler.py index c033e38cc..4224ba9d9 100644 --- a/src/lsst/cmservice/handlers/job_handler.py +++ b/src/lsst/cmservice/handlers/job_handler.py @@ -2,14 +2,13 @@ from typing import TYPE_CHECKING, Any -from sqlalchemy.ext.asyncio import async_scoped_session - from ..common.enums import ErrorActionEnum, StatusEnum from ..common.errors import CMBadEnumError from ..db.element import ElementMixin from .element_handler import ElementHandler if TYPE_CHECKING: + from ..common.types import AnyAsyncSession from ..db import Job @@ -18,7 +17,7 @@ class JobHandler(ElementHandler): async def _post_check( self, - session: async_scoped_session, + session: AnyAsyncSession, element: ElementMixin, **kwargs: Any, ) -> StatusEnum: diff --git a/src/lsst/cmservice/handlers/jobs.py b/src/lsst/cmservice/handlers/jobs.py index 0f735b704..7f6ff80ed 100644 --- a/src/lsst/cmservice/handlers/jobs.py +++ b/src/lsst/cmservice/handlers/jobs.py @@ -10,7 +10,6 @@ from anyio import Path, to_thread from fastapi.concurrency import run_in_threadpool from jinja2 import Environment, PackageLoader -from sqlalchemy.ext.asyncio import async_scoped_session from lsst.ctrl.bps import BaseWmsService, WmsRunReport, WmsStates from lsst.utils import doImport @@ -36,6 +35,10 @@ from .functions import compute_job_status, load_manifest_report, load_wms_reports, status_from_bps_report from .script_handler import FunctionHandler, ScriptHandler +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + + WMS_TO_TASK_STATUS_MAP = { WmsStates.UNKNOWN: TaskStatusEnum.missing, WmsStates.MISFIT: TaskStatusEnum.missing, @@ -64,7 +67,7 @@ class BpsScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -149,7 +152,7 @@ async def _write_script( # unless they are unique to the submission and separated for # readability. The use of any kind of "shared" or "global" config # items breaks provenance for all campaigns that reference them. - bps_wms_extra_files = data_dict.get("bps_wms_extra_files", []) + bps_wms_extra_files: list = data_dict.get("bps_wms_extra_files", []) bps_wms_clustering_file = data_dict.get("bps_wms_clustering_file", None) bps_wms_resources_file = data_dict.get("bps_wms_resources_file", None) bps_wms_yaml_file = data_dict.get("bps_wms_yaml_file", None) @@ -233,7 +236,7 @@ async def _write_script( async def _check_slurm_job( self, - session: async_scoped_session, + session: AnyAsyncSession, slurm_id: str | None, script: Script, parent: ElementMixin, @@ -263,7 +266,7 @@ async def _check_slurm_job( async def _check_htcondor_job( self, - session: async_scoped_session, + session: AnyAsyncSession, htcondor_id: str | None, script: Script, parent: ElementMixin, @@ -303,7 +306,7 @@ async def _check_htcondor_job( async def launch( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -330,7 +333,7 @@ def get_bps_submit_dir(cls, bps_dict: dict) -> str | None: async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -358,7 +361,7 @@ async def _reset_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -407,7 +410,7 @@ def _get_wms_svc(self, **kwargs: Any) -> BaseWmsService | None: async def _load_wms_reports( self, - session: async_scoped_session, + session: AnyAsyncSession, job: Job, wms_workflow_id: str | None, **kwargs: Any, @@ -462,7 +465,7 @@ async def _load_wms_reports( async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin | Job, **kwargs: Any, @@ -478,7 +481,7 @@ async def _do_prepare( async def _do_check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin | Job, **kwargs: Any, @@ -497,7 +500,7 @@ async def _do_check( async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -564,7 +567,7 @@ class ManifestReportScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -616,7 +619,7 @@ class ManifestReportLoadHandler(FunctionHandler): async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -644,7 +647,7 @@ async def _do_prepare( async def _do_check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin | Job, **kwargs: Any, @@ -661,7 +664,7 @@ async def _do_check( async def _load_pipetask_report( self, - session: async_scoped_session, + session: AnyAsyncSession, job: Job, pipetask_report_yaml: str, fake_status: StatusEnum | None = None, @@ -688,7 +691,7 @@ async def _load_pipetask_report( async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, diff --git a/src/lsst/cmservice/handlers/script_handler.py b/src/lsst/cmservice/handlers/script_handler.py index 57ac71484..6429fc958 100644 --- a/src/lsst/cmservice/handlers/script_handler.py +++ b/src/lsst/cmservice/handlers/script_handler.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Any from anyio import Path -from sqlalchemy.ext.asyncio import async_scoped_session from ..common.bash import check_stamp_file, get_diagnostic_message, run_bash_job from ..common.enums import ErrorSourceEnum, ScriptMethodEnum, StatusEnum @@ -26,6 +25,10 @@ from ..db.script import Script from ..db.script_error import ScriptError +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + + logger = LOGGER.bind(module=__name__) DOUBLE_QUOTE = '"' @@ -36,7 +39,7 @@ class BaseScriptHandler(Handler): async def process( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -129,7 +132,7 @@ async def process( async def run_check( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, **kwargs: Any, ) -> tuple[bool, StatusEnum]: @@ -145,7 +148,7 @@ async def run_check( async def prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, ) -> StatusEnum: @@ -157,7 +160,7 @@ async def prepare( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -178,7 +181,7 @@ async def prepare( async def launch( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -191,7 +194,7 @@ async def launch( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -209,7 +212,7 @@ async def launch( async def check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -222,7 +225,7 @@ async def check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -240,7 +243,7 @@ async def check( async def review_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -253,7 +256,7 @@ async def review_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -272,7 +275,7 @@ async def review_script( async def reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, node: NodeMixin, to_status: StatusEnum, *, @@ -305,7 +308,7 @@ async def reset_script( async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -315,7 +318,7 @@ async def _reset_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -325,7 +328,7 @@ async def _purge_products( async def update_status( self, - session: async_scoped_session, + session: AnyAsyncSession, status: StatusEnum, node: NodeMixin, **kwargs: Any, @@ -352,7 +355,7 @@ class ScriptHandler(BaseScriptHandler): @staticmethod async def _check_stamp_file( - session: async_scoped_session, + session: AnyAsyncSession, stamp_file: str | None, script: Script, parent: ElementMixin, @@ -362,7 +365,7 @@ async def _check_stamp_file( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager stamp_file: str | None @@ -390,7 +393,7 @@ async def _check_stamp_file( async def _check_slurm_job( self, - session: async_scoped_session, + session: AnyAsyncSession, slurm_id: str | None, script: Script, parent: ElementMixin, @@ -400,7 +403,7 @@ async def _check_slurm_job( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager slurm_id : str @@ -426,7 +429,7 @@ async def _check_slurm_job( async def _check_htcondor_job( self, - session: async_scoped_session, + session: AnyAsyncSession, htcondor_id: str | None, script: Script, parent: ElementMixin, @@ -436,7 +439,7 @@ async def _check_htcondor_job( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager htcondor_id : str | None @@ -462,7 +465,7 @@ async def _check_htcondor_job( async def prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -486,7 +489,7 @@ async def prepare( async def launch( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -535,7 +538,7 @@ async def launch( async def check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -581,7 +584,7 @@ async def check( async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -590,7 +593,7 @@ async def _write_script( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -608,7 +611,7 @@ async def _write_script( async def _set_script_files( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, prod_area: str | Path, ) -> str: @@ -620,7 +623,7 @@ async def _set_script_files( async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -649,7 +652,7 @@ class FunctionHandler(BaseScriptHandler): async def prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -667,7 +670,7 @@ async def prepare( async def launch( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -686,7 +689,7 @@ async def launch( async def check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -703,7 +706,7 @@ async def check( async def _do_prepare( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -712,7 +715,7 @@ async def _do_prepare( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -730,7 +733,7 @@ async def _do_prepare( async def _do_run( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -739,7 +742,7 @@ async def _do_run( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -757,7 +760,7 @@ async def _do_run( async def _do_check( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -766,7 +769,7 @@ async def _do_check( Parameters ---------- - session : async_scoped_session + session : AnyAsyncSession DB session manager script: Script @@ -784,7 +787,7 @@ async def _do_check( async def _reset_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, diff --git a/src/lsst/cmservice/handlers/scripts.py b/src/lsst/cmservice/handlers/scripts.py index 3b5284270..575ea3883 100644 --- a/src/lsst/cmservice/handlers/scripts.py +++ b/src/lsst/cmservice/handlers/scripts.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any from anyio import Path -from sqlalchemy.ext.asyncio import async_scoped_session from ..common.bash import write_bash_script from ..common.butler import ( @@ -23,6 +22,10 @@ from ..db.step import Step from .script_handler import ScriptHandler +if TYPE_CHECKING: + from ..common.types import AnyAsyncSession + + logger = LOGGER.bind(module=__name__) @@ -31,7 +34,7 @@ class NullScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -58,7 +61,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -82,7 +85,7 @@ class ChainCreateScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -117,7 +120,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -148,7 +151,7 @@ class ChainPrependScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -178,7 +181,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -210,7 +213,7 @@ class ChainCollectScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -257,7 +260,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -288,7 +291,7 @@ class TagInputsScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -318,7 +321,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -345,7 +348,7 @@ class TagCreateScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -371,7 +374,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -400,7 +403,7 @@ class TagAssociateScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -428,7 +431,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -462,7 +465,7 @@ class PrepareStepScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -505,7 +508,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -527,7 +530,7 @@ class ResourceUsageScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -557,7 +560,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -584,7 +587,7 @@ class HipsMapsScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -649,7 +652,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, @@ -683,7 +686,7 @@ class ValidateScriptHandler(ScriptHandler): async def _write_script( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, parent: ElementMixin, **kwargs: Any, @@ -710,7 +713,7 @@ async def _write_script( async def _purge_products( self, - session: async_scoped_session, + session: AnyAsyncSession, script: Script, to_status: StatusEnum, *, diff --git a/src/lsst/cmservice/main.py b/src/lsst/cmservice/main.py index 5b53eec81..b1d4f8460 100644 --- a/src/lsst/cmservice/main.py +++ b/src/lsst/cmservice/main.py @@ -3,13 +3,13 @@ import uvicorn from fastapi import FastAPI -from safir.dependencies.db_session import db_session_dependency from safir.dependencies.http_client import http_client_dependency from safir.logging import configure_logging, configure_uvicorn_logging from safir.middleware.x_forwarded import XForwardedMiddleware from . import __version__ from .config import config +from .db.session import db_session_dependency from .routers import ( healthz, index, @@ -96,9 +96,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator: """Hook FastAPI init/cleanups.""" app.state.tasks = set() # Dependency inits before app starts running - await db_session_dependency.initialize(config.db.url, config.db.password) - assert db_session_dependency._engine is not None - db_session_dependency._engine.echo = config.db.echo + await db_session_dependency.initialize() + assert db_session_dependency.engine is not None # App runs here... yield diff --git a/src/lsst/cmservice/routers/actions.py b/src/lsst/cmservice/routers/actions.py index a5840cd91..a199b3230 100644 --- a/src/lsst/cmservice/routers/actions.py +++ b/src/lsst/cmservice/routers/actions.py @@ -1,11 +1,11 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common.enums import StatusEnum +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from ..handlers import interface router = APIRouter( @@ -21,7 +21,7 @@ ) async def process( query: models.ProcessNodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> tuple[bool, StatusEnum]: """Invoke the interface.process function""" params = query.model_dump() @@ -43,7 +43,7 @@ async def process( ) async def reset_script( query: models.ResetScriptQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Script: """Invoke the interface.reset_script function""" params = query.model_dump() @@ -63,7 +63,7 @@ async def reset_script( ) async def rescue_job( query: models.NodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Job: """Invoke the interface.rescue_job function""" params = query.model_dump() @@ -83,7 +83,7 @@ async def rescue_job( ) async def mark_job_rescued( query: models.NodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.Job]: """Invoke the interface.mark_job_rescued function""" params = query.model_dump() @@ -103,7 +103,7 @@ async def mark_job_rescued( ) async def rematch_pipetask_errors( query: models.RematchQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.PipetaskError]: """Invoke the interface.match_pipetask_errors function""" params = query.model_dump() diff --git a/src/lsst/cmservice/routers/campaigns.py b/src/lsst/cmservice/routers/campaigns.py index 9430aee21..0956aa49a 100644 --- a/src/lsst/cmservice/routers/campaigns.py +++ b/src/lsst/cmservice/routers/campaigns.py @@ -5,15 +5,15 @@ from uuid import uuid5 from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException -from safir.dependencies.db_session import db_session_dependency from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common import timestamp from ..common.enums import DEFAULT_NAMESPACE from ..common.graph import graph_from_edge_list, graph_to_dict from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from ..handlers.functions import render_campaign_steps from . import wrappers @@ -98,7 +98,7 @@ ) async def post_row( row_create: models.CampaignCreate, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], background_tasks: BackgroundTasks, ) -> db.Campaign: try: @@ -125,7 +125,7 @@ async def post_row( ) async def get_step_graph( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> Mapping: # Determine the namespace UUID for the campaign campaign = await db.Campaign.get_row(session, row_id) @@ -148,7 +148,7 @@ async def get_step_graph( ) async def get_script_graph( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> Mapping: # Determine the namespace UUID for the campaign campaign = await db.Campaign.get_row(session, row_id) diff --git a/src/lsst/cmservice/routers/groups.py b/src/lsst/cmservice/routers/groups.py index 56ff456ac..ddada5149 100644 --- a/src/lsst/cmservice/routers/groups.py +++ b/src/lsst/cmservice/routers/groups.py @@ -3,10 +3,10 @@ from typing import Annotated from fastapi import APIRouter, Depends -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from . import wrappers # Template specialization @@ -95,7 +95,7 @@ ) async def rescue_job( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Job: """Invoke the Group.rescue_job function""" the_group = await DbClass.get_row(session, row_id) @@ -110,7 +110,7 @@ async def rescue_job( ) async def mark_job_rescued( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.Job]: """Invoke the Group.mark_job_rescued function""" the_group = await DbClass.get_row(session, row_id) diff --git a/src/lsst/cmservice/routers/jobs.py b/src/lsst/cmservice/routers/jobs.py index 4790fa55a..9e81b09d9 100644 --- a/src/lsst/cmservice/routers/jobs.py +++ b/src/lsst/cmservice/routers/jobs.py @@ -4,12 +4,12 @@ from typing import TYPE_CHECKING, Annotated from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Response -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common.errors import CMBadStateTransitionError, CMMissingIDError from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from ..handlers.functions import force_accept_node from . import wrappers @@ -99,7 +99,7 @@ ) async def get_errors( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> Sequence[db.PipetaskError]: try: async with session.begin(): @@ -121,7 +121,7 @@ async def get_errors( async def accept_job( *, row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], background_tasks: BackgroundTasks, force: bool = False, output_collection: str | None = None, diff --git a/src/lsst/cmservice/routers/loaders.py b/src/lsst/cmservice/routers/loaders.py index 26863e62b..225c78808 100644 --- a/src/lsst/cmservice/routers/loaders.py +++ b/src/lsst/cmservice/routers/loaders.py @@ -1,10 +1,10 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from ..handlers import functions, interface router = APIRouter( @@ -21,7 +21,7 @@ ) async def add_steps( query: models.AddSteps, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Campaign: """Invoke the interface.add_steps function""" try: @@ -40,7 +40,7 @@ async def add_steps( ) async def load_specification( query: models.SpecificationLoad, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Specification: """Load a specification object fom a yaml file @@ -49,7 +49,7 @@ async def load_specification( query: models.SpecificationLoad Details of what to load - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -73,7 +73,7 @@ async def load_specification( ) async def load_and_create_campaign( query: models.LoadAndCreateCampaign, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Campaign: """Load a specification and use it to create a `Campaign` @@ -82,7 +82,7 @@ async def load_and_create_campaign( query: models.LoadAndCreateCampaign Details of what to load and create - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -106,7 +106,7 @@ async def load_and_create_campaign( ) async def load_error_types( query: models.YamlFileQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.PipetaskErrorType]: """Load a set of PipetaskErrorType object from a yaml file @@ -115,7 +115,7 @@ async def load_error_types( query: models.YamlFileQuery Details of what to load - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -139,7 +139,7 @@ async def load_error_types( ) async def load_manifest_report( query: models.LoadManifestReport, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Job: """Load a pipetask report and associated it to a Job @@ -148,7 +148,7 @@ async def load_manifest_report( query: models.LoadManifestReport Details of what to load - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/routers/queues.py b/src/lsst/cmservice/routers/queues.py index b10416b50..02c52c508 100644 --- a/src/lsst/cmservice/routers/queues.py +++ b/src/lsst/cmservice/routers/queues.py @@ -3,12 +3,12 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common.errors import CMMissingIDError from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from . import wrappers logger = LOGGER.bind(module=__name__) @@ -51,7 +51,7 @@ summary="Process the associated element", ) async def process_element( - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], row_id: int, ) -> bool: """Process associated element @@ -61,7 +61,7 @@ async def process_element( row_id: int ID of the Queue row in question - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -86,7 +86,7 @@ async def process_element( summary="Toggle the pause status of a queue entry", ) async def toggle_active( - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], row_id: int, ) -> bool: """Toggle the active status of a queue entry. @@ -96,7 +96,7 @@ async def toggle_active( row_id: int ID of the Queue row in question - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -121,7 +121,7 @@ async def toggle_active( summary="Check how long to sleep based on what is running", ) async def sleep_time( - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], row_id: int, ) -> int: """Check how long to sleep based on what is running @@ -131,7 +131,7 @@ async def sleep_time( row_id: int ID of the Queue row in question - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns diff --git a/src/lsst/cmservice/routers/scripts.py b/src/lsst/cmservice/routers/scripts.py index 6fa61eb37..ed6ad47e0 100644 --- a/src/lsst/cmservice/routers/scripts.py +++ b/src/lsst/cmservice/routers/scripts.py @@ -4,12 +4,12 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common.enums import StatusEnum from ..common.errors import CMMissingIDError +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency from . import wrappers # Template specialization @@ -87,7 +87,7 @@ async def reset_script( row_id: int, query: models.ResetQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> StatusEnum: """Reset a script to an earlier status @@ -96,7 +96,7 @@ async def reset_script( row_id: int ID of the script in question - session: async_scoped_session + session: AnyAsyncSession DB session manager Returns @@ -123,7 +123,7 @@ async def reset_script( ) async def get_script_errors( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> Sequence[db.ScriptError]: try: async with session.begin(): diff --git a/src/lsst/cmservice/routers/wrappers.py b/src/lsst/cmservice/routers/wrappers.py index 36024215c..86906809c 100644 --- a/src/lsst/cmservice/routers/wrappers.py +++ b/src/lsst/cmservice/routers/wrappers.py @@ -13,13 +13,13 @@ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel -from safir.dependencies.db_session import db_session_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from .. import db, models from ..common.enums import StatusEnum from ..common.errors import CMBadStateTransitionError, CMMissingFullnameError, CMMissingIDError from ..common.logging import LOGGER +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency logger = LOGGER.bind(module=__name__) @@ -57,7 +57,7 @@ def get_rows_no_parent_function( summary=f"List all the {db_class.class_string}", ) async def get_rows( - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], skip: int = 0, limit: int = 100, ) -> Sequence[response_model_class]: @@ -104,7 +104,7 @@ def get_rows_function( summary=f"List all the {db_class.class_string}", ) async def get_rows( - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], parent_id: int | None = None, skip: int = 0, limit: int = 100, @@ -156,7 +156,7 @@ def get_row_function( ) async def get_row( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -202,7 +202,7 @@ def get_row_by_fullname_function( ) async def get_row_by_fullname( fullname: str, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -248,7 +248,7 @@ def get_row_by_name_function( ) async def get_row_by_name( name: str, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -300,7 +300,7 @@ def post_row_function( ) async def post_row( row_create: create_model_class, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db_class: try: async with session.begin(): @@ -340,7 +340,7 @@ def delete_row_function( ) async def delete_row( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> None: try: async with session.begin(): @@ -392,7 +392,7 @@ def put_row_function( async def update_row( row_id: int, row_update: update_model_class, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db_class: try: async with session.begin(): @@ -433,7 +433,7 @@ def get_node_spec_block_function( ) async def get_node_spec_block( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> models.SpecBlock: try: async with session.begin(): @@ -476,7 +476,7 @@ def get_node_specification_function( ) async def get_node_specification( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> models.Specification: try: async with session.begin(): @@ -523,7 +523,7 @@ def get_node_parent_function( ) async def get_node_parent( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -566,7 +566,7 @@ def get_node_resolved_collections_function( ) async def get_node_resolved_collections( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> dict[str, str]: try: async with session.begin(): @@ -609,7 +609,7 @@ def get_node_collections_function( ) async def get_node_collections( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> dict[str, str]: try: async with session.begin(): @@ -652,7 +652,7 @@ def get_node_child_config_function( ) async def get_node_child_config( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> dict[str, str | int]: try: async with session.begin(): @@ -695,7 +695,7 @@ def get_node_data_dict_function( ) async def get_node_data_dict( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> dict[str, str]: try: async with session.begin(): @@ -738,7 +738,7 @@ def get_node_spec_aliases_function( ) async def get_node_spec_aliases( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> dict[str, str]: try: async with session.begin(): @@ -787,7 +787,7 @@ def update_node_status_function( async def update_node_status( row_id: int, query: models.UpdateStatusQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -838,7 +838,7 @@ def update_node_collections_function( async def update_node_collections( row_id: int, query: models.UpdateNodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -886,7 +886,7 @@ def update_node_child_config_function( async def update_node_child_config( row_id: int, query: models.UpdateNodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -934,7 +934,7 @@ def update_node_data_dict_function( async def update_node_data_dict( row_id: int, query: models.UpdateNodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -982,7 +982,7 @@ def update_node_spec_aliases_function( async def update_node_spec_aliases( row_id: int, query: models.UpdateNodeQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -1025,7 +1025,7 @@ def get_node_check_prerequisites_function( ) async def node_check_prerequisites( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> bool: try: async with session.begin(): @@ -1073,7 +1073,7 @@ def get_node_reject_function( ) async def node_reject( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -1123,7 +1123,7 @@ def get_node_accept_function( ) async def node_accept( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -1174,7 +1174,7 @@ def get_node_reset_function( async def node_reset( row_id: int, query: models.ResetQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> response_model_class: try: async with session.begin(): @@ -1220,7 +1220,7 @@ def get_node_process_function( ) async def node_process( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> tuple[bool, StatusEnum]: try: async with session.begin(): @@ -1264,7 +1264,7 @@ def get_node_run_check_function( ) async def node_run_check( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> tuple[bool, StatusEnum]: try: async with session.begin(): @@ -1309,7 +1309,7 @@ def get_element_get_scripts_function( ) async def element_get_scripts( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], script_name: str = "", ) -> list[db.Script]: try: @@ -1355,7 +1355,7 @@ def get_element_get_all_scripts_function( ) async def element_get_all_scripts( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.Script]: try: async with session.begin(): @@ -1400,7 +1400,7 @@ def get_element_get_jobs_function( ) async def element_get_jobs( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> list[db.Job]: try: async with session.begin(): @@ -1446,7 +1446,7 @@ def get_element_retry_script_function( async def element_retry_script( row_id: int, query: models.RetryScriptQuery, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Script: try: async with session.begin(): @@ -1494,7 +1494,7 @@ def get_element_wms_task_reports_function( ) async def element_get_wms_task_reports( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> models.MergedWmsTaskReportDict: try: async with session.begin(): @@ -1539,7 +1539,7 @@ def get_element_tasks_function( ) async def element_get_tasks( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> models.MergedTaskSetDict: try: async with session.begin(): @@ -1584,7 +1584,7 @@ def get_element_products_function( ) async def element_get_products( row_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> models.MergedProductSetDict: try: async with session.begin(): diff --git a/src/lsst/cmservice/web_app/app.py b/src/lsst/cmservice/web_app/app.py index 485332a22..7018ee108 100644 --- a/src/lsst/cmservice/web_app/app.py +++ b/src/lsst/cmservice/web_app/app.py @@ -11,13 +11,10 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pydantic import BaseModel -from safir.dependencies.db_session import db_session_dependency from safir.dependencies.http_client import http_client_dependency -from sqlalchemy.ext.asyncio import async_scoped_session from starlette.exceptions import HTTPException as StarletteHTTPException from lsst.cmservice.common.enums import LevelEnum -from lsst.cmservice.config import config from lsst.cmservice.web_app.pages.campaigns import get_all_campaigns, get_campaign_details, search_campaigns from lsst.cmservice.web_app.pages.group_details import get_group_by_id from lsst.cmservice.web_app.pages.job_details import get_job_by_id @@ -35,14 +32,16 @@ update_data_dict, ) +from ..common.types import AnyAsyncSession +from ..db.session import db_session_dependency + @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncGenerator: """Hook FastAPI init/cleanups.""" # Dependency inits before app starts running - await db_session_dependency.initialize(config.db.url, config.db.password) - assert db_session_dependency._engine is not None - db_session_dependency._engine.echo = config.db.echo + await db_session_dependency.initialize() + assert db_session_dependency.engine is not None # App runs here... yield @@ -110,7 +109,7 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException) @web_app.get("/campaigns/", response_class=HTMLResponse) async def get_campaigns( request: Request, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: campaign_list = [] @@ -137,7 +136,7 @@ async def get_campaigns( async def search( request: Request, search_term: Annotated[str, Form()], - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: results = await search_campaigns(session, search_term) @@ -163,7 +162,7 @@ async def search( async def get_steps( request: Request, campaign_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: campaign = await get_campaign_by_id(session, campaign_id) @@ -194,7 +193,7 @@ async def get_step( request: Request, campaign_id: int, step_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: step, step_groups, step_scripts = await get_step_details_by_id(session, step_id) @@ -218,7 +217,7 @@ async def get_step( async def get_group( request: Request, group_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: group_details, jobs, scripts = await get_group_by_id(session, group_id) @@ -243,7 +242,7 @@ async def get_job( step_id: int, group_id: int, job_id: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: job_details, scripts = await get_job_by_id(session, job_id) @@ -269,7 +268,7 @@ async def get_job( @web_app.get("/script/{campaign_id}/{step_id}/{group_id}/{job_id}/{script_id}/", response_class=HTMLResponse) async def get_script( request: Request, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], script_id: int, campaign_id: int | None = None, step_id: int | None = None, @@ -329,7 +328,7 @@ async def update_element_collections( request: Request, element_id: int, element_type: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: element = await get_element(session, element_id, element_type) @@ -360,7 +359,7 @@ async def update_element_child_config( request: Request, element_id: int, element_type: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: element = await get_element(session, element_id, element_type) @@ -392,7 +391,7 @@ async def update_element_data_dict( request: Request, element_id: int, element_type: int, - session: Annotated[async_scoped_session, Depends(db_session_dependency)], + session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> HTMLResponse: try: element = await get_element(session, element_id, element_type) diff --git a/src/lsst/cmservice/web_app/pages/campaigns.py b/src/lsst/cmservice/web_app/pages/campaigns.py index 31d0811a2..fd3efe55e 100644 --- a/src/lsst/cmservice/web_app/pages/campaigns.py +++ b/src/lsst/cmservice/web_app/pages/campaigns.py @@ -2,16 +2,16 @@ from typing import TYPE_CHECKING from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session from lsst.cmservice.common.enums import LevelEnum from lsst.cmservice.common.timestamp import iso_timestamp +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import Campaign, Group from lsst.cmservice.web_app.pages.steps import get_campaign_steps, get_step_details from lsst.cmservice.web_app.utils.utils import map_status -async def get_campaign_details(session: async_scoped_session, campaign: Campaign) -> dict: +async def get_campaign_details(session: AnyAsyncSession, campaign: Campaign) -> dict: if TYPE_CHECKING: assert isinstance(campaign.data, dict) collections = await campaign.resolve_collections(session, throw_overrides=False) @@ -54,21 +54,21 @@ async def get_campaign_details(session: async_scoped_session, campaign: Campaign return campaign_details -async def search_campaigns(session: async_scoped_session, search_term: str) -> Sequence: +async def search_campaigns(session: AnyAsyncSession, search_term: str) -> Sequence: q = select(Campaign).where(Campaign.name.contains(search_term)) async with session.begin_nested(): results = await session.scalars(q) return results.all() -async def get_campaign_groups(session: async_scoped_session, campaign: Campaign) -> Sequence: +async def get_campaign_groups(session: AnyAsyncSession, campaign: Campaign) -> Sequence: q = select(Group).where(Group.c_ == campaign) async with session.begin_nested(): results = await session.scalars(q) return results.all() -async def get_all_campaigns(session: async_scoped_session) -> Sequence: +async def get_all_campaigns(session: AnyAsyncSession) -> Sequence: q = select(Campaign) async with session.begin_nested(): results = await session.scalars(q) diff --git a/src/lsst/cmservice/web_app/pages/group_details.py b/src/lsst/cmservice/web_app/pages/group_details.py index 13a97f1b8..2d0dc0661 100644 --- a/src/lsst/cmservice/web_app/pages/group_details.py +++ b/src/lsst/cmservice/web_app/pages/group_details.py @@ -1,14 +1,14 @@ from typing import Any from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import Group, NodeMixin from lsst.cmservice.web_app.utils.utils import map_status async def get_group_by_id( - session: async_scoped_session, + session: AnyAsyncSession, group_id: int, campaign_id: int | None = None, step_id: int | None = None, @@ -79,7 +79,7 @@ async def get_group_by_id( return group_details, jobs, scripts -async def get_group_jobs(session: async_scoped_session, group: Group) -> list[dict]: +async def get_group_jobs(session: AnyAsyncSession, group: Group) -> list[dict]: jobs = await group.children(session) return [ { @@ -98,7 +98,7 @@ async def get_group_jobs(session: async_scoped_session, group: Group) -> list[di ] -async def get_group_scripts(session: async_scoped_session, group: Group) -> list[dict]: +async def get_group_scripts(session: AnyAsyncSession, group: Group) -> list[dict]: scripts = await group.get_scripts(session) return [ { @@ -113,6 +113,6 @@ async def get_group_scripts(session: async_scoped_session, group: Group) -> list ] -async def get_group_node(session: async_scoped_session, group_id: int) -> NodeMixin: +async def get_group_node(session: AnyAsyncSession, group_id: int) -> NodeMixin: group = await Group.get_row(session, group_id) return group diff --git a/src/lsst/cmservice/web_app/pages/job_details.py b/src/lsst/cmservice/web_app/pages/job_details.py index a55b88689..d468aa17c 100644 --- a/src/lsst/cmservice/web_app/pages/job_details.py +++ b/src/lsst/cmservice/web_app/pages/job_details.py @@ -1,14 +1,14 @@ from typing import Any from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import Job, NodeMixin from lsst.cmservice.web_app.utils.utils import map_status async def get_job_by_id( - session: async_scoped_session, + session: AnyAsyncSession, job_id: int, ) -> tuple[dict[str, Any] | None, list[dict[Any, Any]] | None]: q = select(Job).where(Job.id == job_id) @@ -73,7 +73,7 @@ async def get_job_by_id( return job_details, scripts -async def get_job_scripts(session: async_scoped_session, job: Job) -> list[dict]: +async def get_job_scripts(session: AnyAsyncSession, job: Job) -> list[dict]: scripts = await job.get_scripts(session) job_scripts = [ { @@ -89,6 +89,6 @@ async def get_job_scripts(session: async_scoped_session, job: Job) -> list[dict] return job_scripts -async def get_job_node(session: async_scoped_session, job_id: int) -> NodeMixin: +async def get_job_node(session: AnyAsyncSession, job_id: int) -> NodeMixin: job = await Job.get_row(session, job_id) return job diff --git a/src/lsst/cmservice/web_app/pages/script_details.py b/src/lsst/cmservice/web_app/pages/script_details.py index 7328f1531..662aa99ba 100644 --- a/src/lsst/cmservice/web_app/pages/script_details.py +++ b/src/lsst/cmservice/web_app/pages/script_details.py @@ -2,8 +2,8 @@ import starlette.requests from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import Group, Job, NodeMixin, Script, Step from lsst.cmservice.web_app.utils.utils import map_status @@ -20,7 +20,7 @@ def is_script_collection(collection: tuple[str, str]) -> bool: async def get_script_by_id( - session: async_scoped_session, + session: AnyAsyncSession, script_id: int, request: starlette.requests.Request, campaign_id: int | None = None, @@ -104,27 +104,27 @@ async def get_script_by_id( return script_details -async def get_step_id_by_fullname(session: async_scoped_session, fullname: str) -> int | None: +async def get_step_id_by_fullname(session: AnyAsyncSession, fullname: str) -> int | None: q = select(Step.id).where(Step.fullname == fullname) async with session.begin_nested(): results = await session.scalars(q) return results.one_or_none() -async def get_group_id_by_fullname(session: async_scoped_session, fullname: str) -> int | None: +async def get_group_id_by_fullname(session: AnyAsyncSession, fullname: str) -> int | None: q = select(Group.id).where(Group.fullname == fullname) async with session.begin_nested(): results = await session.scalars(q) return results.one_or_none() -async def get_job_id_by_fullname(session: async_scoped_session, fullname: str) -> int | None: +async def get_job_id_by_fullname(session: AnyAsyncSession, fullname: str) -> int | None: q = select(Job.id).where(Job.fullname == fullname) async with session.begin_nested(): results = await session.scalars(q) return results.one_or_none() -async def get_script_node(session: async_scoped_session, script_id: int) -> NodeMixin: +async def get_script_node(session: AnyAsyncSession, script_id: int) -> NodeMixin: script = await Script.get_row(session, script_id) return script diff --git a/src/lsst/cmservice/web_app/pages/step_details.py b/src/lsst/cmservice/web_app/pages/step_details.py index fb9c26a39..1b5a15a2e 100644 --- a/src/lsst/cmservice/web_app/pages/step_details.py +++ b/src/lsst/cmservice/web_app/pages/step_details.py @@ -1,14 +1,13 @@ from typing import Any -from sqlalchemy.ext.asyncio import async_scoped_session - +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import NodeMixin, Step from lsst.cmservice.web_app.pages.steps import get_step_details from lsst.cmservice.web_app.utils.utils import map_status async def get_step_details_by_id( - session: async_scoped_session, + session: AnyAsyncSession, step_id: int, ) -> tuple[Any, list[dict[Any, Any]], list[dict[Any, Any]]]: step = await Step.get_row(session, step_id) @@ -23,7 +22,7 @@ async def get_step_details_by_id( return step_details, groups, scripts -async def get_step_groups(session: async_scoped_session, step: Step) -> list[dict]: +async def get_step_groups(session: AnyAsyncSession, step: Step) -> list[dict]: groups = await step.children(session) step_groups = [ { @@ -42,7 +41,7 @@ async def get_step_groups(session: async_scoped_session, step: Step) -> list[dic return step_groups -async def get_step_scripts(session: async_scoped_session, step: Step) -> list[dict]: +async def get_step_scripts(session: AnyAsyncSession, step: Step) -> list[dict]: scripts = await step.get_scripts(session) step_scripts = [ { @@ -58,6 +57,6 @@ async def get_step_scripts(session: async_scoped_session, step: Step) -> list[di return step_scripts -async def get_step_node(session: async_scoped_session, step_id: int) -> NodeMixin: +async def get_step_node(session: AnyAsyncSession, step_id: int) -> NodeMixin: step = await Step.get_row(session, step_id) return step diff --git a/src/lsst/cmservice/web_app/pages/steps.py b/src/lsst/cmservice/web_app/pages/steps.py index 187f78031..16a4fa4c4 100644 --- a/src/lsst/cmservice/web_app/pages/steps.py +++ b/src/lsst/cmservice/web_app/pages/steps.py @@ -1,22 +1,22 @@ from collections.abc import Sequence from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_scoped_session from lsst.cmservice.common.enums import StatusEnum +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import Campaign, Step from lsst.cmservice.parsing.string import parse_element_fullname from lsst.cmservice.web_app.utils.utils import map_status -async def get_campaign_steps(session: async_scoped_session, campaign_id: int) -> Sequence: +async def get_campaign_steps(session: AnyAsyncSession, campaign_id: int) -> Sequence: q = select(Step).where(Step.parent_id == campaign_id).order_by(Step.id) async with session.begin_nested(): results = await session.scalars(q) return results.all() -async def get_step_details(session: async_scoped_session, step: Step) -> dict: +async def get_step_details(session: AnyAsyncSession, step: Step) -> dict: step_groups = await step.children(session) no_groups = len(list(step_groups)) no_groups_completed = len([group for group in step_groups if group.status is StatusEnum.accepted]) @@ -38,7 +38,7 @@ async def get_step_details(session: async_scoped_session, step: Step) -> dict: return step_details -async def get_campaign_by_id(session: async_scoped_session, campaign_id: int) -> Campaign | None: +async def get_campaign_by_id(session: AnyAsyncSession, campaign_id: int) -> Campaign | None: q = select(Campaign).where(Campaign.id == campaign_id) async with session.begin_nested(): results = await session.scalars(q) diff --git a/src/lsst/cmservice/web_app/utils/utils.py b/src/lsst/cmservice/web_app/utils/utils.py index 7009be8c2..7027d8d29 100644 --- a/src/lsst/cmservice/web_app/utils/utils.py +++ b/src/lsst/cmservice/web_app/utils/utils.py @@ -1,6 +1,5 @@ -from sqlalchemy.ext.asyncio import async_scoped_session - from lsst.cmservice.common.enums import LevelEnum, StatusEnum +from lsst.cmservice.common.types import AnyAsyncSession from lsst.cmservice.db import NodeMixin @@ -17,26 +16,22 @@ def map_status(status: StatusEnum) -> str | None: return None -async def update_data_dict(session: async_scoped_session, element: NodeMixin, data_dict: dict) -> NodeMixin: +async def update_data_dict(session: AnyAsyncSession, element: NodeMixin, data_dict: dict) -> NodeMixin: updated_element = await element.update_data_dict(session, **data_dict) return updated_element -async def update_collections( - session: async_scoped_session, element: NodeMixin, collections: dict -) -> NodeMixin: +async def update_collections(session: AnyAsyncSession, element: NodeMixin, collections: dict) -> NodeMixin: updated_element = await element.update_collections(session, **collections) return updated_element -async def update_child_config( - session: async_scoped_session, element: NodeMixin, child_config: dict -) -> NodeMixin: +async def update_child_config(session: AnyAsyncSession, element: NodeMixin, child_config: dict) -> NodeMixin: updated_element = await element.update_child_config(session, **child_config) return updated_element -async def get_element(session: async_scoped_session, element_id: int, element_type: int) -> NodeMixin | None: +async def get_element(session: AnyAsyncSession, element_id: int, element_type: int) -> NodeMixin | None: from lsst.cmservice.web_app.pages.group_details import get_group_node from lsst.cmservice.web_app.pages.job_details import get_job_node from lsst.cmservice.web_app.pages.script_details import get_script_node diff --git a/tests/db/test_handlers.py b/tests/db/test_handlers.py index 31f0d10eb..daf078bd8 100644 --- a/tests/db/test_handlers.py +++ b/tests/db/test_handlers.py @@ -8,17 +8,18 @@ import structlog from _pytest.monkeypatch import MonkeyPatch from safir.database import create_async_session -from sqlalchemy.ext.asyncio import AsyncEngine, async_scoped_session +from sqlalchemy.ext.asyncio import AsyncEngine from lsst.cmservice import db from lsst.cmservice.common.enums import LevelEnum, StatusEnum +from lsst.cmservice.common.types import AnyAsyncSession from .util_functions import cleanup, create_tree @pytest.mark.asyncio() async def check_run_script( - session: async_scoped_session, + session: AnyAsyncSession, parent: db.ElementMixin, script_name: str, spec_block_name: str, @@ -47,7 +48,7 @@ async def check_run_script( @pytest.mark.asyncio() async def check_script( - session: async_scoped_session, + session: AnyAsyncSession, parent: db.ElementMixin, script_name: str, spec_block_name: str, diff --git a/tests/db/util_functions.py b/tests/db/util_functions.py index 6a2ab13f7..a3a172397 100644 --- a/tests/db/util_functions.py +++ b/tests/db/util_functions.py @@ -1,19 +1,19 @@ import importlib from pathlib import Path -from typing import TypeAlias, TypeVar +from typing import TypeAlias import pytest -from sqlalchemy.ext.asyncio import async_scoped_session from lsst.cmservice import db from lsst.cmservice.common import errors from lsst.cmservice.common.enums import LevelEnum, StatusEnum, TableEnum +from lsst.cmservice.common.types import AnyAsyncSession -E = TypeVar("E", db.Group, db.Campaign, db.Step, db.Job) +type AnyElement = db.Group | db.Campaign | db.Step | db.Job async def add_scripts( - session: async_scoped_session, + session: AnyAsyncSession, element: db.ElementMixin, ) -> tuple[list[db.Script], db.ScriptDependency]: prep_script = await db.Script.create_row( @@ -42,7 +42,7 @@ async def add_scripts( async def create_tree( - session: async_scoped_session, + session: AnyAsyncSession, level: LevelEnum, uuid_int: int, ) -> None: @@ -128,7 +128,7 @@ async def create_tree( async def delete_all_rows( - session: async_scoped_session, + session: AnyAsyncSession, table_class: TypeAlias = db.RowMixin, ) -> None: rows = await table_class.get_rows(session) @@ -140,7 +140,7 @@ async def delete_all_rows( async def delete_all_artifacts( - session: async_scoped_session, + session: AnyAsyncSession, *, check_cascade: bool = False, ) -> None: @@ -159,20 +159,20 @@ async def delete_all_artifacts( async def delete_all_spec_stuff( - session: async_scoped_session, + session: AnyAsyncSession, ) -> None: await delete_all_rows(session, db.Specification) await delete_all_rows(session, db.SpecBlock) async def delete_all_queues( - session: async_scoped_session, + session: AnyAsyncSession, ) -> None: await delete_all_rows(session, db.Queue) async def cleanup( - session: async_scoped_session, + session: AnyAsyncSession, *, check_cascade: bool = False, ) -> None: @@ -182,11 +182,12 @@ async def cleanup( await session.commit() await session.close() - await session.remove() + if hasattr(session, "remove"): + await session.remove() async def check_update_methods( - session: async_scoped_session, + session: AnyAsyncSession, entry: db.NodeMixin, entry_class: TypeAlias = db.ElementMixin, ) -> None: @@ -294,7 +295,7 @@ async def check_update_methods( async def check_scripts( - session: async_scoped_session, + session: AnyAsyncSession, entry: db.ElementMixin, ) -> None: interface = importlib.import_module("lsst.cmservice.handlers.interface") @@ -378,8 +379,8 @@ async def check_scripts( async def check_get_methods( - session: async_scoped_session, - entry: E, + session: AnyAsyncSession, + entry: AnyElement, entry_class: TypeAlias = db.ElementMixin, parent_class: TypeAlias | None = db.ElementMixin, ) -> None: @@ -461,7 +462,7 @@ async def check_get_methods( async def check_queue( - session: async_scoped_session, + session: AnyAsyncSession, entry: db.ElementMixin, ) -> None: # make and test queue object From 4ad117a354addaf192bc5ce43acad35f21b7b984 Mon Sep 17 00:00:00 2001 From: Toby Jennings Date: Fri, 20 Jun 2025 11:25:50 -0500 Subject: [PATCH 05/37] chore(typing): - Refactor typealiases in router wrapper module - Remove "list" from spec_alias fields - Refactor typing in test modules - Update types in router tests - remove default values for TypeAliases --- src/lsst/cmservice/client/wrappers.py | 52 ++-- src/lsst/cmservice/common/types.py | 6 + src/lsst/cmservice/db/campaign.py | 2 +- src/lsst/cmservice/db/group.py | 2 +- src/lsst/cmservice/db/job.py | 2 +- src/lsst/cmservice/db/specification.py | 2 +- src/lsst/cmservice/db/step.py | 2 +- src/lsst/cmservice/models/element.py | 12 +- src/lsst/cmservice/models/interface.py | 8 +- src/lsst/cmservice/models/specification.py | 8 +- src/lsst/cmservice/routers/wrappers.py | 269 +++++++++++---------- tests/cli/util_functions.py | 27 +-- tests/routers/util_functions.py | 57 ++--- 13 files changed, 236 insertions(+), 213 deletions(-) diff --git a/src/lsst/cmservice/client/wrappers.py b/src/lsst/cmservice/client/wrappers.py index 1f41b2410..4107abc28 100644 --- a/src/lsst/cmservice/client/wrappers.py +++ b/src/lsst/cmservice/client/wrappers.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, TypeAlias from httpx import ConnectError, HTTPStatusError -from pydantic import BaseModel, TypeAdapter +from pydantic import TypeAdapter from .. import models from ..common.logging import LOGGER @@ -17,7 +17,7 @@ def get_rows_no_parent_function( - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that gets all the rows from a table @@ -28,7 +28,7 @@ def get_rows_no_parent_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -53,7 +53,7 @@ def get_rows(obj: CMClient) -> list[response_model_class]: def get_rows_function( - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, query: str = "", ) -> Callable: # pragma: no cover """Return a function that gets all the rows from a table @@ -66,7 +66,7 @@ def get_rows_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -99,7 +99,7 @@ def get_rows( def get_row_function( - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that gets a single row from a table (by ID) @@ -107,7 +107,7 @@ def get_row_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -131,8 +131,8 @@ def row_get( def create_row_function( - response_model_class: TypeAlias = BaseModel, - create_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, + create_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that creates a single row in a table @@ -140,10 +140,10 @@ def create_row_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value - create_model_class: TypeAlias = BaseModel, + create_model_class: TypeAlias, Pydantic class used to serialize the inputs value query: str @@ -168,8 +168,8 @@ def row_create(obj: CMClient, **kwargs: Any) -> response_model_class: def update_row_function( - response_model_class: TypeAlias = BaseModel, - update_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, + update_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that updates a single row in a table @@ -177,10 +177,10 @@ def update_row_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value - update_model_class: TypeAlias = BaseModel, + update_model_class: TypeAlias, Pydantic class used to serialize the input values query: str @@ -233,7 +233,7 @@ def row_delete( def get_row_by_fullname_function( - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that gets a single row from a table (by fullname) @@ -241,7 +241,7 @@ def get_row_by_fullname_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -268,7 +268,7 @@ def get_row_by_fullname( def get_row_by_name_function( - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, query: str = "", ) -> Callable: """Return a function that gets a single row from a table (by name) @@ -276,7 +276,7 @@ def get_row_by_name_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -316,7 +316,7 @@ def get_node_property_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -353,7 +353,7 @@ def get_node_post_query_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query_class: TypeAlias @@ -394,7 +394,7 @@ def get_node_post_no_query_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -421,7 +421,7 @@ def node_update( def get_general_post_function( - query_class: TypeAlias = BaseModel, + query_class: TypeAlias, response_model_class: TypeAlias = Any, query: str = "", results_key: str | None = None, @@ -431,7 +431,7 @@ def get_general_post_function( Parameters ---------- - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str @@ -460,7 +460,7 @@ def general_post_function( def get_general_query_function( - query_class: TypeAlias = BaseModel, + query_class: TypeAlias, response_model_class: TypeAlias = Any, query: str = "", query_suffix: str = "", @@ -474,7 +474,7 @@ def get_general_query_function( query_class: TypeAlias Pydantic class used to serialize the query parameters - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value query: str diff --git a/src/lsst/cmservice/common/types.py b/src/lsst/cmservice/common/types.py index 7f8ef633d..169a52d4f 100644 --- a/src/lsst/cmservice/common/types.py +++ b/src/lsst/cmservice/common/types.py @@ -2,5 +2,11 @@ from sqlalchemy.ext.asyncio import async_scoped_session from sqlmodel.ext.asyncio.session import AsyncSession +from .. import models + type AnyAsyncSession = AsyncSession | AsyncSessionSA | async_scoped_session """A type union of async database sessions the application may use""" + + +type AnyCampaignElement = models.Group | models.Campaign | models.Step | models.Job +"""A type union of Campaign elements""" diff --git a/src/lsst/cmservice/db/campaign.py b/src/lsst/cmservice/db/campaign.py index 0c7d299da..c31afcbfc 100644 --- a/src/lsst/cmservice/db/campaign.py +++ b/src/lsst/cmservice/db/campaign.py @@ -62,7 +62,7 @@ class Campaign(Base, ElementMixin): metadata_: Mapped[dict] = mapped_column("metadata_", type_=MutableDict.as_mutable(JSONB), default=dict) child_config: Mapped[dict | list | None] = mapped_column(type_=JSON) collections: Mapped[dict | list | None] = mapped_column(type_=JSON) - spec_aliases: Mapped[dict | list | None] = mapped_column(type_=JSON) + spec_aliases: Mapped[dict | None] = mapped_column(type_=JSON) spec_: Mapped[Specification] = relationship( "Specification", diff --git a/src/lsst/cmservice/db/group.py b/src/lsst/cmservice/db/group.py index 30780d1c2..e902fcf75 100644 --- a/src/lsst/cmservice/db/group.py +++ b/src/lsst/cmservice/db/group.py @@ -61,7 +61,7 @@ class Group(Base, ElementMixin): metadata_: Mapped[dict] = mapped_column("metadata_", type_=MutableDict.as_mutable(JSONB), default=dict) child_config: Mapped[dict | list | None] = mapped_column(type_=JSON) collections: Mapped[dict | list | None] = mapped_column(type_=JSON) - spec_aliases: Mapped[dict | list | None] = mapped_column(type_=JSON) + spec_aliases: Mapped[dict | None] = mapped_column(type_=JSON) spec_block_: Mapped[SpecBlock] = relationship("SpecBlock", viewonly=True) c_: Mapped["Campaign"] = relationship( diff --git a/src/lsst/cmservice/db/job.py b/src/lsst/cmservice/db/job.py index 53ba44c66..cdb52df20 100644 --- a/src/lsst/cmservice/db/job.py +++ b/src/lsst/cmservice/db/job.py @@ -68,7 +68,7 @@ class Job(Base, ElementMixin): ) child_config: Mapped[dict | list | None] = mapped_column(type_=JSON) collections: Mapped[dict | list | None] = mapped_column(type_=JSON) - spec_aliases: Mapped[dict | list | None] = mapped_column(type_=JSON) + spec_aliases: Mapped[dict | None] = mapped_column(type_=JSON) wms_job_id: Mapped[str | None] = mapped_column() stamp_url: Mapped[str | None] = mapped_column() diff --git a/src/lsst/cmservice/db/specification.py b/src/lsst/cmservice/db/specification.py index 824cacc02..33a4b26b8 100644 --- a/src/lsst/cmservice/db/specification.py +++ b/src/lsst/cmservice/db/specification.py @@ -28,7 +28,7 @@ class Specification(Base, RowMixin): data: Mapped[dict] = mapped_column(type_=JSON, default=dict) child_config: Mapped[dict | list | None] = mapped_column(type_=JSON) collections: Mapped[dict | list | None] = mapped_column(type_=JSON) - spec_aliases: Mapped[dict | list | None] = mapped_column(type_=JSON) + spec_aliases: Mapped[dict | None] = mapped_column(type_=JSON) col_names_for_table = ["id", "name"] diff --git a/src/lsst/cmservice/db/step.py b/src/lsst/cmservice/db/step.py index d58e31ec5..54524c5cb 100644 --- a/src/lsst/cmservice/db/step.py +++ b/src/lsst/cmservice/db/step.py @@ -58,7 +58,7 @@ class Step(Base, ElementMixin): metadata_: Mapped[dict] = mapped_column("metadata_", type_=MutableDict.as_mutable(JSONB), default=dict) child_config: Mapped[dict | list | None] = mapped_column(type_=JSON) collections: Mapped[dict | list | None] = mapped_column(type_=JSON) - spec_aliases: Mapped[dict | list | None] = mapped_column(type_=JSON) + spec_aliases: Mapped[dict | None] = mapped_column(type_=JSON) spec_block_: Mapped[SpecBlock] = relationship("SpecBlock", viewonly=True) parent_: Mapped[Campaign] = relationship("Campaign", back_populates="s_") diff --git a/src/lsst/cmservice/models/element.py b/src/lsst/cmservice/models/element.py index a20ec71b2..eb4ebbc18 100644 --- a/src/lsst/cmservice/models/element.py +++ b/src/lsst/cmservice/models/element.py @@ -21,13 +21,13 @@ class ElementBase(BaseModel): metadata_: dict = Field(default_factory=dict) # Overrides for configuring child nodes - child_config: dict | str | None = None + child_config: dict | None = None # Overrides for making collection names - collections: dict | str | None = None + collections: dict | None = None # Overrides for which SpecBlocks to use in constructing child Nodes - spec_aliases: dict | str | None = None + spec_aliases: dict | None = None # Override for Callback handler class handler: str | None = None @@ -75,13 +75,13 @@ class ElementUpdate(BaseModel): metadata_: dict | None = None # Overrides for configuring child nodes - child_config: dict | str | None = None + child_config: dict | None = None # Overrides for making collection names - collections: dict | str | None = None + collections: dict | None = None # Overrides for which SpecBlocks to use in constructing child Nodes - spec_aliases: dict | str | None = None + spec_aliases: dict | None = None # Override for Callback handler class handler: str | None = None diff --git a/src/lsst/cmservice/models/interface.py b/src/lsst/cmservice/models/interface.py index 34336d622..7112724a7 100644 --- a/src/lsst/cmservice/models/interface.py +++ b/src/lsst/cmservice/models/interface.py @@ -138,13 +138,13 @@ class LoadAndCreateCampaign(YamlFileQuery): # If empty use {spec_name}#campaign spec_block_assoc_name: str | None = None # Parameter Overrides - data: dict | str | None = None + data: dict | None = None # Overrides for configuring child nodes - child_config: dict | str | None = None + child_config: dict | None = None # Overrides for making collection names - collections: dict | str | None = None + collections: dict | None = None # Overrides for which SpecBlocks to use in constructing child Nodes - spec_aliases: dict | str | None = None + spec_aliases: dict | None = None # Override for Callback handler class handler: str | None = None # Allow updating existing specifications diff --git a/src/lsst/cmservice/models/specification.py b/src/lsst/cmservice/models/specification.py index b7528dd79..462fd1a8f 100644 --- a/src/lsst/cmservice/models/specification.py +++ b/src/lsst/cmservice/models/specification.py @@ -14,16 +14,16 @@ class SpecificationBase(BaseModel): name: str # Parameter Overrides - data: dict | str | None = None + data: dict | None = None # Overrides for configuring child nodes - child_config: dict | str | None = None + child_config: dict | None = None # Overrides for making collection names - collections: dict | str | None = None + collections: dict | None = None # Overrides for which SpecBlocks to use in constructing child Nodes - spec_aliases: dict | str | None = None + spec_aliases: dict | None = None class SpecificationCreate(SpecificationBase): diff --git a/src/lsst/cmservice/routers/wrappers.py b/src/lsst/cmservice/routers/wrappers.py index 86906809c..ed72324e6 100644 --- a/src/lsst/cmservice/routers/wrappers.py +++ b/src/lsst/cmservice/routers/wrappers.py @@ -9,10 +9,9 @@ """ from collections.abc import Callable, Sequence -from typing import Annotated, TypeAlias +from typing import TYPE_CHECKING, Annotated, TypeAlias from fastapi import APIRouter, Depends, HTTPException -from pydantic import BaseModel from .. import db, models from ..common.enums import StatusEnum @@ -26,8 +25,8 @@ def get_rows_no_parent_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that gets all the rows from a table and attaches that function to a router. @@ -40,10 +39,10 @@ def get_rows_no_parent_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value - db_class: TypeAlias = db.RowMixin + db_class: TypeAlias Underlying database class Returns @@ -54,13 +53,14 @@ def get_rows_no_parent_function( @router.get( "/list", - summary=f"List all the {db_class.class_string}", + summary=f"List all the {db_class.__qualname__}", + response_model=Sequence[response_model_class], ) async def get_rows( session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], skip: int = 0, limit: int = 100, - ) -> Sequence[response_model_class]: + ) -> Sequence[db.RowMixin]: try: async with session.begin(): return await db_class.get_rows(session, skip=skip, limit=limit) @@ -73,8 +73,8 @@ async def get_rows( def get_rows_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that gets all the rows from a table and attaches that function to a router. @@ -87,10 +87,10 @@ def get_rows_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value - db_class: TypeAlias = db.RowMixin + db_class: TypeAlias Underlying database class Returns @@ -101,14 +101,15 @@ def get_rows_function( @router.get( "/list", - summary=f"List all the {db_class.class_string}", + summary=f"List all the {db_class.__qualname__}", + response_model=Sequence[response_model_class], ) async def get_rows( session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], parent_id: int | None = None, skip: int = 0, limit: int = 100, - ) -> Sequence[response_model_class]: + ) -> Sequence[db.RowMixin]: try: async with session.begin(): return await db_class.get_rows( @@ -127,8 +128,8 @@ async def get_rows( def get_row_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that gets a single row from a table (by ID) and attaches that function to a router. @@ -138,7 +139,7 @@ def get_row_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -152,12 +153,13 @@ def get_row_function( @router.get( "/get/{row_id}", - summary=f"Retrieve a {db_class.class_string} by name", + summary=f"Retrieve a {db_class.__qualname__} by name", + response_model=response_model_class, ) async def get_row( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.RowMixin: try: async with session.begin(): return await db_class.get_row(session, row_id) @@ -173,8 +175,8 @@ async def get_row( def get_row_by_fullname_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that gets a single row from a table (by fullname) and attaches that function to a router. @@ -184,7 +186,7 @@ def get_row_by_fullname_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -198,12 +200,13 @@ def get_row_by_fullname_function( @router.get( "/get_row_by_fullname", - summary=f"Retrieve a {db_class.class_string} by name", + summary=f"Retrieve a {db_class.__qualname__} by name", + response_model=response_model_class, ) async def get_row_by_fullname( fullname: str, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.RowMixin: try: async with session.begin(): return await db_class.get_row_by_fullname(session, fullname) @@ -219,8 +222,8 @@ async def get_row_by_fullname( def get_row_by_name_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that gets a single row from a table (by name) and attaches that function to a router. @@ -230,7 +233,7 @@ def get_row_by_name_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -244,12 +247,13 @@ def get_row_by_name_function( @router.get( "/get_row_by_name", - summary=f"Retrieve a {db_class.class_string} by name", + summary=f"Retrieve a {db_class.__qualname__} by name", + response_model=response_model_class, ) async def get_row_by_name( name: str, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.RowMixin: try: async with session.begin(): return await db_class.get_row_by_name(session, name) @@ -265,9 +269,9 @@ async def get_row_by_name( def post_row_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - create_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + create_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that creates a single row in a table and attaches that function to a router. @@ -277,10 +281,10 @@ def post_row_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel, + response_model_class: TypeAlias, Pydantic class used to serialize the return value - create_model_class: TypeAlias = BaseModel, + create_model_class: TypeAlias, Pydantic class used to serialize the inputs value db_class: TypeAlias = db.RowMixin @@ -296,12 +300,12 @@ def post_row_function( "/create", status_code=201, response_model=response_model_class, - summary=f"Create a {db_class.class_string}", + summary=f"Create a {db_class.__qualname__}", ) async def post_row( row_create: create_model_class, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> db_class: + ) -> db.RowMixin: try: async with session.begin(): return await db_class.create_row(session, **row_create.model_dump()) @@ -314,7 +318,7 @@ async def post_row( def delete_row_function( router: APIRouter, - db_class: TypeAlias = db.RowMixin, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that deletes a single row in a table and attaches that function to a router. @@ -336,7 +340,7 @@ def delete_row_function( @router.delete( "/delete/{row_id}", status_code=204, - summary=f"Delete a {db_class.class_string}", + summary=f"Delete a {db_class.__qualname__}", ) async def delete_row( row_id: int, @@ -357,9 +361,9 @@ async def delete_row( def put_row_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - update_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.RowMixin, + response_model_class: TypeAlias, + update_model_class: TypeAlias, + db_class: type[db.RowMixin], ) -> Callable: """Return a function that updates a single row in a table and attaches that function to a router. @@ -369,10 +373,10 @@ def put_row_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return values - update_model_class: TypeAlias = BaseModel, + update_model_class: TypeAlias, Pydantic class used to serialize the input values db_class: TypeAlias = db.RowMixin @@ -387,13 +391,13 @@ def put_row_function( @router.put( "/update/{row_id}", response_model=response_model_class, - summary=f"Update a {db_class.class_string}", + summary=f"Update a {db_class.__qualname__}", ) async def update_row( row_id: int, row_update: update_model_class, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> db_class: + ) -> db.RowMixin: try: async with session.begin(): return await db_class.update_row(session, row_id, **row_update.model_dump()) @@ -409,7 +413,7 @@ async def update_row( def get_node_spec_block_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets the SpecBlock associated to a Node. @@ -418,7 +422,7 @@ def get_node_spec_block_function( router: APIRouter Router to attach the function to - db_class: TypeAlias = db.RowMixin + db_class: TypeAlias = db.NodeMixin Underlying database class Returns @@ -429,12 +433,13 @@ def get_node_spec_block_function( @router.get( "/get/{row_id}/spec_block", - summary=f"Get the SpecBlock associated to a {db_class.class_string}", + summary=f"Get the SpecBlock associated to a {db_class.__qualname__}", + response_model=models.SpecBlock, ) async def get_node_spec_block( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> models.SpecBlock: + ) -> db.SpecBlock: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -452,7 +457,7 @@ async def get_node_spec_block( def get_node_specification_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets the Specification associated to a Node. @@ -472,12 +477,13 @@ def get_node_specification_function( @router.get( "/get/{row_id}/specification", - summary=f"Get the Specification associated to a {db_class.class_string}", + summary=f"Get the Specification associated to a {db_class.__qualname__}", + response_model=models.Specification, ) async def get_node_specification( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> models.Specification: + ) -> db.Specification: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -495,8 +501,8 @@ async def get_node_specification( def get_node_parent_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets the parent Node associated to a Node. @@ -505,7 +511,7 @@ def get_node_parent_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -519,12 +525,13 @@ def get_node_parent_function( @router.get( "/get/{row_id}/parent", - summary=f"Get the Parent associated to a {db_class.class_string}", + summary=f"Get the Parent associated to a {db_class.__qualname__}", + response_model=response_model_class, ) async def get_node_parent( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -542,7 +549,7 @@ async def get_node_parent( def get_node_resolved_collections_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets resolved collection names associated to a Node. @@ -562,7 +569,7 @@ def get_node_resolved_collections_function( @router.get( "/get/{row_id}/resolved_collections", - summary=f"Get the resolved collections associated to a {db_class.class_string}", + summary=f"Get the resolved collections associated to a {db_class.__qualname__}", ) async def get_node_resolved_collections( row_id: int, @@ -585,7 +592,7 @@ async def get_node_resolved_collections( def get_node_collections_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets collection names associated to a Node. @@ -605,7 +612,7 @@ def get_node_collections_function( @router.get( "/get/{row_id}/collections", - summary=f"Get the collections associated to a {db_class.class_string}", + summary=f"Get the collections associated to a {db_class.__qualname__}", ) async def get_node_collections( row_id: int, @@ -628,7 +635,7 @@ async def get_node_collections( def get_node_child_config_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets child_conifg dict associated to a Node. @@ -648,7 +655,7 @@ def get_node_child_config_function( @router.get( "/get/{row_id}/child_config", - summary=f"Get the child_config associated to a {db_class.class_string}", + summary=f"Get the child_config associated to a {db_class.__qualname__}", ) async def get_node_child_config( row_id: int, @@ -671,7 +678,7 @@ async def get_node_child_config( def get_node_data_dict_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function gets data_dict associated to a Node. @@ -691,7 +698,7 @@ def get_node_data_dict_function( @router.get( "/get/{row_id}/data_dict", - summary=f"Get the data_dict associated to a {db_class.class_string}", + summary=f"Get the data_dict associated to a {db_class.__qualname__}", ) async def get_node_data_dict( row_id: int, @@ -714,7 +721,7 @@ async def get_node_data_dict( def get_node_spec_aliases_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that gets the spec_aliases associated to a Node. @@ -734,7 +741,7 @@ def get_node_spec_aliases_function( @router.get( "/get/{row_id}/spec_aliases", - summary=f"Get the spec_aliases associated to a {db_class.class_string}", + summary=f"Get the spec_aliases associated to a {db_class.__qualname__}", ) async def get_node_spec_aliases( row_id: int, @@ -757,8 +764,8 @@ async def get_node_spec_aliases( def update_node_status_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that updates the status of a Node. @@ -767,7 +774,7 @@ def update_node_status_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -783,12 +790,13 @@ def update_node_status_function( "/update/{row_id}/status", status_code=201, summary="Update status field associated to a node", + response_model=response_model_class, ) async def update_node_status( row_id: int, query: models.UpdateStatusQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -808,8 +816,8 @@ async def update_node_status( def update_node_collections_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that updates the collections associated to a Node. @@ -818,7 +826,7 @@ def update_node_collections_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -833,13 +841,14 @@ def update_node_collections_function( @router.post( "/update/{row_id}/collections", status_code=201, - summary=f"Update the collections associated to a {db_class.class_string}", + summary=f"Update the collections associated to a {db_class.__qualname__}", + response_model=response_model_class, ) async def update_node_collections( row_id: int, query: models.UpdateNodeQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -857,8 +866,8 @@ async def update_node_collections( def update_node_child_config_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that updates the child_config associated to a Node. @@ -867,7 +876,7 @@ def update_node_child_config_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -881,13 +890,14 @@ def update_node_child_config_function( @router.post( "/update/{row_id}/child_config", - summary=f"Update the child_config associated to a {db_class.class_string}", + summary=f"Update the child_config associated to a {db_class.__qualname__}", + response_model=response_model_class, ) async def update_node_child_config( row_id: int, query: models.UpdateNodeQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -905,8 +915,8 @@ async def update_node_child_config( def update_node_data_dict_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that updates the data_dict associated to a Node. @@ -915,7 +925,7 @@ def update_node_data_dict_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -929,13 +939,14 @@ def update_node_data_dict_function( @router.post( "/update/{row_id}/data_dict", - summary=f"Update the data_dict associated to a {db_class.class_string}", + summary=f"Update the data_dict associated to a {db_class.__qualname__}", + response_model=response_model_class, ) async def update_node_data_dict( row_id: int, query: models.UpdateNodeQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -953,8 +964,8 @@ async def update_node_data_dict( def update_node_spec_aliases_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that updates the spec_aliases associated to a Node. @@ -963,7 +974,7 @@ def update_node_spec_aliases_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -977,13 +988,14 @@ def update_node_spec_aliases_function( @router.post( "/update/{row_id}/spec_aliases", - summary=f"Update the spec_aliases associated to a {db_class.class_string}", + summary=f"Update the spec_aliases associated to a {db_class.__qualname__}", + response_model=response_model_class, ) async def update_node_spec_aliases( row_id: int, query: models.UpdateNodeQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -1001,7 +1013,7 @@ async def update_node_spec_aliases( def get_node_check_prerequisites_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that checks the prerequisites of a Node. @@ -1021,7 +1033,7 @@ def get_node_check_prerequisites_function( @router.get( "/get/{row_id}/check_prerequisites", - summary=f"Check the prerequisites associated to a {db_class.class_string}", + summary=f"Check the prerequisites associated to a {db_class.__qualname__}", ) async def node_check_prerequisites( row_id: int, @@ -1044,8 +1056,8 @@ async def node_check_prerequisites( def get_node_reject_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that marks a Node as rejected. @@ -1054,7 +1066,7 @@ def get_node_reject_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -1069,12 +1081,13 @@ def get_node_reject_function( @router.post( "/action/{row_id}/reject", status_code=201, - summary=f"Mark a {db_class.class_string} as rejected", + summary=f"Mark a {db_class.__qualname__} as rejected", + response_model=response_model_class, ) async def node_reject( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -1094,8 +1107,8 @@ async def node_reject( def get_node_accept_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that marks a Node as accepted. @@ -1104,7 +1117,7 @@ def get_node_accept_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -1119,12 +1132,13 @@ def get_node_accept_function( @router.post( "/action/{row_id}/accept", status_code=201, - summary=f"Mark a {db_class.class_string} as accepted", + summary=f"Mark a {db_class.__qualname__} as accepted", + response_model=response_model_class, ) async def node_accept( row_id: int, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -1144,8 +1158,8 @@ async def node_accept( def get_node_reset_function( router: APIRouter, - response_model_class: TypeAlias = BaseModel, - db_class: TypeAlias = db.NodeMixin, + response_model_class: TypeAlias, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function resets a Node status to waiting. @@ -1154,7 +1168,7 @@ def get_node_reset_function( router: APIRouter Router to attach the function to - response_model_class: TypeAlias = BaseModel + response_model_class: TypeAlias Pydantic class used to serialize the return value db_class: TypeAlias = db.RowMixin @@ -1169,13 +1183,14 @@ def get_node_reset_function( @router.post( "/action/{row_id}/reset", status_code=201, - summary=f"Mark a {db_class.class_string} as reseted", + summary=f"Mark a {db_class.__qualname__} as reseted", + response_model=response_model_class, ) async def node_reset( row_id: int, query: models.ResetQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], - ) -> response_model_class: + ) -> db.NodeMixin: try: async with session.begin(): the_node = await db_class.get_row(session, row_id) @@ -1195,7 +1210,7 @@ async def node_reset( def get_node_process_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that causes a Node to be processed. @@ -1216,7 +1231,7 @@ def get_node_process_function( @router.post( "/action/{row_id}/process", status_code=201, - summary=f"Mark a {db_class.class_string} as processed", + summary=f"Mark a {db_class.__qualname__} as processed", ) async def node_process( row_id: int, @@ -1239,7 +1254,7 @@ async def node_process( def get_node_run_check_function( router: APIRouter, - db_class: TypeAlias = db.NodeMixin, + db_class: type[db.NodeMixin], ) -> Callable: """Return a function that checks the status of a Node. @@ -1260,7 +1275,7 @@ def get_node_run_check_function( @router.post( "/action/{row_id}/run_check", status_code=201, - summary=f"Mark a {db_class.class_string} as run_checked", + summary=f"Mark a {db_class.__qualname__} as run_checked", ) async def node_run_check( row_id: int, @@ -1283,7 +1298,7 @@ async def node_run_check( def get_element_get_scripts_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function get the scripts associated to an Element. @@ -1305,7 +1320,7 @@ def get_element_get_scripts_function( "/get/{row_id}/scripts", status_code=201, response_model=Sequence[models.Script], - summary=f"Get the scripts associated to a {db_class.class_string}", + summary=f"Get the scripts associated to a {db_class.__qualname__}", ) async def element_get_scripts( row_id: int, @@ -1329,7 +1344,7 @@ async def element_get_scripts( def get_element_get_all_scripts_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function that gets all scripts associated to an Element. @@ -1351,7 +1366,7 @@ def get_element_get_all_scripts_function( "/get/{row_id}/all_scripts", status_code=201, response_model=Sequence[models.Script], - summary=f"Get the all scripts associated to a {db_class.class_string}", + summary=f"Get the all scripts associated to a {db_class.__qualname__}", ) async def element_get_all_scripts( row_id: int, @@ -1374,7 +1389,7 @@ async def element_get_all_scripts( def get_element_get_jobs_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function get the jobs associated to an Element. @@ -1396,7 +1411,7 @@ def get_element_get_jobs_function( "/get/{row_id}/jobs", status_code=201, response_model=Sequence[models.Job], - summary=f"Get the jobs associated to a {db_class.class_string}", + summary=f"Get the jobs associated to a {db_class.__qualname__}", ) async def element_get_jobs( row_id: int, @@ -1419,7 +1434,7 @@ async def element_get_jobs( def get_element_retry_script_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function that will retry a script @@ -1441,13 +1456,15 @@ def get_element_retry_script_function( "/action/{row_id}/retry_script", status_code=201, response_model=models.Script, - summary=f"Retry a script associated to a {db_class.class_string}", + summary=f"Retry a script associated to a {db_class.__qualname__}", ) async def element_retry_script( row_id: int, query: models.RetryScriptQuery, session: Annotated[AnyAsyncSession, Depends(db_session_dependency)], ) -> db.Script: + if TYPE_CHECKING: + assert query.script_name is not None try: async with session.begin(): the_element = await db_class.get_row(session, row_id) @@ -1468,7 +1485,7 @@ async def element_retry_script( def get_element_wms_task_reports_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function get the WmsTaskReports associated to an Element. @@ -1490,7 +1507,7 @@ def get_element_wms_task_reports_function( "/get/{row_id}/wms_task_reports", status_code=201, response_model=models.MergedWmsTaskReportDict, - summary=f"Get the WmsTaskReports associated to a {db_class.class_string}", + summary=f"Get the WmsTaskReports associated to a {db_class.__qualname__}", ) async def element_get_wms_task_reports( row_id: int, @@ -1513,7 +1530,7 @@ async def element_get_wms_task_reports( def get_element_tasks_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function get the TaskSets associated to an Element. @@ -1535,7 +1552,7 @@ def get_element_tasks_function( "/get/{row_id}/tasks", status_code=201, response_model=models.MergedTaskSetDict, - summary=f"Get the TaskSets associated to a {db_class.class_string}", + summary=f"Get the TaskSets associated to a {db_class.__qualname__}", ) async def element_get_tasks( row_id: int, @@ -1558,7 +1575,7 @@ async def element_get_tasks( def get_element_products_function( router: APIRouter, - db_class: TypeAlias = db.ElementMixin, + db_class: type[db.ElementMixin], ) -> Callable: """Return a function get the ProductSets associated to an Element. @@ -1580,7 +1597,7 @@ def get_element_products_function( "/get/{row_id}/products", status_code=201, response_model=models.MergedProductSetDict, - summary=f"Get the ProductSets associated to a {db_class.class_string}", + summary=f"Get the ProductSets associated to a {db_class.__qualname__}", ) async def element_get_products( row_id: int, diff --git a/tests/cli/util_functions.py b/tests/cli/util_functions.py index b91d77155..287091d95 100644 --- a/tests/cli/util_functions.py +++ b/tests/cli/util_functions.py @@ -1,6 +1,6 @@ import uuid from pathlib import Path -from typing import TypeAlias, TypeVar +from typing import TypeAlias import yaml from click import BaseCommand @@ -9,12 +9,10 @@ from lsst.cmservice import models from lsst.cmservice.common.enums import LevelEnum, StatusEnum +from lsst.cmservice.common.types import AnyCampaignElement -T = TypeVar("T") -E = TypeVar("E", models.Group, models.Campaign, models.Step, models.Job) - -def check_and_parse_result( +def check_and_parse_result[T]( result: Result, return_class: type[T], ) -> T: @@ -35,7 +33,7 @@ def expect_failed_result( def add_scripts( runner: CliRunner, client_top: BaseCommand, - element: models.ElementMixin, + element: AnyCampaignElement, namespace: uuid.UUID, ) -> tuple[list[models.Script], models.Dependency | None]: namespaced_spec_block_name = uuid.uuid5(namespace, "null_script") @@ -171,7 +169,7 @@ def delete_all_rows( runner: CliRunner, client_top: BaseCommand, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: TypeAlias, ) -> None: result = runner.invoke(client_top, f"{entry_class_name} list --output yaml") rows = check_and_parse_result(result, list[entry_class]) @@ -224,12 +222,12 @@ def cleanup( delete_all_queues(runner, client_top) -def check_update_methods( +def check_update_methods[E: AnyCampaignElement]( runner: CliRunner, client_top: BaseCommand, - entry: models.ElementMixin, + entry: E, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: type[E], ) -> None: result = runner.invoke( client_top, @@ -319,6 +317,7 @@ def check_update_methods( ) # FIXME: is this the return type we want? check_spec = check_and_parse_result(result, entry_class) + assert check_spec.spec_aliases is not None assert check_spec.spec_aliases["test"] == "dummy", "update_spec_aliases failed" result = runner.invoke( @@ -339,7 +338,7 @@ def check_update_methods( def check_scripts( runner: CliRunner, client_top: BaseCommand, - entry: models.ElementMixin, + entry: AnyCampaignElement, entry_class_name: str, ) -> None: models.ScriptQuery( @@ -417,9 +416,9 @@ def check_scripts( def check_get_methods( runner: CliRunner, client_top: BaseCommand, - entry: E, + entry: AnyCampaignElement, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: type[AnyCampaignElement], ) -> None: result = runner.invoke(client_top, f"{entry_class_name} get all --output yaml --row_id {entry.id}") check_get = check_and_parse_result(result, entry_class) @@ -481,7 +480,7 @@ def check_get_methods( def check_queue( runner: CliRunner, client_top: BaseCommand, - entry: models.ElementMixin, + entry: AnyCampaignElement, *, run_daemon: bool = False, ) -> None: diff --git a/tests/routers/util_functions.py b/tests/routers/util_functions.py index c0b1772b7..e22f76dfd 100644 --- a/tests/routers/util_functions.py +++ b/tests/routers/util_functions.py @@ -1,18 +1,16 @@ from pathlib import Path -from typing import TypeAlias, TypeVar +from typing import TypeAlias from httpx import AsyncClient, Response from pydantic import TypeAdapter from lsst.cmservice import models from lsst.cmservice.common.enums import LevelEnum, StatusEnum +from lsst.cmservice.common.types import AnyCampaignElement from lsst.cmservice.config import config -T = TypeVar("T") -E = TypeVar("E", models.Group, models.Campaign, models.Step, models.Job) - -def check_and_parse_response( +def check_and_parse_response[T]( response: Response, return_class: type[T], ) -> T: @@ -32,7 +30,7 @@ def expect_failed_response( async def add_scripts( client: AsyncClient, - element: E, + element: AnyCampaignElement, api_version: str, ) -> tuple[list[models.Script], models.Dependency]: prep_script_model = models.ScriptCreate( @@ -183,7 +181,7 @@ async def delete_all_rows( client: AsyncClient, api_version: str, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: TypeAlias, ) -> None: response = await client.get(f"{config.asgi.prefix}/{api_version}/{entry_class_name}/list") rows = check_and_parse_response(response, list[entry_class]) @@ -242,7 +240,7 @@ async def check_update_methods( api_version: str, entry: models.ElementMixin, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: type[models.ElementMixin], ) -> None: update_model = models.UpdateNodeQuery( fullname=entry.fullname, @@ -252,8 +250,8 @@ async def check_update_methods( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/{entry.id}/data_dict", content=update_model.model_dump_json(), ) - check = check_and_parse_response(response, entry_class) - assert check.data["test"] == "dummy", "update_data_dict failed" + check_a = check_and_parse_response(response, entry_class) + assert check_a.data["test"] == "dummy", "update_data_dict failed" response = await client.post( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/-1/data_dict", @@ -264,8 +262,8 @@ async def check_update_methods( response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}/data_dict", ) - check = check_and_parse_response(response, dict) - assert check["test"] == "dummy", "get_data_dict failed" + check_b = check_and_parse_response(response, dict) + assert check_b["test"] == "dummy", "get_data_dict failed" response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/-1/data_dict", @@ -280,8 +278,9 @@ async def check_update_methods( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/{entry.id}/collections", content=update_model.model_dump_json(), ) - check = check_and_parse_response(response, entry_class) - assert check.collections["test"] == "dummy", "update_collections failed" + check_c = check_and_parse_response(response, entry_class) + assert check_c.collections is not None + assert check_c.collections["test"] == "dummy", "update_collections failed" response = await client.post( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/-1/collections", @@ -292,8 +291,8 @@ async def check_update_methods( response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}/collections", ) - check = check_and_parse_response(response, dict) - assert check["test"] == "dummy", "get_collections failed" + check_d = check_and_parse_response(response, dict) + assert check_d["test"] == "dummy", "get_collections failed" response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/-1/collections", @@ -303,8 +302,8 @@ async def check_update_methods( response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}/resolved_collections", ) - check = check_and_parse_response(response, dict) - assert check["test"] == "dummy", "get_resolved_collections failed" + check_e = check_and_parse_response(response, dict) + assert check_e["test"] == "dummy", "get_resolved_collections failed" response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/-1/resolved_collections", @@ -319,8 +318,9 @@ async def check_update_methods( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/{entry.id}/child_config", content=update_model.model_dump_json(), ) - check = check_and_parse_response(response, entry_class) - assert check.child_config["test"] == "dummy", "update_child_config failed" + check_f = check_and_parse_response(response, entry_class) + assert check_f.child_config is not None + assert check_f.child_config["test"] == "dummy", "update_child_config failed" response = await client.post( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/-1/child_config", @@ -331,8 +331,8 @@ async def check_update_methods( response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}/child_config", ) - check = check_and_parse_response(response, dict) - assert check["test"] == "dummy", "get_child_config failed" + check_g = check_and_parse_response(response, dict) + assert check_g["test"] == "dummy", "get_child_config failed" response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/-1/child_config", @@ -347,8 +347,9 @@ async def check_update_methods( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/{entry.id}/spec_aliases", content=update_model.model_dump_json(), ) - check = check_and_parse_response(response, entry_class) - assert check.spec_aliases["test"] == "dummy", "update_spec_aliases failed" + check_h = check_and_parse_response(response, entry_class) + assert check_h.spec_aliases is not None + assert check_h.spec_aliases["test"] == "dummy", "update_spec_aliases failed" response = await client.post( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/update/-1/spec_aliases", @@ -359,8 +360,8 @@ async def check_update_methods( response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}/spec_aliases", ) - check = check_and_parse_response(response, dict) - assert check["test"] == "dummy", "get_spec_aliases failed" + check_i = check_and_parse_response(response, dict) + assert check_i["test"] == "dummy", "get_spec_aliases failed" response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/-1/spec_aliases", @@ -670,12 +671,12 @@ async def check_scripts( expect_failed_response(response, 404) -async def check_get_methods( +async def check_get_methods[E: AnyCampaignElement]( client: AsyncClient, api_version: str, entry: E, entry_class_name: str, - entry_class: TypeAlias = models.ElementMixin, + entry_class: type[E], ) -> None: response = await client.get( f"{config.asgi.prefix}/{api_version}/{entry_class_name}/get/{entry.id}", From a2b610388fdf53d1fdf7cd8c3df6ceae12d54f43 Mon Sep 17 00:00:00 2001 From: Toby Jennings Date: Thu, 26 Jun 2025 16:34:07 -0500 Subject: [PATCH 06/37] fix(parse): Allow '-' in element names (fixes #186) --- src/lsst/cmservice/parsing/string.py | 10 +++++----- tests/test_parsing.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lsst/cmservice/parsing/string.py b/src/lsst/cmservice/parsing/string.py index 24a052ede..dc485a187 100644 --- a/src/lsst/cmservice/parsing/string.py +++ b/src/lsst/cmservice/parsing/string.py @@ -48,11 +48,11 @@ def parse_element_fullname(fullname: str) -> Fullname: fullname_r = re.compile( ( r"^" - r"(?P[\w]+){1}(?:\/)*" - r"(?P[\w]+){0,1}(?:\/)*" - r"(?P[\w]+){0,1}(?:\/)*" - r"(?P[\w]+){0,1}(?:\/)*" - r"(?P