From 745dc88dab2610e06ce8c2139fadcd107b0830ec Mon Sep 17 00:00:00 2001 From: Justin Vasel Date: Fri, 16 Aug 2024 11:07:15 -0500 Subject: [PATCH] Lightly refactor messages - Replace deprecated pydantic v1 validators with v2 equivalents. - Rename "HeartBeat" to "HeartbeatMessage" and "Retraction" to "RetractionMessage" for consistency. - Update schemas, unit tests, and tutorial notebook to reflect these changes. --- snews/__init__.py | 5 +- snews/__main__.py | 3 + snews/examples/tutorial.ipynb | 141 ++++++++------ snews/models/messages.py | 116 +++++++++--- .../schema/CoincidenceTierMessage.schema.json | 7 +- snews/schema/Detector.schema.json | 2 +- ...hema.json => HeartbeatMessage.schema.json} | 9 +- ...ema.json => RetractionMessage.schema.json} | 10 +- snews/schema/SigTierMessage.schema.json | 172 ------------------ .../SignificanceTierMessage.schema.json | 7 +- snews/schema/TimeTierMessage.schema.json | 164 ----------------- snews/schema/TimingTierMessage.schema.json | 16 +- test/models/test_message_models.py | 38 ++-- 13 files changed, 227 insertions(+), 463 deletions(-) rename snews/schema/{HeartBeat.schema.json => HeartbeatMessage.schema.json} (96%) rename snews/schema/{Retraction.schema.json => RetractionMessage.schema.json} (96%) delete mode 100644 snews/schema/SigTierMessage.schema.json delete mode 100644 snews/schema/TimeTierMessage.schema.json diff --git a/snews/__init__.py b/snews/__init__.py index 3d2d1d0..224dbff 100644 --- a/snews/__init__.py +++ b/snews/__init__.py @@ -1,3 +1,6 @@ # -*- coding: utf-8 -*- +from .data import detectors +from .models import messages, timing +from .schema import SNEWSJsonSchema -__all__ = ["data", "models", "schemas"] +__all__ = ["detectors", "messages", "timing", "SNEWSJsonSchema"] diff --git a/snews/__main__.py b/snews/__main__.py index d432e28..30a0a16 100644 --- a/snews/__main__.py +++ b/snews/__main__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Standard modules +import inspect import json import logging from pathlib import Path @@ -32,6 +33,8 @@ def generate_model_schemas(outdir: str = None, models_module: list = models, dry model_class = getattr(models, model_class_name) for model_name in model_class.__all__: model = getattr(model_class, model_name) + if not inspect.isclass(model): + continue if not issubclass(model, BaseModel): continue diff --git a/snews/examples/tutorial.ipynb b/snews/examples/tutorial.ipynb index 2e90197..7747f56 100644 --- a/snews/examples/tutorial.ipynb +++ b/snews/examples/tutorial.ipynb @@ -9,11 +9,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "from pprint import pp" + "import pprint" ] }, { @@ -41,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -80,27 +80,27 @@ ], "source": [ "# Available attributes\n", - "pp(data.detectors.__all__)\n", + "pprint.pp(data.detectors.__all__)\n", "print()\n", "\n", "# All known detectors\n", "all_detectors = data.detectors.all\n", - "pp(all_detectors)\n", + "pprint.pp(all_detectors)\n", "print()\n", "\n", "# List all known detector names\n", "names = data.detectors.names\n", - "pp(names)\n", + "pprint.pp(names)\n", "print()\n", "\n", "# A specific detector model\n", "detector_model = data.query(data.detectors.all, {\"name\": \"Super-K\"})[0]\n", - "pp(detector_model)\n", + "pprint.pp(detector_model)\n", "print()\n", "\n", "# A specific detector in JSON\n", "detector_json = detector_model.model_dump()\n", - "pp(detector_json)\n", + "pprint.pp(detector_json)\n", "print()" ] }, @@ -113,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -171,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -245,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -274,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -283,7 +283,7 @@ "'2024-01-01T12:34:56.000000000Z'" ] }, - "execution_count": 23, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -302,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -311,7 +311,7 @@ "text": [ "Model categories: ['detectors', 'messages']\n", "Detector models: ['Detector', 'DetectorType']\n", - "Message models: ['HeartBeat', 'Retraction', 'CoincidenceTierMessage', 'SignificanceTierMessage', 'TimingTierMessage']\n" + "Message models: ['HeartbeatMessage', 'RetractionMessage', 'CoincidenceTierMessage', 'SignificanceTierMessage', 'TimingTierMessage']\n" ] } ], @@ -323,7 +323,8 @@ "print(\"Detector models:\", models.detectors.__all__)\n", "\n", "# Message Models\n", - "print(\"Message models:\", models.messages.__all__)" + "import inspect\n", + "print(\"Message models:\", [m for m in models.messages.__all__ if inspect.isclass(getattr(models.messages, m))])" ] }, { @@ -335,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -344,7 +345,7 @@ "text": [ "VALID MESSAGE INITIALIZATION:\n", "{'id': 'Super-K_Heartbeat_None',\n", - " 'uid': UUID('f7ab35c9-efa6-40a4-8f46-ce0a17fa7563'),\n", + " 'uuid': 'c1134bd9-d4f7-43b8-a590-4d04d661423c',\n", " 'tier': ,\n", " 'sent_time_utc': None,\n", " 'machine_time_utc': None,\n", @@ -352,7 +353,7 @@ " 'is_test': False,\n", " 'is_firedrill': False,\n", " 'meta': None,\n", - " 'schema_version': '1a',\n", + " 'schema_version': '0.1',\n", " 'detector_name': 'Super-K',\n", " 'detector_status': 'OFF'}\n" ] @@ -360,15 +361,15 @@ ], "source": [ "# Required fields = [detector_status, detector_name]\n", - "msg = models.messages.HeartBeat(detector_status=\"OFF\", detector_name=\"Super-K\")\n", + "msg = models.messages.HeartbeatMessage(detector_status=\"OFF\", detector_name=\"Super-K\")\n", "\n", "print(\"VALID MESSAGE INITIALIZATION:\")\n", - "pp(msg.model_dump())" + "pprint.pp(msg.model_dump())" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -381,25 +382,25 @@ }, { "ename": "ValidationError", - "evalue": "1 validation error for HeartBeat\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", + "evalue": "1 validation error for HeartbeatMessage\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[5], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: detector_status\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartBeat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdetector_status\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdetector_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSuper-K\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-formats-oWKN0o7n-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartBeat\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" + "Cell \u001b[0;32mIn[15], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: detector_status\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartbeatMessage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdetector_status\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdetector_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSuper-K\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-services-hhK1y8sx-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartbeatMessage\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" ] } ], "source": [ "print(\"\\nINVALID MESSAGE INITIALIZATION: detector_status\")\n", - "msg = models.messages.HeartBeat(detector_status=\"No\", detector_name=\"Super-K\")" + "msg = models.messages.HeartbeatMessage(detector_status=\"No\", detector_name=\"Super-K\")" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -412,25 +413,25 @@ }, { "ename": "ValidationError", - "evalue": "1 validation error for HeartBeat\n Value error, Invalid detector name. Options are: ['Super-K'] [type=value_error, input_value=HeartBeat(id='Super-J_Hea...', detector_status='ON'), input_type=HeartBeat]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", + "evalue": "1 validation error for HeartbeatMessage\n Value error, Invalid detector name. Options are: ['Super-K'] [type=value_error, input_value=HeartbeatMessage(id='Supe...', detector_status='ON'), input_type=HeartbeatMessage]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[6], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: detector_name\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartBeat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdetector_status\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mON\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdetector_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSuper-J\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-formats-oWKN0o7n-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartBeat\n Value error, Invalid detector name. Options are: ['Super-K'] [type=value_error, input_value=HeartBeat(id='Super-J_Hea...', detector_status='ON'), input_type=HeartBeat]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" + "Cell \u001b[0;32mIn[17], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: detector_name\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartbeatMessage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdetector_status\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mON\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdetector_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSuper-J\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-services-hhK1y8sx-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartbeatMessage\n Value error, Invalid detector name. Options are: ['Super-K'] [type=value_error, input_value=HeartbeatMessage(id='Supe...', detector_status='ON'), input_type=HeartbeatMessage]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" ] } ], "source": [ "print(\"\\nINVALID MESSAGE INITIALIZATION: detector_name\")\n", - "msg = models.messages.HeartBeat(detector_status=\"ON\", detector_name=\"Super-J\")" + "msg = models.messages.HeartbeatMessage(detector_status=\"ON\", detector_name=\"Super-J\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -443,57 +444,74 @@ }, { "ename": "ValidationError", - "evalue": "2 validation errors for HeartBeat\ndetector_name\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing\ndetector_status\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing", + "evalue": "2 validation errors for HeartbeatMessage\ndetector_name\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing\ndetector_status\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[7], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: Missing values\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartBeat\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-formats-oWKN0o7n-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mValidationError\u001b[0m: 2 validation errors for HeartBeat\ndetector_name\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing\ndetector_status\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing" + "Cell \u001b[0;32mIn[18], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE INITIALIZATION: Missing values\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmessages\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mHeartbeatMessage\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-services-hhK1y8sx-py3.11/lib/python3.11/site-packages/pydantic/main.py:164\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(__pydantic_self__, **data)\u001b[0m\n\u001b[1;32m 162\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m 163\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 164\u001b[0m \u001b[43m__pydantic_self__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m__pydantic_self__\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mValidationError\u001b[0m: 2 validation errors for HeartbeatMessage\ndetector_name\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing\ndetector_status\n Field required [type=missing, input_value={'tier': }, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.5/v/missing" ] } ], "source": [ "print(\"\\nINVALID MESSAGE INITIALIZATION: Missing values\")\n", - "msg = models.messages.HeartBeat()" + "msg = models.messages.HeartbeatMessage()" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "\n", + "VALID MESSAGE CREATION\n", + "{'id': 'Super-K_Heartbeat_None',\n", + " 'uuid': '3438376d-1d6e-4221-8611-d42d76b8465d',\n", + " 'tier': ,\n", + " 'sent_time_utc': None,\n", + " 'machine_time_utc': None,\n", + " 'is_pre_sn': False,\n", + " 'is_test': False,\n", + " 'is_firedrill': False,\n", + " 'meta': None,\n", + " 'schema_version': '0.1',\n", + " 'detector_name': 'Super-K',\n", + " 'detector_status': 'ON'}\n", "\n", "INVALID MESSAGE UPDATE: detector_status\n" ] }, { "ename": "ValidationError", - "evalue": "1 validation error for HeartBeat\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", + "evalue": "1 validation error for HeartbeatMessage\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[8], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE UPDATE: detector_status\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2\u001b[0m msg \u001b[38;5;241m=\u001b[39m models\u001b[38;5;241m.\u001b[39mmessages\u001b[38;5;241m.\u001b[39mHeartBeat(detector_status\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mON\u001b[39m\u001b[38;5;124m\"\u001b[39m, detector_name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSuper-K\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m \u001b[43mmsg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdetector_status\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", - "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-formats-oWKN0o7n-py3.11/lib/python3.11/site-packages/pydantic/main.py:786\u001b[0m, in \u001b[0;36mBaseModel.__setattr__\u001b[0;34m(self, name, value)\u001b[0m\n\u001b[1;32m 784\u001b[0m attr\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__set__\u001b[39m(\u001b[38;5;28mself\u001b[39m, value)\n\u001b[1;32m 785\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_config\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvalidate_assignment\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[0;32m--> 786\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_assignment\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 787\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_config\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mextra\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mallow\u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m name \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_fields:\n\u001b[1;32m 788\u001b[0m \u001b[38;5;66;03m# TODO - matching error\u001b[39;00m\n\u001b[1;32m 789\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m object has no field \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", - "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartBeat\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" + "Cell \u001b[0;32mIn[20], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m pprint\u001b[38;5;241m.\u001b[39mpp(msg\u001b[38;5;241m.\u001b[39mmodel_dump())\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mINVALID MESSAGE UPDATE: detector_status\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 6\u001b[0m \u001b[43mmsg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdetector_status\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/snews-data-services-hhK1y8sx-py3.11/lib/python3.11/site-packages/pydantic/main.py:786\u001b[0m, in \u001b[0;36mBaseModel.__setattr__\u001b[0;34m(self, name, value)\u001b[0m\n\u001b[1;32m 784\u001b[0m attr\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__set__\u001b[39m(\u001b[38;5;28mself\u001b[39m, value)\n\u001b[1;32m 785\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_config\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvalidate_assignment\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[0;32m--> 786\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_assignment\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvalue\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 787\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_config\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mextra\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mallow\u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m name \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_fields:\n\u001b[1;32m 788\u001b[0m \u001b[38;5;66;03m# TODO - matching error\u001b[39;00m\n\u001b[1;32m 789\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m object has no field \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "\u001b[0;31mValidationError\u001b[0m: 1 validation error for HeartbeatMessage\ndetector_status\n Value error, Detector status must be either ON or OFF [type=value_error, input_value='No', input_type=str]\n For further information visit https://errors.pydantic.dev/2.5/v/value_error" ] } ], "source": [ + "print(\"\\nVALID MESSAGE CREATION\")\n", + "msg = models.messages.HeartbeatMessage(detector_status=\"ON\", detector_name=\"Super-K\")\n", + "pprint.pp(msg.model_dump())\n", + "\n", "print(\"\\nINVALID MESSAGE UPDATE: detector_status\")\n", - "msg = models.messages.HeartBeat(detector_status=\"ON\", detector_name=\"Super-K\")\n", "msg.detector_status = \"No\"" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -501,9 +519,9 @@ "output_type": "stream", "text": [ "\n", - "INVALID MESSAGE INITIALIZATION: detector_name\n", + "VALID MESSAGE INITIALIZATION: fake detector_name when is_test=True\n", "{'id': 'Super-J_Heartbeat_None',\n", - " 'uid': UUID('94dd33fa-20c3-4810-8187-1ea1fbfc1289'),\n", + " 'uuid': '603b5735-706f-43a8-92fe-542828925355',\n", " 'tier': ,\n", " 'sent_time_utc': None,\n", " 'machine_time_utc': None,\n", @@ -511,7 +529,7 @@ " 'is_test': True,\n", " 'is_firedrill': False,\n", " 'meta': None,\n", - " 'schema_version': '1a',\n", + " 'schema_version': '0.1',\n", " 'detector_name': 'Super-J',\n", " 'detector_status': 'ON'}\n" ] @@ -519,13 +537,13 @@ ], "source": [ "print(\"\\nVALID MESSAGE INITIALIZATION: fake detector_name when is_test=True\")\n", - "msg = models.messages.HeartBeat(detector_status=\"ON\", detector_name=\"Super-J\", is_test=True)\n", - "pp(msg.model_dump())" + "msg = models.messages.HeartbeatMessage(detector_status=\"ON\", detector_name=\"Super-J\", is_test=True)\n", + "pprint.pp(msg.model_dump())" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -533,30 +551,37 @@ "output_type": "stream", "text": [ "MESSAGE 1\n", - "id='Super-J_Heartbeat_None' uid=UUID('ee3fd344-7529-4f91-8539-aa2a1ba1dce8') tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='1a' detector_name='Super-J' detector_status='ON'\n", + "id='Super-J_Heartbeat_None' uuid='1a5a2454-6982-42b8-a204-f6567380e2a0' tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='0.1' detector_name='Super-J' detector_status='ON'\n", "\n", "MESSAGE 2\n", "None\n", "\n", "\n", "MESSAGE 1\n", - "id='Super-J_Heartbeat_None' uid=UUID('ee3fd344-7529-4f91-8539-aa2a1ba1dce8') tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='1a' detector_name='Super-J' detector_status='ON'\n", + "id='Super-J_Heartbeat_None' uuid='1a5a2454-6982-42b8-a204-f6567380e2a0' tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='0.1' detector_name='Super-J' detector_status='ON'\n", "\n", "MESSAGE 2\n", - "id='Super-J_Heartbeat_None' uid=UUID('ee3fd344-7529-4f91-8539-aa2a1ba1dce8') tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='1a' detector_name='Super-J' detector_status='ON'\n" + "id='Super-J_Heartbeat_None' uuid='1a5a2454-6982-42b8-a204-f6567380e2a0' tier= sent_time_utc=None machine_time_utc=None is_pre_sn=False is_test=True is_firedrill=False meta=None schema_version='0.1' detector_name='Super-J' detector_status='ON'\n" ] } ], "source": [ - "msg1 = models.messages.HeartBeat(detector_status=\"ON\", detector_name=\"Super-J\", is_test=True)\n", + "msg1 = models.messages.HeartbeatMessage(detector_status=\"ON\", detector_name=\"Super-J\", is_test=True)\n", "msg2 = None\n", "print(f\"MESSAGE 1\\n{msg1}\\n\\nMESSAGE 2\\n{msg2}\\n\")\n", "\n", "msg1_dict = msg1.model_dump()\n", "\n", - "msg2 = models.messages.HeartBeat(**msg1_dict)\n", + "msg2 = models.messages.HeartbeatMessage(**msg1_dict)\n", "print(f\"\\nMESSAGE 1\\n{msg1}\\n\\nMESSAGE 2\\n{msg2}\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -575,7 +600,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/snews/models/messages.py b/snews/models/messages.py index 9ce4915..40c9112 100644 --- a/snews/models/messages.py +++ b/snews/models/messages.py @@ -1,11 +1,4 @@ # -*- coding: utf-8 -*- -__all__ = [ - "HeartBeat", - "Retraction", - "CoincidenceTierMessage", - "SignificanceTierMessage", - "TimingTierMessage" -] # Standard library modules from datetime import UTC, datetime, timedelta @@ -15,15 +8,24 @@ # Third-party modules import numpy as np -from pydantic import (UUID4, BaseModel, Field, NonNegativeFloat, - field_validator, model_validator, root_validator, - validator) +from pydantic import (BaseModel, Field, NonNegativeFloat, ValidationError, + field_validator, model_validator) # Local modules from ..__version__ import schema_version from ..data import detectors from ..models.timing import PrecisionTimestamp +__all__ = [ + "HeartbeatMessage", + "RetractionMessage", + "CoincidenceTierMessage", + "SignificanceTierMessage", + "TimingTierMessage", + "compatible_message_types", + "create_messages", +] + # ................................................................................................. def convert_timestamp_to_ns_precision(timestamp: Union[str, datetime, np.datetime64]) -> str: @@ -68,10 +70,11 @@ class Config: description="Textual identifier for the message" ) - uid: UUID4 = Field( + uuid: str = Field( title="Unique message ID", default_factory=uuid4, - description="Unique identifier for the message" + description="Unique identifier for the message", + validate_default=True ) tier: Tier = Field( @@ -83,13 +86,15 @@ class Config: sent_time_utc: Optional[str] = Field( default=None, title="Sent time (UTC)", - description="Time the message was sent in ISO 8601-1:2019 format" + description="Time the message was sent in ISO 8601-1:2019 format", + validate_default=True ) machine_time_utc: Optional[str] = Field( default=None, title="Machine time (UTC)", - description="Time of the event at the detector in ISO 8601-1:2019 format" + description="Time of the event at the detector in ISO 8601-1:2019 format", + validate_default=True ) is_pre_sn: Optional[bool] = Field( @@ -123,7 +128,7 @@ class Config: frozen=True, ) - @validator("sent_time_utc", "machine_time_utc", pre=True, always=True) + @field_validator("sent_time_utc", "machine_time_utc", mode="before") def _convert_timestamp_to_ns_precision(cls, v): """ Convert to nanosecond precision (before running Pydantic validators). @@ -131,6 +136,13 @@ def _convert_timestamp_to_ns_precision(cls, v): if v is not None: return convert_timestamp_to_ns_precision(timestamp=v) + @field_validator("uuid", mode="before") + def _cast_uuid_to_string(cls, v): + """ + Cast UUID to string (before running Pydantic validators). + """ + return str(v) + @model_validator(mode="after") def _format_id(self): """ @@ -143,6 +155,18 @@ def _format_id(self): return self + def fields(self): + """ + Return a list of fields for the message. + """ + return list(self.model_fields.keys()) + + def required_fields(self): + """ + Return a list of required fields for the message. + """ + return [k for k, v in self.model_fields.items() if v.is_required()] + # ................................................................................................. class DetectorMessageBase(MessageBase): @@ -171,7 +195,7 @@ def _validate_detector_name(self) -> str: # ................................................................................................. -class HeartBeat(DetectorMessageBase): +class HeartbeatMessage(DetectorMessageBase): """ Heartbeat detector message. """ @@ -186,7 +210,7 @@ class Config: examples=["ON", "OFF"] ) - @root_validator(pre=True) + @model_validator(mode="before") def _set_tier(cls, values): values['tier'] = Tier.HEART_BEAT return values @@ -204,7 +228,7 @@ def _validate_model(self): # ................................................................................................. -class Retraction(DetectorMessageBase): +class RetractionMessage(DetectorMessageBase): """ Retraction detector message. """ @@ -212,7 +236,7 @@ class Retraction(DetectorMessageBase): class Config: validate_assignment = True - retract_message_uid: Optional[UUID4] = Field( + retract_message_uid: Optional[str] = Field( default=None, title="Unique message ID", description="Unique identifier for the message to retract" @@ -230,7 +254,7 @@ class Config: description="Reason for retraction", ) - @root_validator(pre=True) + @model_validator(mode="before") def _set_tier(cls, values): values['tier'] = Tier.RETRACTION return values @@ -238,17 +262,17 @@ def _set_tier(cls, values): @model_validator(mode="after") def _validate_model(self): if self.retract_latest and self.retract_message_uid is not None: - raise ValueError("retract_message_uid cannot be specified when retract_latest=True") + raise ValueError("retract_message_uuid cannot be specified when retract_latest=True") if not self.retract_latest and self.retract_message_uid is None: - raise ValueError("Must specify either retract_message_uid or retract_latest=True") + raise ValueError("Must specify either retract_message_uuid or retract_latest=True") return self # ................................................................................................. class TierMessageBase(DetectorMessageBase): """ - Tier detector base message + Tier base message """ class Config: @@ -276,13 +300,13 @@ class TimingTierMessage(TierMessageBase): class Config: validate_assignment = True - timing_series: List[str] = Field( + timing_series: List[Union[str, int]] = Field( ..., title="Timing Series", description="Timing series of the event", ) - @root_validator(pre=True) + @model_validator(mode="before") def _set_tier(cls, values): values['tier'] = Tier.TIMING_TIER return values @@ -322,7 +346,7 @@ class Config: description="Time bin width of the event", ) - @root_validator(pre=True) + @model_validator(mode="before") def _set_tier(cls, values): values['tier'] = Tier.SIGNIFICANCE_TIER return values @@ -358,7 +382,7 @@ class Config: description="Time of the first neutrino in the event in ISO 8601-1:2019 format" ) - @root_validator(pre=True) + @model_validator(mode="before") def _set_tier(cls, values): values['tier'] = Tier.COINCIDENCE_TIER return values @@ -384,3 +408,41 @@ def _validate_neutrino_time(self): raise ValueError("neutrino_time_utc must be in the past") return self + + +# ................................................................................................. +def compatible_message_types(**kwargs) -> list: + """ + Return a list of message types that are compatible with the given keyword arguments. + """ + + message_types = [ + HeartbeatMessage, + RetractionMessage, + CoincidenceTierMessage, + SignificanceTierMessage, + TimingTierMessage, + ] + + compatible_message_types = [] + for message_type in message_types: + try: + message_type(**kwargs) + compatible_message_types.append(message_type) + except ValidationError: + pass + + return compatible_message_types + + +# ................................................................................................. +def create_messages(**kwargs) -> list: + """ + Return a list of messages initialized with the given keyword arguments. + """ + + messages = [] + for message_type in compatible_message_types(**kwargs): + messages.append(message_type(**kwargs)) + + return messages diff --git a/snews/schema/CoincidenceTierMessage.schema.json b/snews/schema/CoincidenceTierMessage.schema.json index 92c3c6c..88fb529 100644 --- a/snews/schema/CoincidenceTierMessage.schema.json +++ b/snews/schema/CoincidenceTierMessage.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "Tier": { "enum": [ @@ -30,9 +30,8 @@ "description": "Textual identifier for the message", "title": "Human-readable message ID" }, - "uid": { + "uuid": { "description": "Unique identifier for the message", - "format": "uuid4", "title": "Unique message ID", "type": "string" }, @@ -132,7 +131,7 @@ "type": "null" } ], - "default": "1a", + "default": "0.1", "description": "Schema version of the message", "title": "Schema Version" }, diff --git a/snews/schema/Detector.schema.json b/snews/schema/Detector.schema.json index 7db753f..9e7b4f0 100644 --- a/snews/schema/Detector.schema.json +++ b/snews/schema/Detector.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "DetectorType": { "enum": [ diff --git a/snews/schema/HeartBeat.schema.json b/snews/schema/HeartbeatMessage.schema.json similarity index 96% rename from snews/schema/HeartBeat.schema.json rename to snews/schema/HeartbeatMessage.schema.json index da61abd..c89745e 100644 --- a/snews/schema/HeartBeat.schema.json +++ b/snews/schema/HeartbeatMessage.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "Tier": { "enum": [ @@ -30,9 +30,8 @@ "description": "Textual identifier for the message", "title": "Human-readable message ID" }, - "uid": { + "uuid": { "description": "Unique identifier for the message", - "format": "uuid4", "title": "Unique message ID", "type": "string" }, @@ -132,7 +131,7 @@ "type": "null" } ], - "default": "1a", + "default": "0.1", "description": "Schema version of the message", "title": "Schema Version" }, @@ -156,6 +155,6 @@ "detector_name", "detector_status" ], - "title": "HeartBeat", + "title": "HeartbeatMessage", "type": "object" } \ No newline at end of file diff --git a/snews/schema/Retraction.schema.json b/snews/schema/RetractionMessage.schema.json similarity index 96% rename from snews/schema/Retraction.schema.json rename to snews/schema/RetractionMessage.schema.json index 7048f1e..b630146 100644 --- a/snews/schema/Retraction.schema.json +++ b/snews/schema/RetractionMessage.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "Tier": { "enum": [ @@ -30,9 +30,8 @@ "description": "Textual identifier for the message", "title": "Human-readable message ID" }, - "uid": { + "uuid": { "description": "Unique identifier for the message", - "format": "uuid4", "title": "Unique message ID", "type": "string" }, @@ -132,7 +131,7 @@ "type": "null" } ], - "default": "1a", + "default": "0.1", "description": "Schema version of the message", "title": "Schema Version" }, @@ -144,7 +143,6 @@ "retract_message_uid": { "anyOf": [ { - "format": "uuid4", "type": "string" }, { @@ -172,6 +170,6 @@ "detector_name", "retraction_reason" ], - "title": "Retraction", + "title": "RetractionMessage", "type": "object" } \ No newline at end of file diff --git a/snews/schema/SigTierMessage.schema.json b/snews/schema/SigTierMessage.schema.json deleted file mode 100644 index 30f9c9d..0000000 --- a/snews/schema/SigTierMessage.schema.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "0.1a", - "$defs": { - "Tier": { - "enum": [ - "Heartbeat", - "Retraction", - "TimeTier", - "SigTier", - "CoincidenceTier" - ], - "title": "Tier", - "type": "string" - } - }, - "properties": { - "id": { - "default": null, - "description": "Textual identifier for the message", - "title": "Human-readable message ID", - "type": "string" - }, - "uid": { - "description": "Unique identifier for the message", - "format": "uuid4", - "title": "Unique message ID", - "type": "string" - }, - "tier": { - "allOf": [ - { - "$ref": "#/$defs/Tier" - } - ], - "description": "Message tier", - "title": "Message Tier" - }, - "sent_time_utc": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Time the message was sent in ISO format", - "title": "Sent Time Utc" - }, - "expiration_time_utc": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Time the message expires in ISO format", - "title": "Expiration Time (UTC)" - }, - "machine_time_utc": { - "description": "Time of the event at the detector in ISO format", - "title": "Machine Time Utc", - "type": "string" - }, - "is_pre_sn": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is associated with pre-SN", - "title": "Pre-SN Flag" - }, - "is_test": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is a test", - "title": "Test Flag" - }, - "is_firedrill": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is associated with a fire drill", - "title": "Fire Drill Flag" - }, - "meta": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Attached metadata", - "title": "Metadata" - }, - "schema_version": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "0.1a", - "description": "Schema version of the message", - "title": "Schema Version" - }, - "detector_name": { - "default": null, - "description": "Name of the detector that sent the message", - "title": "Detector Name", - "type": "string" - }, - "neutrino_time_utc": { - "description": "Time of the first neutrino in the event in ISO format", - "title": "Neutrino Time Utc", - "type": "string" - }, - "p_values": { - "description": "p-values of the event", - "items": { - "minimum": 0.0, - "type": "number" - }, - "title": "P Values", - "type": "array" - }, - "t_bin_width_sec": { - "description": "Time bin width of the event", - "minimum": 0.0, - "title": "T Bin Width Sec", - "type": "number" - } - }, - "required": [ - "tier", - "machine_time_utc", - "neutrino_time_utc", - "p_values", - "t_bin_width_sec" - ], - "title": "SigTierMessage", - "type": "object" -} \ No newline at end of file diff --git a/snews/schema/SignificanceTierMessage.schema.json b/snews/schema/SignificanceTierMessage.schema.json index 3d0e215..8a908e6 100644 --- a/snews/schema/SignificanceTierMessage.schema.json +++ b/snews/schema/SignificanceTierMessage.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "Tier": { "enum": [ @@ -30,9 +30,8 @@ "description": "Textual identifier for the message", "title": "Human-readable message ID" }, - "uid": { + "uuid": { "description": "Unique identifier for the message", - "format": "uuid4", "title": "Unique message ID", "type": "string" }, @@ -132,7 +131,7 @@ "type": "null" } ], - "default": "1a", + "default": "0.1", "description": "Schema version of the message", "title": "Schema Version" }, diff --git a/snews/schema/TimeTierMessage.schema.json b/snews/schema/TimeTierMessage.schema.json deleted file mode 100644 index adf9473..0000000 --- a/snews/schema/TimeTierMessage.schema.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "0.1a", - "$defs": { - "Tier": { - "enum": [ - "Heartbeat", - "Retraction", - "TimeTier", - "SigTier", - "CoincidenceTier" - ], - "title": "Tier", - "type": "string" - } - }, - "properties": { - "id": { - "default": null, - "description": "Textual identifier for the message", - "title": "Human-readable message ID", - "type": "string" - }, - "uid": { - "description": "Unique identifier for the message", - "format": "uuid4", - "title": "Unique message ID", - "type": "string" - }, - "tier": { - "allOf": [ - { - "$ref": "#/$defs/Tier" - } - ], - "description": "Message tier", - "title": "Message Tier" - }, - "sent_time_utc": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Time the message was sent in ISO format", - "title": "Sent Time Utc" - }, - "expiration_time_utc": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Time the message expires in ISO format", - "title": "Expiration Time (UTC)" - }, - "machine_time_utc": { - "description": "Time of the event at the detector in ISO format", - "title": "Machine Time Utc", - "type": "string" - }, - "is_pre_sn": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is associated with pre-SN", - "title": "Pre-SN Flag" - }, - "is_test": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is a test", - "title": "Test Flag" - }, - "is_firedrill": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "description": "True if the message is associated with a fire drill", - "title": "Fire Drill Flag" - }, - "meta": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Attached metadata", - "title": "Metadata" - }, - "schema_version": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "0.1a", - "description": "Schema version of the message", - "title": "Schema Version" - }, - "detector_name": { - "default": null, - "description": "Name of the detector that sent the message", - "title": "Detector Name", - "type": "string" - }, - "neutrino_time_utc": { - "description": "Time of the first neutrino in the event in ISO format", - "title": "Neutrino Time Utc", - "type": "string" - }, - "timing_series": { - "description": "Timing series of the event", - "items": { - "type": "string" - }, - "title": "Timing Series", - "type": "array" - } - }, - "required": [ - "tier", - "machine_time_utc", - "neutrino_time_utc", - "timing_series" - ], - "title": "TimeTierMessage", - "type": "object" -} \ No newline at end of file diff --git a/snews/schema/TimingTierMessage.schema.json b/snews/schema/TimingTierMessage.schema.json index a01a2ac..ac9281b 100644 --- a/snews/schema/TimingTierMessage.schema.json +++ b/snews/schema/TimingTierMessage.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "schema_author": "Supernova Early Warning System (SNEWS)", - "schema_version": "1a", + "schema_version": "0.1", "$defs": { "Tier": { "enum": [ @@ -30,9 +30,8 @@ "description": "Textual identifier for the message", "title": "Human-readable message ID" }, - "uid": { + "uuid": { "description": "Unique identifier for the message", - "format": "uuid4", "title": "Unique message ID", "type": "string" }, @@ -132,7 +131,7 @@ "type": "null" } ], - "default": "1a", + "default": "0.1", "description": "Schema version of the message", "title": "Schema Version" }, @@ -159,7 +158,14 @@ "timing_series": { "description": "Timing series of the event", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] }, "title": "Timing Series", "type": "array" diff --git a/test/models/test_message_models.py b/test/models/test_message_models.py index d3b46a7..d4073fb 100644 --- a/test/models/test_message_models.py +++ b/test/models/test_message_models.py @@ -10,9 +10,9 @@ # Local modules from snews.data import detectors -from snews.models.messages import (CoincidenceTierMessage, HeartBeat, - Retraction, SignificanceTierMessage, Tier, - TimingTierMessage) +from snews.models.messages import (CoincidenceTierMessage, HeartbeatMessage, + RetractionMessage, SignificanceTierMessage, + Tier, TimingTierMessage) # STRATEGIES ====================================================================================== @@ -64,13 +64,14 @@ # Retraction message strategy_required_fields_retraction = { **strategy_required_fields_base, - "retract_message_uid": st.uuids(version=4), + "retract_message_uid": st.uuids(version=4).map(lambda x: str(x)), "retraction_reason": st.text(min_size=1), } # TESTS =========================================================================================== + # Timing Tier Test @given(**strategy_required_fields_tier_timing) def test_snews_message_model_timing_tier_required(**kwargs): @@ -83,7 +84,9 @@ def test_snews_message_model_timing_tier_invalid_timing_series(**kwargs): msg = TimingTierMessage(**kwargs) msg.timing_series = ["1987-02-24T05:31:00Z", "Feb 24, 1987 5:31 AM UTC"] - assert "Timing series entries must be in ISO 8601-1:2019 format" in str(exc_info.value) + assert "Timing series entries must be in ISO 8601-1:2019 format" in str( + exc_info.value + ) # Significance Tier Test @@ -127,17 +130,16 @@ def test_snews_message_model_coincidence_tier_nu_time_in_future(**kwargs): assert "neutrino_time_utc must be in the past" in str(exc_info.value) -# Heartbeat Test +# Heartbeat Tests @given(**strategy_required_fields_heartbeat) def test_snews_message_model_heartbeat_required(**kwargs): - HeartBeat(**kwargs) + HeartbeatMessage(**kwargs) -# Heartbeat Test @given(**strategy_required_fields_heartbeat) def test_snews_message_model_heartbeat_invalid_detector(**kwargs): with pytest.raises(ValueError) as exc_info: - msg = HeartBeat(**kwargs) + msg = HeartbeatMessage(**kwargs) msg.is_test = False msg.detector_name = "Super-Duper-K" @@ -147,33 +149,37 @@ def test_snews_message_model_heartbeat_invalid_detector(**kwargs): @given(**strategy_required_fields_heartbeat) def test_snews_message_model_heartbeat_invalid_detector_status(**kwargs): with pytest.raises(ValueError) as exc_info: - msg = HeartBeat(**kwargs) + msg = HeartbeatMessage(**kwargs) msg.detector_status = "OK" assert "Detector status must be either ON or OFF" in str(exc_info.value) -# Retraction Test +# Retraction Tests @given(**strategy_required_fields_retraction) def test_snews_message_model_retraction_required(**kwargs): - Retraction(**kwargs) + RetractionMessage(**kwargs) @given(**strategy_required_fields_retraction) def test_snews_message_model_retraction_validation_both_indicators(**kwargs): with pytest.raises(ValueError) as exc_info: - msg = Retraction(**kwargs) + msg = RetractionMessage(**kwargs) msg.retract_latest = True msg.retract_message_uid = "1234567890" - assert "retract_message_uid cannot be specified when retract_latest=True" in str(exc_info.value) + assert "retract_message_uuid cannot be specified when retract_latest=True" in str( + exc_info.value + ) @given(**strategy_required_fields_retraction) def test_snews_message_model_retraction_validation_neither_indicator(**kwargs): with pytest.raises(ValueError) as exc_info: - msg = Retraction(**kwargs) + msg = RetractionMessage(**kwargs) msg.retract_latest = False msg.retract_message_uid = None - assert "Must specify either retract_message_uid or retract_latest=True" in str(exc_info.value) + assert "Must specify either retract_message_uuid or retract_latest=True" in str( + exc_info.value + )