diff --git a/.devcontainer/.dev_config.yaml b/.devcontainer/.dev_config.yaml index 846fa3e..18f55df 100644 --- a/.devcontainer/.dev_config.yaml +++ b/.devcontainer/.dev_config.yaml @@ -11,14 +11,14 @@ cors_allowed_headers: [] db_connection_str: "mongodb://mongodb:27017" db_name: "access-requests" -notification_event_topic: notifications -notification_event_type: notification +access_request_events_topic: access_request_events +access_request_created_event_type: access_request_created +access_request_allowed_event_type: access_request_allowed +access_request_denied_event_type: access_request_denied service_instance_id: "1" kafka_servers: ["kafka:9092"] download_access_url: "http://127.0.0.1:8080/download-access" -data_steward_email: "helpdesk@ghga.de" - auth_key: "{}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec7c95a..60c8352 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,7 +48,7 @@ repos: - id: no-commit-to-branch args: [--branch, dev, --branch, int, --branch, main] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.2 + rev: v0.3.3 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/.pyproject_generation/pyproject_custom.toml b/.pyproject_generation/pyproject_custom.toml index 9e97e84..47094f6 100644 --- a/.pyproject_generation/pyproject_custom.toml +++ b/.pyproject_generation/pyproject_custom.toml @@ -1,9 +1,9 @@ [project] name = "ars" -version = "1.1.1" +version = "2.0.0" description = "Access Request Service" dependencies = [ - "ghga-event-schemas~=3.0.0", + "ghga-event-schemas~=3.1.0", "ghga-service-commons[api,auth]>=2.0.0, <3", "hexkit[mongodb,akafka]>=2.1.0", "httpx>=0.27.0", diff --git a/README.md b/README.md index 75ffbfc..1c5c9fc 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ We recommend using the provided Docker container. A pre-build version is available at [docker hub](https://hub.docker.com/repository/docker/ghga/access-request-service): ```bash -docker pull ghga/access-request-service:1.1.1 +docker pull ghga/access-request-service:2.0.0 ``` Or you can build the container yourself from the [`./Dockerfile`](./Dockerfile): ```bash # Execute in the repo's root dir: -docker build -t ghga/access-request-service:1.1.1 . +docker build -t ghga/access-request-service:2.0.0 . ``` For production-ready deployment, we recommend using Kubernetes, however, @@ -30,7 +30,7 @@ for simple use cases, you could execute the service using docker on a single server: ```bash # The entrypoint is preconfigured: -docker run -p 8080:8080 ghga/access-request-service:1.1.1 --help +docker run -p 8080:8080 ghga/access-request-service:2.0.0 --help ``` If you prefer not to use containers, you may install the service from source: @@ -47,14 +47,6 @@ ars --help ### Parameters The service requires the following configuration parameters: -- **`data_steward_email`**: An email address that can be used to notify data stewards. - - - **Any of** - - - *string, format: email* - - - *null* - - **`access_upfront_max_days`** *(integer)*: The maximum lead time in days to request access grants. Default: `180`. - **`access_grant_min_days`** *(integer)*: The minimum number of days that the access will be granted. Default: `7`. @@ -71,23 +63,43 @@ The service requires the following configuration parameters: ``` -- **`notification_event_topic`** *(string)*: Name of the topic used for notification events. +- **`access_request_events_topic`** *(string)*: The topic used for events related to access requests. + + + Examples: + + ```json + "access_requests" + ``` + + +- **`access_request_created_event_type`** *(string)*: The type to use for 'access request created' events. + + + Examples: + + ```json + "access_request_created" + ``` + + +- **`access_request_allowed_event_type`** *(string)*: The type to use for 'access request allowed' events. Examples: ```json - "notifications" + "access_request_allowed" ``` -- **`notification_event_type`** *(string)*: The type used for notification events. +- **`access_request_denied_event_type`** *(string)*: The type to use for 'access request denied' events. Examples: ```json - "notification" + "access_request_denied" ``` diff --git a/config_schema.json b/config_schema.json index 3c23ec9..9422f54 100644 --- a/config_schema.json +++ b/config_schema.json @@ -2,19 +2,6 @@ "additionalProperties": false, "description": "Modifies the orginal Settings class provided by the user", "properties": { - "data_steward_email": { - "anyOf": [ - { - "format": "email", - "type": "string" - }, - { - "type": "null" - } - ], - "description": "An email address that can be used to notify data stewards", - "title": "Data Steward Email" - }, "access_upfront_max_days": { "default": 180, "description": "The maximum lead time in days to request access grants", @@ -41,20 +28,36 @@ "title": "Download Access Url", "type": "string" }, - "notification_event_topic": { - "description": "Name of the topic used for notification events.", + "access_request_events_topic": { + "description": "The topic used for events related to access requests.", + "examples": [ + "access_requests" + ], + "title": "Access Request Events Topic", + "type": "string" + }, + "access_request_created_event_type": { + "description": "The type to use for 'access request created' events", + "examples": [ + "access_request_created" + ], + "title": "Access Request Created Event Type", + "type": "string" + }, + "access_request_allowed_event_type": { + "description": "The type to use for 'access request allowed' events", "examples": [ - "notifications" + "access_request_allowed" ], - "title": "Notification Event Topic", + "title": "Access Request Allowed Event Type", "type": "string" }, - "notification_event_type": { - "description": "The type used for notification events.", + "access_request_denied_event_type": { + "description": "The type to use for 'access request denied' events", "examples": [ - "notification" + "access_request_denied" ], - "title": "Notification Event Type", + "title": "Access Request Denied Event Type", "type": "string" }, "service_name": { @@ -341,10 +344,11 @@ } }, "required": [ - "data_steward_email", "download_access_url", - "notification_event_topic", - "notification_event_type", + "access_request_events_topic", + "access_request_created_event_type", + "access_request_allowed_event_type", + "access_request_denied_event_type", "service_instance_id", "kafka_servers", "db_connection_str", diff --git a/example_config.yaml b/example_config.yaml index 2b05118..765482b 100644 --- a/example_config.yaml +++ b/example_config.yaml @@ -1,5 +1,9 @@ access_grant_max_days: 730 access_grant_min_days: 7 +access_request_allowed_event_type: access_request_allowed +access_request_created_event_type: access_request_created +access_request_denied_event_type: access_request_denied +access_request_events_topic: access_request_events access_upfront_max_days: 180 api_root_path: '' auth_algs: @@ -16,7 +20,6 @@ cors_allow_credentials: false cors_allowed_headers: [] cors_allowed_methods: [] cors_allowed_origins: [] -data_steward_email: helpdesk@ghga.de db_connection_str: '**********' db_name: access-requests docs_url: /docs @@ -32,8 +35,6 @@ kafka_ssl_keyfile: '' kafka_ssl_password: '' log_format: null log_level: INFO -notification_event_topic: notifications -notification_event_type: notification openapi_url: /openapi.json port: 8080 service_instance_id: '1' diff --git a/lock/requirements-dev-template.in b/lock/requirements-dev-template.in index d596e9e..b543188 100644 --- a/lock/requirements-dev-template.in +++ b/lock/requirements-dev-template.in @@ -28,6 +28,7 @@ jsonschema2md>=1.0.0 setuptools>=69.1.0 # required since switch to pyproject.toml and pip-tools -pip-tools>=7.4.0 tomli>=2.0.1 tomli_w>=1.0.0 + +uv>=0.1.21 diff --git a/lock/requirements-dev.txt b/lock/requirements-dev.txt index 2a67e18..e47a729 100644 --- a/lock/requirements-dev.txt +++ b/lock/requirements-dev.txt @@ -1,9 +1,5 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --generate-hashes --output-file=/workspace/lock/requirements-dev.txt /tmp/tmp6r0ap6iq/pyproject.toml /workspace/lock/requirements-dev.in -# +# This file was autogenerated by uv via the following command: +# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements-dev.txt /tmp/tmp8rmzyh_v/pyproject.toml /workspace/lock/requirements-dev.in aiokafka==0.8.1 \ --hash=sha256:1e24839088fd6d3ff481cc09a48ea487b997328df11630bc0a1b88255edbcfe9 \ --hash=sha256:1f43d2afd7d3e4407ada8d754895fad7c344ca00648a8a38418d76564eaaf6cd \ @@ -52,10 +48,6 @@ attrs==23.2.0 \ # via # jsonschema # referencing -build==1.1.1 \ - --hash=sha256:8ed0851ee76e6e38adce47e4bee3b51c771d86c64cf578d0c2245567ee200e73 \ - --hash=sha256:8eea65bb45b1aac2e734ba2cc8dad3a6d97d97901a395bd0ed3e7b46953d2a31 - # via pip-tools certifi==2024.2.2 \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 @@ -217,63 +209,61 @@ click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via - # -r /workspace/lock/requirements-dev-template.in - # pip-tools # typer # uvicorn -coverage[toml]==7.4.3 \ - --hash=sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa \ - --hash=sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003 \ - --hash=sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f \ - --hash=sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c \ - --hash=sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e \ - --hash=sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0 \ - --hash=sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9 \ - --hash=sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52 \ - --hash=sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e \ - --hash=sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454 \ - --hash=sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0 \ - --hash=sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079 \ - --hash=sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352 \ - --hash=sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f \ - --hash=sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30 \ - --hash=sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe \ - --hash=sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113 \ - --hash=sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765 \ - --hash=sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc \ - --hash=sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e \ - --hash=sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501 \ - --hash=sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7 \ - --hash=sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2 \ - --hash=sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f \ - --hash=sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4 \ - --hash=sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524 \ - --hash=sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c \ - --hash=sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51 \ - --hash=sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840 \ - --hash=sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6 \ - --hash=sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee \ - --hash=sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e \ - --hash=sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45 \ - --hash=sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba \ - --hash=sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d \ - --hash=sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3 \ - --hash=sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10 \ - --hash=sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e \ - --hash=sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb \ - --hash=sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9 \ - --hash=sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a \ - --hash=sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47 \ - --hash=sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1 \ - --hash=sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3 \ - --hash=sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914 \ - --hash=sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328 \ - --hash=sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6 \ - --hash=sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d \ - --hash=sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0 \ - --hash=sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94 \ - --hash=sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc \ - --hash=sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2 +coverage==7.4.4 \ + --hash=sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c \ + --hash=sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63 \ + --hash=sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7 \ + --hash=sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f \ + --hash=sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8 \ + --hash=sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf \ + --hash=sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0 \ + --hash=sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384 \ + --hash=sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76 \ + --hash=sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7 \ + --hash=sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d \ + --hash=sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70 \ + --hash=sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f \ + --hash=sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818 \ + --hash=sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b \ + --hash=sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d \ + --hash=sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec \ + --hash=sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083 \ + --hash=sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2 \ + --hash=sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9 \ + --hash=sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd \ + --hash=sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade \ + --hash=sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e \ + --hash=sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a \ + --hash=sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227 \ + --hash=sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87 \ + --hash=sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c \ + --hash=sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e \ + --hash=sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c \ + --hash=sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e \ + --hash=sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd \ + --hash=sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec \ + --hash=sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562 \ + --hash=sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8 \ + --hash=sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677 \ + --hash=sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357 \ + --hash=sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c \ + --hash=sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd \ + --hash=sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49 \ + --hash=sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286 \ + --hash=sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1 \ + --hash=sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf \ + --hash=sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51 \ + --hash=sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409 \ + --hash=sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384 \ + --hash=sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e \ + --hash=sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978 \ + --hash=sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57 \ + --hash=sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e \ + --hash=sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2 \ + --hash=sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48 \ + --hash=sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4 # via pytest-cov cryptography==42.0.5 \ --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ @@ -341,14 +331,12 @@ filelock==3.13.1 \ --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c # via virtualenv -ghga-event-schemas==3.0.0 \ - --hash=sha256:67dce9db2d45be862f69a58a903fac43416997ad50fd4f1f1d25822533a187d1 \ - --hash=sha256:7a8952e37bd935809f324aa21653b008e01a5ea920d36217734ee35776d92602 - # via ars (pyproject.toml) -ghga-service-commons[api,auth,objectstorage]==2.0.1 \ +ghga-event-schemas==3.1.0 \ + --hash=sha256:87706784895376314124d30a0ba77dd7cfebdbfbcbb98e88d2a836486f11c385 \ + --hash=sha256:fa0048eda36002e7a79bc9084d2acdcc9eb9d38bcf263d6f68ad6fc453cae130 +ghga-service-commons==2.0.1 \ --hash=sha256:957c44d8ad006da525c506d815210a701af2dc4ebf0e6473800c00f926f77ce8 \ --hash=sha256:9e7ea822ec692fdc6df93ea62ed65e8dd2bf48886bf1441c96697c0be2101c15 - # via ars (pyproject.toml) gprof2dot==2022.7.29 \ --hash=sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5 \ --hash=sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6 @@ -359,12 +347,10 @@ h11==0.14.0 \ # via # httpcore # uvicorn -hexkit[akafka,mongodb]==2.1.1 \ +hexkit==2.1.1 \ --hash=sha256:1f0a0e20a6d56fe4fa5e0b1c798df4720d2f84e20cbe7f16464bd5107e109c90 \ --hash=sha256:3ec0f9690eb573125e22bce0c662019c9708cd17f8d2353396dc4496577718c3 - # via - # ars (pyproject.toml) - # ghga-service-commons + # via ghga-service-commons httpcore==1.0.4 \ --hash=sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73 \ --hash=sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022 @@ -410,10 +396,7 @@ httptools==0.6.1 \ httpx==0.27.0 \ --hash=sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5 \ --hash=sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5 - # via - # -r /workspace/lock/requirements-dev-template.in - # ars (pyproject.toml) - # pytest-httpx + # via pytest-httpx identify==2.5.35 \ --hash=sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791 \ --hash=sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e @@ -426,10 +409,6 @@ idna==3.6 \ # email-validator # httpx # requests -importlib-metadata==7.0.2 \ - --hash=sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792 \ - --hash=sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100 - # via build iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 @@ -447,7 +426,6 @@ jsonschema-specifications==2023.12.1 \ jsonschema2md==1.1.0 \ --hash=sha256:2386fc4d119330686db3989ea497ab96a4defb6388386fc0ceff756b5c1a66a7 \ --hash=sha256:e89edf2de1bc7fc3e842915c7c29b7b70888555a87002eccc06350c0412a1458 - # via -r /workspace/lock/requirements-dev-template.in jwcrypto==1.5.6 \ --hash=sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789 \ --hash=sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039 @@ -461,7 +439,6 @@ kafka-python==2.0.2 \ logot==1.2.0 \ --hash=sha256:e4972cc1569322ed6d1e25e8c4507c3d9ebb4fdd93058c420f800bf223bc5f90 \ --hash=sha256:ed994e50e30fed2378965a859def1eff1c62d97255479196de33f6de7cec2866 - # via -r /workspace/lock/requirements-dev-template.in motor==3.3.2 \ --hash=sha256:6fe7e6f0c4f430b9e030b9d22549b732f7c2226af3ab71ecc309e4a1b7d19953 \ --hash=sha256:d2fc38de15f1c8058f389c1a44a4d4105c0405c48c061cd492a654496f7bc26a @@ -494,13 +471,10 @@ mypy==1.9.0 \ --hash=sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4 \ --hash=sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f \ --hash=sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6 - # via -r /workspace/lock/requirements-dev-template.in mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 - # via - # -r /workspace/lock/requirements-dev-template.in - # mypy + # via mypy nodeenv==1.8.0 \ --hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \ --hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec @@ -510,13 +484,8 @@ packaging==24.0 \ --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 # via # aiokafka - # build # docker # pytest -pip-tools==7.4.1 \ - --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ - --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 - # via -r /workspace/lock/requirements-dev-template.in platformdirs==4.2.0 \ --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 @@ -528,14 +497,13 @@ pluggy==1.4.0 \ pre-commit==3.6.2 \ --hash=sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c \ --hash=sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e - # via -r /workspace/lock/requirements-dev-template.in pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pydantic[email]==2.6.3 \ - --hash=sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a \ - --hash=sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f +pydantic==2.6.4 \ + --hash=sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6 \ + --hash=sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5 # via # fastapi # ghga-event-schemas @@ -711,37 +679,26 @@ pymongo==4.6.2 \ --hash=sha256:fbafe3a1df21eeadb003c38fc02c1abf567648b6477ec50c4a3c042dca205371 \ --hash=sha256:fe010154dfa9e428bd2fb3e9325eff2216ab20a69ccbd6b5cac6785ca2989161 # via motor -pyproject-hooks==1.0.0 \ - --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \ - --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5 - # via - # build - # pip-tools pytest==8.1.1 \ --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 # via - # -r /workspace/lock/requirements-dev-template.in # pytest-asyncio # pytest-cov # pytest-httpx # pytest-profiling -pytest-asyncio==0.23.5.post1 \ - --hash=sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e \ - --hash=sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813 - # via -r /workspace/lock/requirements-dev-template.in +pytest-asyncio==0.23.6 \ + --hash=sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a \ + --hash=sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f pytest-cov==4.1.0 \ --hash=sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6 \ --hash=sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a - # via -r /workspace/lock/requirements-dev-template.in pytest-httpx==0.30.0 \ --hash=sha256:6d47849691faf11d2532565d0c8e0e02b9f4ee730da31687feae315581d7520c \ --hash=sha256:755b8edca87c974dd4f3605c374fda11db84631de3d163b99c0df5807023a19a - # via -r /workspace/lock/requirements-dev-template.in pytest-profiling==1.7.0 \ --hash=sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29 \ --hash=sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019 - # via -r /workspace/lock/requirements-dev-template.in python-dotenv==1.0.1 \ --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a @@ -805,18 +762,16 @@ pyyaml==6.0.1 \ # jsonschema2md # pre-commit # uvicorn -referencing==0.33.0 \ - --hash=sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5 \ - --hash=sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7 +referencing==0.34.0 \ + --hash=sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844 \ + --hash=sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4 # via # jsonschema # jsonschema-specifications requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 - # via - # -r /workspace/lock/requirements-dev-template.in - # docker + # via docker rpds-py==0.18.0 \ --hash=sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f \ --hash=sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c \ @@ -920,25 +875,28 @@ rpds-py==0.18.0 \ # via # jsonschema # referencing -ruff==0.3.2 \ - --hash=sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4 \ - --hash=sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037 \ - --hash=sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9 \ - --hash=sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b \ - --hash=sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b \ - --hash=sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01 \ - --hash=sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302 \ - --hash=sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d \ - --hash=sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a \ - --hash=sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d \ - --hash=sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da \ - --hash=sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a \ - --hash=sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa \ - --hash=sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7 \ - --hash=sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36 \ - --hash=sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745 \ - --hash=sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142 - # via -r /workspace/lock/requirements-dev-template.in +ruff==0.3.3 \ + --hash=sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61 \ + --hash=sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8 \ + --hash=sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5 \ + --hash=sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8 \ + --hash=sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab \ + --hash=sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d \ + --hash=sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376 \ + --hash=sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778 \ + --hash=sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f \ + --hash=sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8 \ + --hash=sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d \ + --hash=sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386 \ + --hash=sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc \ + --hash=sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493 \ + --hash=sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d \ + --hash=sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0 \ + --hash=sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e +setuptools==69.2.0 \ + --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ + --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c + # via nodeenv six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -946,7 +904,6 @@ six==1.16.0 \ snakeviz==2.2.0 \ --hash=sha256:569e2d71c47f80a886aa6e70d6405cb6d30aa3520969ad956b06f824c5f02b8e \ --hash=sha256:7bfd00be7ae147eb4a170a471578e1cd3f41f803238958b6b8efcf2c698a6aa9 - # via -r /workspace/lock/requirements-dev-template.in sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc @@ -959,26 +916,19 @@ starlette==0.36.3 \ # via fastapi stringcase==1.2.0 \ --hash=sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008 - # via -r /workspace/lock/requirements-dev-template.in -testcontainers[kafka,mongo]==4.0.1 \ +testcontainers==4.0.1 \ --hash=sha256:0359c1391124d594caeb96f0adddbf16fd07aeec8cea5bbc00f9c44a140e3b25 \ --hash=sha256:2c91b1fd8fc9901a08054206f1df108cb07685fc30232e6332ee12f292a17ea1 - # via -r /workspace/lock/requirements-dev.in tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via - # -r /workspace/lock/requirements-dev-template.in - # build # coverage # mypy - # pip-tools - # pyproject-hooks # pytest tomli-w==1.0.0 \ --hash=sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463 \ --hash=sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9 - # via -r /workspace/lock/requirements-dev-template.in tornado==6.4 \ --hash=sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0 \ --hash=sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63 \ @@ -995,9 +945,6 @@ tornado==6.4 \ typer==0.9.0 \ --hash=sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2 \ --hash=sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee - # via - # -r /workspace/lock/requirements-dev-template.in - # ars (pyproject.toml) typing-extensions==4.10.0 \ --hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \ --hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb @@ -1016,11 +963,28 @@ urllib3==2.2.1 \ --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \ --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19 # via - # -r /workspace/lock/requirements-dev-template.in # docker # requests # testcontainers -uvicorn[standard]==0.27.1 \ +uv==0.1.22 \ + --hash=sha256:144ffabe5f2519bf371bfd3db074b3d0952db31263fbd9b5b9a18efbc90ee772 \ + --hash=sha256:23ce9a081e046d40ea9582824f1ec1a64830e7292f5885b42526baa0d9d834e1 \ + --hash=sha256:275892300f842170eb03e15ee077d9f2327966d3e12048568230a13cd8de7ba5 \ + --hash=sha256:28ea7dd1e9048610a13b47976c3f45ab1cc99e96f085c9e6659f024b46dad870 \ + --hash=sha256:4936331140392c057e56e29751bcf0461ba5ec92592bf2082f7a85e8170c4051 \ + --hash=sha256:4d23fa6693d5026b45df75b996a3f43166d10caa690925f9ee55e2d5ff61cfa9 \ + --hash=sha256:6ae068131fd255bf55f48c077e54558184a1a23fcefbb78de3189853d121cf29 \ + --hash=sha256:94f45b92d48489867c2b07a5ec2b1ead6994d46573f9066d3473e08a04f662e4 \ + --hash=sha256:9b0d5d062484dcf97e191d4a28de23974ba6a384315300f9a439268506460b7d \ + --hash=sha256:9c238d417a67aab57c2d407778c86078f92e12d127bc4076f5565a9203f3e4f5 \ + --hash=sha256:b86f7c1ccbffa34cf82d2b5ff6b8650d3a66f4b9496b8fd07b18420ae232ebd7 \ + --hash=sha256:baf5c2c0a1097dc29da6ab610c90b798d5e5e97aaace2e66cb474a8e2c1e0361 \ + --hash=sha256:dee413bac546c359a37065e9b8ef1d48210517763000e569ed577745da11c321 \ + --hash=sha256:e83c858770ad2eced9750bd6b7aed320d74e4a35a9408895de506bbc7b11343c \ + --hash=sha256:f350292267f361c0aeb8c68f81e81d18e7eb383d8323dd10017ee93747471deb \ + --hash=sha256:fa0f0eb1306082c65f042c4c54d6a7c1078b9b3ad65da4af7df11fba91848e21 \ + --hash=sha256:fefb107a2fb4eac874cdf1066f8cc40c5112853890fa49520e13975a6ad2778a +uvicorn==0.27.1 \ --hash=sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a \ --hash=sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4 # via ghga-service-commons @@ -1212,10 +1176,6 @@ websockets==12.0 \ --hash=sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8 \ --hash=sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7 # via uvicorn -wheel==0.43.0 \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 - # via pip-tools wrapt==1.16.0 \ --hash=sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc \ --hash=sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81 \ @@ -1288,13 +1248,3 @@ wrapt==1.16.0 \ --hash=sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a \ --hash=sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4 # via testcontainers -zipp==3.17.0 \ - --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ - --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 - # via importlib-metadata - -# WARNING: The following packages were not pinned, but pip requires them to be -# pinned when the requirements file includes hashes and the requirement is not -# satisfied by a package already installed. Consider using the --allow-unsafe flag. -# pip -# setuptools diff --git a/lock/requirements.txt b/lock/requirements.txt index abb43e8..52896b9 100644 --- a/lock/requirements.txt +++ b/lock/requirements.txt @@ -1,9 +1,5 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --constraint=/workspace/lock/requirements-dev.txt --generate-hashes --output-file=/workspace/lock/requirements.txt /tmp/tmp6r0ap6iq/pyproject.toml -# +# This file was autogenerated by uv via the following command: +# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements.txt /tmp/tmp8rmzyh_v/pyproject.toml -c /workspace/lock/requirements-dev.txt aiokafka==0.8.1 \ --hash=sha256:1e24839088fd6d3ff481cc09a48ea487b997328df11630bc0a1b88255edbcfe9 \ --hash=sha256:1f43d2afd7d3e4407ada8d754895fad7c344ca00648a8a38418d76564eaaf6cd \ @@ -30,41 +26,32 @@ aiokafka==0.8.1 \ --hash=sha256:d300188e358cd29989c817f6ee2a2965a039e5a71de8ade6f80f02ebb9bd07b8 \ --hash=sha256:fd8f9e17bc9cd2ea664a7f5133aede39a8fffebffe0c450252d475dbdedb4a35 \ --hash=sha256:ff318d29ecbeea8c58d69c91c24d48d7ed4a8d3e829b607e670d118a9a35d5ba - # via - # -c /workspace/lock/requirements-dev.txt - # hexkit + # via hexkit annotated-types==0.6.0 \ --hash=sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43 \ --hash=sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d - # via - # -c /workspace/lock/requirements-dev.txt - # pydantic + # via pydantic anyio==4.3.0 \ --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \ --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 # via - # -c /workspace/lock/requirements-dev.txt # httpx # starlette # watchfiles async-timeout==4.0.3 \ --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \ --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028 - # via - # -c /workspace/lock/requirements-dev.txt - # aiokafka + # via aiokafka attrs==23.2.0 \ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via - # -c /workspace/lock/requirements-dev.txt # jsonschema # referencing certifi==2024.2.2 \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 # via - # -c /workspace/lock/requirements-dev.txt # httpcore # httpx cffi==1.16.0 \ @@ -120,14 +107,11 @@ cffi==1.16.0 \ --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 - # via - # -c /workspace/lock/requirements-dev.txt - # cryptography + # via cryptography click==8.1.7 \ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via - # -c /workspace/lock/requirements-dev.txt # typer # uvicorn cryptography==42.0.5 \ @@ -163,66 +147,45 @@ cryptography==42.0.5 \ --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 - # via - # -c /workspace/lock/requirements-dev.txt - # jwcrypto + # via jwcrypto dnspython==2.6.1 \ --hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \ --hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc # via - # -c /workspace/lock/requirements-dev.txt # email-validator # pymongo email-validator==2.1.1 \ --hash=sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84 \ --hash=sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05 - # via - # -c /workspace/lock/requirements-dev.txt - # pydantic + # via pydantic exceptiongroup==1.2.0 \ --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \ --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68 - # via - # -c /workspace/lock/requirements-dev.txt - # anyio + # via anyio fastapi==0.109.2 \ --hash=sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d \ --hash=sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73 - # via - # -c /workspace/lock/requirements-dev.txt - # ghga-service-commons -ghga-event-schemas==3.0.0 \ - --hash=sha256:67dce9db2d45be862f69a58a903fac43416997ad50fd4f1f1d25822533a187d1 \ - --hash=sha256:7a8952e37bd935809f324aa21653b008e01a5ea920d36217734ee35776d92602 - # via - # -c /workspace/lock/requirements-dev.txt - # ars (pyproject.toml) -ghga-service-commons[api,auth,objectstorage]==2.0.1 \ + # via ghga-service-commons +ghga-event-schemas==3.1.0 \ + --hash=sha256:87706784895376314124d30a0ba77dd7cfebdbfbcbb98e88d2a836486f11c385 \ + --hash=sha256:fa0048eda36002e7a79bc9084d2acdcc9eb9d38bcf263d6f68ad6fc453cae130 +ghga-service-commons==2.0.1 \ --hash=sha256:957c44d8ad006da525c506d815210a701af2dc4ebf0e6473800c00f926f77ce8 \ --hash=sha256:9e7ea822ec692fdc6df93ea62ed65e8dd2bf48886bf1441c96697c0be2101c15 - # via - # -c /workspace/lock/requirements-dev.txt - # ars (pyproject.toml) h11==0.14.0 \ --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 # via - # -c /workspace/lock/requirements-dev.txt # httpcore # uvicorn -hexkit[akafka,mongodb]==2.1.1 \ +hexkit==2.1.1 \ --hash=sha256:1f0a0e20a6d56fe4fa5e0b1c798df4720d2f84e20cbe7f16464bd5107e109c90 \ --hash=sha256:3ec0f9690eb573125e22bce0c662019c9708cd17f8d2353396dc4496577718c3 - # via - # -c /workspace/lock/requirements-dev.txt - # ars (pyproject.toml) - # ghga-service-commons + # via ghga-service-commons httpcore==1.0.4 \ --hash=sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73 \ --hash=sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022 - # via - # -c /workspace/lock/requirements-dev.txt - # httpx + # via httpx httptools==0.6.1 \ --hash=sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563 \ --hash=sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142 \ @@ -260,20 +223,14 @@ httptools==0.6.1 \ --hash=sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81 \ --hash=sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185 \ --hash=sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3 - # via - # -c /workspace/lock/requirements-dev.txt - # uvicorn + # via uvicorn httpx==0.27.0 \ --hash=sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5 \ --hash=sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5 - # via - # -c /workspace/lock/requirements-dev.txt - # ars (pyproject.toml) idna==3.6 \ --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f # via - # -c /workspace/lock/requirements-dev.txt # anyio # email-validator # httpx @@ -281,50 +238,36 @@ jsonschema==4.21.1 \ --hash=sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f \ --hash=sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5 # via - # -c /workspace/lock/requirements-dev.txt # ghga-event-schemas # hexkit jsonschema-specifications==2023.12.1 \ --hash=sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc \ --hash=sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c - # via - # -c /workspace/lock/requirements-dev.txt - # jsonschema + # via jsonschema jwcrypto==1.5.6 \ --hash=sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789 \ --hash=sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039 - # via - # -c /workspace/lock/requirements-dev.txt - # ghga-service-commons + # via ghga-service-commons kafka-python==2.0.2 \ --hash=sha256:04dfe7fea2b63726cd6f3e79a2d86e709d608d74406638c5da33a01d45a9d7e3 \ --hash=sha256:2d92418c7cb1c298fa6c7f0fb3519b520d0d7526ac6cb7ae2a4fc65a51a94b6e - # via - # -c /workspace/lock/requirements-dev.txt - # aiokafka + # via aiokafka motor==3.3.2 \ --hash=sha256:6fe7e6f0c4f430b9e030b9d22549b732f7c2226af3ab71ecc309e4a1b7d19953 \ --hash=sha256:d2fc38de15f1c8058f389c1a44a4d4105c0405c48c061cd492a654496f7bc26a - # via - # -c /workspace/lock/requirements-dev.txt - # hexkit + # via hexkit packaging==24.0 \ --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 - # via - # -c /workspace/lock/requirements-dev.txt - # aiokafka + # via aiokafka pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +pydantic==2.6.4 \ + --hash=sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6 \ + --hash=sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5 # via - # -c /workspace/lock/requirements-dev.txt - # cffi -pydantic[email]==2.6.3 \ - --hash=sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a \ - --hash=sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f - # via - # -c /workspace/lock/requirements-dev.txt # fastapi # ghga-event-schemas # ghga-service-commons @@ -410,15 +353,11 @@ pydantic-core==2.16.3 \ --hash=sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120 \ --hash=sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f \ --hash=sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a - # via - # -c /workspace/lock/requirements-dev.txt - # pydantic + # via pydantic pydantic-settings==2.2.1 \ --hash=sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed \ --hash=sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091 - # via - # -c /workspace/lock/requirements-dev.txt - # hexkit + # via hexkit pymongo==4.6.2 \ --hash=sha256:097791d5a8d44e2444e0c8c4d6e14570ac11e22bcb833808885a5db081c3dc2a \ --hash=sha256:0d002ae456a15b1d790a78bb84f87af21af1cb716a63efb2c446ab6bcbbc48ca \ @@ -502,14 +441,11 @@ pymongo==4.6.2 \ --hash=sha256:fb24abcd50501b25d33a074c1790a1389b6460d2509e4b240d03fd2e5c79f463 \ --hash=sha256:fbafe3a1df21eeadb003c38fc02c1abf567648b6477ec50c4a3c042dca205371 \ --hash=sha256:fe010154dfa9e428bd2fb3e9325eff2216ab20a69ccbd6b5cac6785ca2989161 - # via - # -c /workspace/lock/requirements-dev.txt - # motor + # via motor python-dotenv==1.0.1 \ --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a # via - # -c /workspace/lock/requirements-dev.txt # pydantic-settings # uvicorn pyyaml==6.0.1 \ @@ -565,14 +501,12 @@ pyyaml==6.0.1 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f # via - # -c /workspace/lock/requirements-dev.txt # hexkit # uvicorn -referencing==0.33.0 \ - --hash=sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5 \ - --hash=sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7 +referencing==0.34.0 \ + --hash=sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844 \ + --hash=sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4 # via - # -c /workspace/lock/requirements-dev.txt # jsonschema # jsonschema-specifications rpds-py==0.18.0 \ @@ -676,33 +610,25 @@ rpds-py==0.18.0 \ --hash=sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58 \ --hash=sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e # via - # -c /workspace/lock/requirements-dev.txt # jsonschema # referencing sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc # via - # -c /workspace/lock/requirements-dev.txt # anyio # httpx starlette==0.36.3 \ --hash=sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044 \ --hash=sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080 - # via - # -c /workspace/lock/requirements-dev.txt - # fastapi + # via fastapi typer==0.9.0 \ --hash=sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2 \ --hash=sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee - # via - # -c /workspace/lock/requirements-dev.txt - # ars (pyproject.toml) typing-extensions==4.10.0 \ --hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \ --hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb # via - # -c /workspace/lock/requirements-dev.txt # anyio # fastapi # jwcrypto @@ -711,12 +637,10 @@ typing-extensions==4.10.0 \ # starlette # typer # uvicorn -uvicorn[standard]==0.27.1 \ +uvicorn==0.27.1 \ --hash=sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a \ --hash=sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4 - # via - # -c /workspace/lock/requirements-dev.txt - # ghga-service-commons + # via ghga-service-commons uvloop==0.19.0 \ --hash=sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd \ --hash=sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec \ @@ -749,9 +673,7 @@ uvloop==0.19.0 \ --hash=sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e \ --hash=sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7 \ --hash=sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256 - # via - # -c /workspace/lock/requirements-dev.txt - # uvicorn + # via uvicorn watchfiles==0.21.0 \ --hash=sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc \ --hash=sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365 \ @@ -828,9 +750,7 @@ watchfiles==0.21.0 \ --hash=sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165 \ --hash=sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303 \ --hash=sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d - # via - # -c /workspace/lock/requirements-dev.txt - # uvicorn + # via uvicorn websockets==12.0 \ --hash=sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b \ --hash=sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6 \ @@ -904,6 +824,4 @@ websockets==12.0 \ --hash=sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5 \ --hash=sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8 \ --hash=sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7 - # via - # -c /workspace/lock/requirements-dev.txt - # uvicorn + # via uvicorn diff --git a/openapi.yaml b/openapi.yaml index 6d0a617..9df9b19 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -135,7 +135,7 @@ components: info: description: A service managing access requests for the GHGA Data Portal title: Access Request Service - version: 1.1.1 + version: 2.0.0 openapi: 3.1.0 paths: /access-requests: diff --git a/pyproject.toml b/pyproject.toml index afe57d9..a5e9177 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,10 @@ classifiers = [ "Intended Audience :: Developers", ] name = "ars" -version = "1.1.1" +version = "2.0.0" description = "Access Request Service" dependencies = [ - "ghga-event-schemas~=3.0.0", + "ghga-event-schemas~=3.1.0", "ghga-service-commons[api,auth]>=2.0.0, <3", "hexkit[mongodb,akafka]>=2.1.0", "httpx>=0.27.0", diff --git a/scripts/update_lock.py b/scripts/update_lock.py index 98516b8..5def807 100755 --- a/scripts/update_lock.py +++ b/scripts/update_lock.py @@ -23,6 +23,7 @@ import os import re import subprocess +from itertools import zip_longest from pathlib import Path from tempfile import TemporaryDirectory @@ -63,22 +64,25 @@ def fix_temp_dir_comments(file_path: Path): def is_file_outdated(old_file: Path, new_file: Path) -> bool: """Compares two lock files and returns True if there is a difference, else False""" - header_comment = "# pip-compile" outdated = False with open(old_file, encoding="utf-8") as old: with open(new_file, encoding="utf-8") as new: - old_lines = old.readlines() - new_lines = new.readlines() - if len(old_lines) != len(new_lines): - outdated = True - if not outdated: - for old_line, new_line in zip(old_lines, new_lines): - if old_line.startswith(header_comment): - continue - if old_line != new_line: - outdated = True - break + outdated = any( + old_line != new_line + for old_line, new_line in zip_longest( + ( + line + for line in (line.strip() for line in old) + if line and not line.startswith("#") + ), + ( + line + for line in (line.strip() for line in new) + if line and not line.startswith("#") + ), + ) + ) if outdated: cli.echo_failure(f"{str(old_file)} is out of date!") return outdated @@ -97,10 +101,11 @@ def compile_lock_file( print(f"Updating '{output.name}'...") command = [ - "pip-compile", - "--rebuild", + "uv", + "pip", + "compile", + "--refresh", "--generate-hashes", - "--annotate", ] if upgrade: diff --git a/src/ars/adapters/outbound/event_pub.py b/src/ars/adapters/outbound/event_pub.py index a0bc2e2..2f5c970 100644 --- a/src/ars/adapters/outbound/event_pub.py +++ b/src/ars/adapters/outbound/event_pub.py @@ -22,54 +22,82 @@ from pydantic import Field from pydantic_settings import BaseSettings -from ars.ports.outbound.notification_emitter import NotificationEmitterPort +from ars.core import models +from ars.ports.outbound.event_pub import EventPublisherPort -__all__ = ["NotificationEmitterConfig", "NotificationEmitter"] +__all__ = ["EventPubTranslatorConfig", "EventPubTranslator"] -class NotificationEmitterConfig(BaseSettings): - """Config for sending notification events.""" +class EventPubTranslatorConfig(BaseSettings): + """Config for the event pub translator""" - notification_event_topic: str = Field( - ..., - description=("Name of the topic used for notification events."), - examples=["notifications"], + access_request_events_topic: str = Field( + default=..., + description="The topic used for events related to access requests.", + examples=["access_requests"], ) - notification_event_type: str = Field( - ..., - description=("The type used for notification events."), - examples=["notification"], + access_request_created_event_type: str = Field( + default=..., + description="The type to use for 'access request created' events", + examples=["access_request_created"], + ) + access_request_allowed_event_type: str = Field( + default=..., + description="The type to use for 'access request allowed' events", + examples=["access_request_allowed"], + ) + access_request_denied_event_type: str = Field( + default=..., + description="The type to use for 'access request denied' events", + examples=["access_request_denied"], ) -class NotificationEmitter(NotificationEmitterPort): - """Translator from NotificationEmitterPort to EventPublisherProtocol.""" +class EventPubTranslator(EventPublisherPort): + """Translator from EventPublisherPort to EventPublisherProtocol.""" def __init__( self, *, - config: NotificationEmitterConfig, + config: EventPubTranslatorConfig, event_publisher: EventPublisherProtocol, ): """Initialize with config and a provider of the EventPublisherProtocol.""" - self._event_topic = config.notification_event_topic - self._event_type = config.notification_event_type + self._config = config self._event_publisher = event_publisher - async def notify( - self, *, email: str, full_name: str, subject: str, text: str + async def _publish_access_request_event( + self, *, request: models.AccessRequest, type_: str ) -> None: - """Send notification to the specified email address.""" - payload: JsonObject = event_schemas.Notification( - recipient_email=email, - recipient_name=full_name, - subject=subject, - plaintext_body=text, + """Publish an access request-related event with the given details and type.""" + payload: JsonObject = event_schemas.AccessRequestDetails( + user_id=request.user_id, dataset_id=request.dataset_id ).model_dump() await self._event_publisher.publish( payload=payload, - type_=self._event_type, - key=email, - topic=self._event_topic, + type_=type_, + key=request.user_id, + topic=self._config.access_request_events_topic, + ) + + async def publish_request_allowed(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was allowed.""" + await self._publish_access_request_event( + request=request, + type_=self._config.access_request_allowed_event_type, + ) + + async def publish_request_created(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was created.""" + await self._publish_access_request_event( + request=request, + type_=self._config.access_request_created_event_type, + ) + + async def publish_request_denied(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was denied.""" + await self._publish_access_request_event( + request=request, + type_=self._config.access_request_denied_event_type, ) diff --git a/src/ars/config.py b/src/ars/config.py index 106eab3..aa187f2 100644 --- a/src/ars/config.py +++ b/src/ars/config.py @@ -23,7 +23,7 @@ from hexkit.providers.mongodb import MongoDbConfig from pydantic import Field -from ars.adapters.outbound.event_pub import NotificationEmitterConfig +from ars.adapters.outbound.event_pub import EventPubTranslatorConfig from ars.adapters.outbound.http import AccessGrantsConfig from ars.core.repository import AccessRequestConfig @@ -37,7 +37,7 @@ class Config( LoggingConfig, MongoDbConfig, KafkaConfig, - NotificationEmitterConfig, + EventPubTranslatorConfig, AccessGrantsConfig, AccessRequestConfig, ): diff --git a/src/ars/core/notifications.py b/src/ars/core/notifications.py deleted file mode 100644 index 0d46c81..0000000 --- a/src/ars/core/notifications.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2021 - 2023 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln -# for the German Human Genome-Phenome Archive (GHGA) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""The content of all notification and confirmation emails.""" - -from typing import NamedTuple - -__all__ = [ - "Notification", - "REQUEST_CREATED_NOTIFICATION", - "REQUEST_CREATED_CONFIRMATION", - "REQUEST_ALLOWED_NOTIFICATION", - "REQUEST_ALLOWED_CONFIRMATION", - "REQUEST_DENIED_NOTIFICATION", - "REQUEST_DENIED_CONFIRMATION", -] - - -class Notification(NamedTuple): - """A notification with a subject and a body text.""" - - subject: str - text: str - - -# The subject and text for the various notification emails. -# The attributes of the request can be interpolated. - - -REQUEST_CREATED_NOTIFICATION = Notification( - "Your data download access request has been registered", - """ -Your request to download the dataset {dataset_id} has been registered. - -You should be contacted by one of our data stewards in the next three workdays. -""", -) - -REQUEST_CREATED_CONFIRMATION = Notification( - "A data download access request has been created", - """ -{full_user_name} requested to download the dataset {dataset_id}. - -The specified contact email address is: {email} -""", -) - -REQUEST_ALLOWED_NOTIFICATION = Notification( - "Your data download access request has been accepted", - """ -We are glad to inform you that your request to download the dataset -{dataset_id} has been accepted. - -You can now start download the dataset as explained in the GHGA Data Portal. -""", -) - -REQUEST_ALLOWED_CONFIRMATION = Notification( - "Data download access has been allowed", - """ -The request by {full_user_name} to download the dataset -{dataset_id} has now been registered as allowed -and the access has been granted. -""", -) - -REQUEST_DENIED_NOTIFICATION = Notification( - "Your data download access request has been rejected", - """ -Unfortunately, your request to download the dataset -{dataset_id} has been rejected. - -Please contact our help desk for information about this decision. -""", -) - -REQUEST_DENIED_CONFIRMATION = Notification( - "Data download access has been rejected", - """ -The request by {full_user_name} to download the dataset -{dataset_id} has now been registered as rejected -and the access has not been granted. -""", -) diff --git a/src/ars/core/repository.py b/src/ars/core/repository.py index 52fe248..b267ae1 100644 --- a/src/ars/core/repository.py +++ b/src/ars/core/repository.py @@ -22,7 +22,7 @@ from ghga_service_commons.auth.ghga import AuthContext, has_role from ghga_service_commons.utils.utc_dates import now_as_utc -from pydantic import EmailStr, Field +from pydantic import Field from pydantic_settings import BaseSettings from ars.core.models import ( @@ -31,20 +31,11 @@ AccessRequestData, AccessRequestStatus, ) -from ars.core.notifications import ( - REQUEST_ALLOWED_CONFIRMATION, - REQUEST_ALLOWED_NOTIFICATION, - REQUEST_CREATED_CONFIRMATION, - REQUEST_CREATED_NOTIFICATION, - REQUEST_DENIED_CONFIRMATION, - REQUEST_DENIED_NOTIFICATION, - Notification, -) from ars.core.roles import DATA_STEWARD_ROLE from ars.ports.inbound.repository import AccessRequestRepositoryPort from ars.ports.outbound.access_grants import AccessGrantsPort from ars.ports.outbound.dao import AccessRequestDaoPort, ResourceNotFoundError -from ars.ports.outbound.notification_emitter import NotificationEmitterPort +from ars.ports.outbound.event_pub import EventPublisherPort __all__ = ["AccessRequestConfig", "AccessRequestRepository"] @@ -52,10 +43,6 @@ class AccessRequestConfig(BaseSettings): """Config parameters needed for the AccessRequestRepository.""" - data_steward_email: Optional[EmailStr] = Field( - ..., description="An email address that can be used to notify data stewards" - ) - access_upfront_max_days: int = Field( default=6 * 30, description="The maximum lead time in days to request access grants", @@ -78,16 +65,15 @@ def __init__( *, config: AccessRequestConfig, access_request_dao: AccessRequestDaoPort, - notification_emitter: NotificationEmitterPort, + event_publisher: EventPublisherPort, access_grants: AccessGrantsPort, ): """Initialize with specific configuration and outbound adapter.""" self._max_lead_time = timedelta(days=config.access_upfront_max_days) self._min_duration = timedelta(days=config.access_grant_min_days) self._max_duration = timedelta(days=config.access_grant_max_days) - self._data_steward_email = config.data_steward_email self._dao = access_request_dao - self._notification_emitter = notification_emitter + self._event_publisher = event_publisher self._access_grants = access_grants async def create( @@ -99,8 +85,9 @@ async def create( Users may only create access requests for themselves. - Raises an AccessRequestAuthorizationError if the user is not authorized. - Raises an AccessRequestInvalidDuration error if the dates are invalid. + Raises: + - `AccessRequestAuthorizationError` if the user is not authorized. + - `AccessRequestInvalidDuration` error if the dates are invalid. """ user_id = auth_context.id if not user_id or creation_data.user_id != user_id: @@ -133,22 +120,7 @@ async def create( request = await self._dao.insert(access_request_data) - # notify data steward - data_steward_email = self._data_steward_email - if data_steward_email: - await self._notify( - recipient_email=data_steward_email, - recipient_name="Data Steward", - request=request, - notification=REQUEST_CREATED_CONFIRMATION, - ) - # notify requester - await self._notify( - recipient_email=access_request_data.email, - recipient_name=full_user_name, - request=request, - notification=REQUEST_CREATED_NOTIFICATION, - ) + await self._event_publisher.publish_request_created(request=request) return request @@ -164,7 +136,7 @@ async def get( Only data stewards may list requests created by other users. - Raises an AccessRequestAuthorizationError if the user is not authorized. + Raises an `AccessRequestAuthorizationError` if the user is not authorized. """ if not auth_context.id: raise self.AccessRequestAuthorizationError("Not authorized") @@ -204,10 +176,11 @@ async def update( Only data stewards may use this method. - Raises an AccessRequestAuthorizationError if the user is not authorized. - raises an AccessRequestNotFoundError if the specified request was not found. - raises an AccessRequestInvalidState error is the specified state is invalid. - Raises an AccessRequestServerError if the grant could not be registered. + Raises: + - `AccessRequestAuthorizationError` if the user is not authorized. + - `AccessRequestNotFoundError` if the specified request was not found. + - `AccessRequestInvalidState` error if the specified state is invalid. + - `AccessRequestServerError` if the grant could not be registered. """ user_id = auth_context.id if not user_id or not has_role(auth_context, DATA_STEWARD_ROLE): @@ -248,43 +221,13 @@ async def update( f"Could not register the download access grant: {error}" ) from error - # notify data steward - data_steward_email = self._data_steward_email - if data_steward_email: - await self._notify( - recipient_email=data_steward_email, - recipient_name="Data Steward", - request=request, - notification=REQUEST_DENIED_CONFIRMATION - if status == AccessRequestStatus.DENIED - else REQUEST_ALLOWED_CONFIRMATION, - ) - # notify requester - await self._notify( - recipient_email=request.email, - recipient_name=request.full_user_name, - request=request, - notification=REQUEST_DENIED_NOTIFICATION - if status == AccessRequestStatus.DENIED - else REQUEST_ALLOWED_NOTIFICATION, - ) + # Emit events that communicate the fate of the access request + if status == AccessRequestStatus.DENIED: + await self._event_publisher.publish_request_denied(request=request) + elif status == AccessRequestStatus.ALLOWED: + await self._event_publisher.publish_request_allowed(request=request) @staticmethod def _hide_internals(request: AccessRequest) -> AccessRequest: """Blank out internal information in the request""" return request.model_copy(update={"changed_by": None}) - - async def _notify( - self, - recipient_email: str, - recipient_name: str, - request: AccessRequest, - notification: Notification, - ) -> None: - """Send the given notification to the specified recipient.""" - await self._notification_emitter.notify( - email=recipient_email, - full_name=recipient_name, - subject=notification.subject, - text=notification.text.format(**request.model_dump()).strip(), - ) diff --git a/src/ars/inject.py b/src/ars/inject.py index a2ed763..1dd2bbc 100644 --- a/src/ars/inject.py +++ b/src/ars/inject.py @@ -29,7 +29,7 @@ from ars.adapters.inbound.fastapi_ import dummies from ars.adapters.inbound.fastapi_.configure import get_configured_app from ars.adapters.outbound.dao import AccessRequestDaoConstructor -from ars.adapters.outbound.event_pub import NotificationEmitter +from ars.adapters.outbound.event_pub import EventPubTranslator from ars.adapters.outbound.http import AccessGrantsAdapter from ars.config import Config from ars.core.repository import AccessRequestRepository @@ -50,12 +50,12 @@ async def prepare_core( KafkaEventPublisher.construct(config=config) as event_publisher, AccessGrantsAdapter.construct(config=config) as access_grants, ): - notification_emitter = NotificationEmitter( + event_publisher = EventPubTranslator( config=config, event_publisher=event_publisher ) yield AccessRequestRepository( access_request_dao=access_request_dao, - notification_emitter=notification_emitter, + event_publisher=event_publisher, access_grants=access_grants, config=config, ) diff --git a/src/ars/ports/inbound/repository.py b/src/ars/ports/inbound/repository.py index 418c092..21c31d8 100644 --- a/src/ars/ports/inbound/repository.py +++ b/src/ars/ports/inbound/repository.py @@ -59,7 +59,9 @@ async def create( Users may only create access requests for themselves. - Raises an AccessRequestAuthorizationError if the user is not authorized. + Raises: + - `AccessRequestAuthorizationError` if the user is not authorized. + - `AccessRequestInvalidDuration` error if the dates are invalid. """ ... @@ -76,8 +78,7 @@ async def get( Only data stewards may list requests created by other users. - Raises an AccessRequestAuthorizationError if the user is not authorized. - Raises an AccessRequestInvalidDuration error if the dates are invalid. + Raises an `AccessRequestAuthorizationError` if the user is not authorized. """ ... @@ -93,9 +94,10 @@ async def update( Only data stewards may use this method. - Raises an AccessRequestAuthorizationError if the user is not authorized. - raises an AccessRequestNotFoundError if the specified request was not found. - raises an AccessRequestInvalidState error is the specified state is invalid. - Raises an AccessRequestServerError if the grant could not be registered. + Raises: + - `AccessRequestAuthorizationError` if the user is not authorized. + - `AccessRequestNotFoundError` if the specified request was not found. + - `AccessRequestInvalidState` error if the specified state is invalid. + - `AccessRequestServerError` if the grant could not be registered. """ ... diff --git a/src/ars/ports/outbound/notification_emitter.py b/src/ars/ports/outbound/event_pub.py similarity index 50% rename from src/ars/ports/outbound/notification_emitter.py rename to src/ars/ports/outbound/event_pub.py index 9e8601a..8e15d45 100644 --- a/src/ars/ports/outbound/notification_emitter.py +++ b/src/ars/ports/outbound/event_pub.py @@ -12,21 +12,27 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -"""Outbound port for sending notifications.""" +"""Interface for broadcasting events to other services.""" from abc import ABC, abstractmethod -__all__ = ["NotificationEmitterPort"] +from ars.core import models + + +class EventPublisherPort(ABC): + """An interface for an adapter that publishes events happening to this service.""" + @abstractmethod + async def publish_request_allowed(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was allowed.""" -class NotificationEmitterPort(ABC): - """Emits results of a calculation.""" + @abstractmethod + async def publish_request_created(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was created.""" + ... @abstractmethod - async def notify( - self, *, email: str, full_name: str, subject: str, text: str - ) -> None: - """Send notification to the specified email address.""" + async def publish_request_denied(self, *, request: models.AccessRequest) -> None: + """Publish an event relaying that an access request was denied.""" ... diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index 990ce4c..221dc41 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -119,7 +119,6 @@ async def joint_fixture_function( config = Config( auth_key=AUTH_KEY_PAIR.export_public(), # pyright: ignore download_access_url="http://access", - data_steward_email="steward@ghga.de", **kafka_fixture.config.model_dump(), **mongodb_fixture.config.model_dump(), ) diff --git a/tests/test_api.py b/tests/test_api.py index dd0e94c..2c700b6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,17 +17,13 @@ """Integration test using the REST API of the access request service""" import json -import re -from collections.abc import Sequence from datetime import datetime, timedelta -from typing import NamedTuple, cast import pytest from ghga_service_commons.utils.utc_dates import now_as_utc -from hexkit.providers.akafka.testutils import RecordedEvent from pytest_httpx import HTTPXMock -from .fixtures import ( # noqa: F401 +from tests.fixtures import ( # noqa: F401 JointFixture, fixture_auth_headers_doe, fixture_auth_headers_doe_inactive, @@ -74,34 +70,6 @@ def assert_same_datetime(date1: str, date2: str, max_diff_seconds=5) -> None: ) -class NotificationPayload(NamedTuple): - """Class that stores an expected notification event payload.""" - - email: str - name: str - subject: str # regex pattern - text: str # regex pattern - - -def assert_recorded_events( - recorded_events: Sequence[RecordedEvent], - expected_payloads: list[NotificationPayload], -) -> None: - """Assert that the recorded events are as expected.""" - assert len(recorded_events) == len(expected_payloads) - for event, expected in zip(recorded_events, expected_payloads): - assert event.type_ == "notification" - assert event.key == expected.email - got = event.payload - assert isinstance(got, dict) - assert got["recipient_email"] == expected.email - assert got["email_cc"] == [] - assert got["email_bcc"] == [] - assert got["recipient_name"] == expected.name - assert re.search(expected.subject, cast(str, got["subject"])) - assert re.search(expected.text, cast(str, got["plaintext_body"])) - - async def test_health_check(joint_fixture: JointFixture): """Test that the health check endpoint works.""" response = await joint_fixture.rest_client.get("/health") @@ -115,9 +83,9 @@ async def test_create_access_request( ): """Test that an active user can create an access request.""" kafka = joint_fixture.kafka - topic = joint_fixture.config.notification_event_topic + topic = joint_fixture.config.access_request_events_topic async with kafka.record_events(in_topic=topic): - pass # skip previous notifications + pass # skip previous events async with kafka.record_events(in_topic=topic) as recorder: response = await joint_fixture.rest_client.post( "/access-requests", json=CREATION_DATA, headers=auth_headers_doe @@ -128,23 +96,16 @@ async def test_create_access_request( access_request_id = response.json() assert_is_uuid(access_request_id) - # check that notifications have been sent - assert_recorded_events( - recorder.recorded_events, - [ - NotificationPayload( - "steward@ghga.de", - "Data Steward", - "A data download access request has been created", - "Dr. John Doe requested to download the dataset some-dataset", - ), - NotificationPayload( - "me@john-doe.name", - "Dr. John Doe", - "Your data download access request has been registered", - "Your request to download the dataset some-dataset has been registered", - ), - ], + # check that an event was published for 'access request created' + assert len(recorder.recorded_events) == 1 + recorded_event = recorder.recorded_events[0] + assert recorded_event.key == CREATION_DATA["user_id"] + assert recorded_event.payload == { + "user_id": CREATION_DATA["user_id"], + "dataset_id": CREATION_DATA["dataset_id"], + } + assert ( + recorded_event.type_ == joint_fixture.config.access_request_created_event_type ) @@ -358,9 +319,9 @@ async def test_patch_access_request( # set status to allowed as data steward kafka = joint_fixture.kafka - topic = joint_fixture.config.notification_event_topic + topic = joint_fixture.config.access_request_events_topic async with kafka.record_events(in_topic=topic): - pass # skip previous notifications + pass # skip previous events async with kafka.record_events(in_topic=topic) as recorder: response = await joint_fixture.rest_client.patch( f"/access-requests/{access_request_id}", @@ -379,23 +340,16 @@ async def test_patch_access_request( validity["valid_until"].replace("Z", "+00:00") == CREATION_DATA["access_ends"] ) - # check that notifications have been sent - assert_recorded_events( - recorder.recorded_events, - [ - NotificationPayload( - "steward@ghga.de", - "Data Steward", - "Data download access has been allowed", - "some-dataset has now been registered as allowed", - ), - NotificationPayload( - "me@john-doe.name", - "Dr. John Doe", - "Your data download access request has been accepted", - "You can now start download the dataset", - ), - ], + # check that an event was published for 'access request allowed' + assert len(recorder.recorded_events) == 1 + recorded_event = recorder.recorded_events[0] + assert recorded_event.key == CREATION_DATA["user_id"] + assert recorded_event.payload == { + "user_id": CREATION_DATA["user_id"], + "dataset_id": CREATION_DATA["dataset_id"], + } + assert ( + recorded_event.type_ == joint_fixture.config.access_request_allowed_event_type ) # get request as user diff --git a/tests/test_event_pub.py b/tests/test_event_pub.py index 4f60180..2208164 100644 --- a/tests/test_event_pub.py +++ b/tests/test_event_pub.py @@ -19,18 +19,24 @@ from typing import Any import pytest +from ghga_event_schemas.pydantic_ import AccessRequestDetails +from ghga_service_commons.utils.utc_dates import now_as_utc from hexkit.custom_types import Ascii, JsonObject from hexkit.protocols.eventpub import EventPublisherProtocol from ars.adapters.outbound.event_pub import ( - NotificationEmitter, - NotificationEmitterConfig, + EventPubTranslator, + EventPubTranslatorConfig, ) +from ars.core.models import AccessRequest, AccessRequestStatus pytestmark = pytest.mark.asyncio(scope="session") -dummy_config = NotificationEmitterConfig( - notification_event_topic="dummy_topic", notification_event_type="dummy_type" +dummy_config = EventPubTranslatorConfig( + access_request_allowed_event_type="access_request_allowed", + access_request_created_event_type="access_request_created", + access_request_denied_event_type="access_request_denied", + access_request_events_topic="access_requests", ) @@ -54,27 +60,41 @@ async def _publish_validated( event_recorder = EventRecorder() -notification_emitter = NotificationEmitter( +event_publisher = EventPubTranslator( config=dummy_config, event_publisher=event_recorder ) -async def test_sending_a_notification(): - """Test that a notification is translated properly.""" - await notification_emitter.notify( - email="someone@somewhere.org", - full_name="Some User Name", - subject="Some subject", - text="Some text", +@pytest.mark.parametrize("status", ["created", "allowed", "denied"]) +async def test_access_request_events(status: str): + """Test that an event is published properly.""" + request = AccessRequest( + id="unique_access_request_id", + user_id="user123", + dataset_id="dataset456", + email="requester@example.com", + request_text="Requesting access for research purposes", + access_starts=now_as_utc(), + access_ends=now_as_utc(), + full_user_name="Dr. Jane Doe", + request_created=now_as_utc(), + status=AccessRequestStatus.PENDING, + status_changed=None, + changed_by=None, ) + + publish_method = getattr(event_publisher, f"publish_request_{status}") + await publish_method(request=request) + + expected_topic = dummy_config.access_request_events_topic + expected_type = getattr(dummy_config, f"access_request_{status}_event_type") + expected_payload = AccessRequestDetails( + user_id=request.user_id, dataset_id=request.dataset_id + ).model_dump() + assert event_recorder.recorded_event == { - "recipient_email": "someone@somewhere.org", - "recipient_name": "Some User Name", - "email_cc": [], - "email_bcc": [], - "subject": "Some subject", - "plaintext_body": "Some text", - "topic": "dummy_topic", - "type": "dummy_type", - "key": "someone@somewhere.org", + "type": expected_type, + "key": request.user_id, + "topic": expected_topic, + **expected_payload, } diff --git a/tests/test_repository.py b/tests/test_repository.py index 36a9529..26b485f 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -34,7 +34,7 @@ from ars.core.repository import AccessRequestConfig, AccessRequestRepository from ars.ports.outbound.access_grants import AccessGrantsPort from ars.ports.outbound.dao import AccessRequestDaoPort, ResourceNotFoundError -from ars.ports.outbound.notification_emitter import NotificationEmitterPort +from ars.ports.outbound.event_pub import EventPublisherPort pytestmark = pytest.mark.asyncio(scope="session") @@ -74,7 +74,6 @@ access_upfront_max_days=365, access_grant_min_days=30, access_grant_max_days=2 * 365, - data_steward_email=auth_context_steward.email, ) @@ -190,39 +189,46 @@ async def update(self, dto: AccessRequest) -> None: self.last_upsert = dto -class NotificationRecord(NamedTuple): - """Class that records a sent notification while testing.""" +class MockAccessRequestEvent(NamedTuple): + """Mock of AccessRequestDetails plus status field to represent event type""" - recipient: str - subject: str - text: str + user_id: str + dataset_id: str + status: str -class NotificationEmitterDummy(NotificationEmitterPort): - """Dummy notification emitter for testing.""" +class EventPublisherDummy(EventPublisherPort): + """Dummy event publisher for testing.""" - notifications: dict[str, NotificationRecord] + events: list[MockAccessRequestEvent] def reset(self) -> None: - """Reset the recorded notification.""" - self.notifications = {} + """Reset the recorded events.""" + self.events = [] @property - def num_notifications(self): - """Get total number of recorded notifications.""" - return len(self.notifications) + def num_events(self): + """Get total number of recorded events.""" + return len(self.events) + + def _record_request(self, *, request: AccessRequest, request_state: str): + """Record a request as either created, allowed, or denied for a user and dataset.""" + mock_event = MockAccessRequestEvent( + request.user_id, request.dataset_id, request_state + ) + self.events.append(mock_event) - def notification_for(self, email: str) -> NotificationRecord: - """Get recorded notification to the given email.""" - return self.notifications[email] + async def publish_request_allowed(self, *, request: AccessRequest) -> None: + """Mark an access request as allowed via event publish.""" + self._record_request(request=request, request_state="allowed") - async def notify( - self, *, email: str, full_name: str, subject: str, text: str - ) -> None: - """Send a notification.""" - if email in self.notifications: - raise RuntimeError(f"A notification to {email} was already sent.") - self.notifications[email] = NotificationRecord(full_name, subject, text) + async def publish_request_created(self, *, request: AccessRequest) -> None: + """Mark an access request as created via event publish.""" + self._record_request(request=request, request_state="created") + + async def publish_request_denied(self, *, request: AccessRequest) -> None: + """Mark an access request as denied via event publish.""" + self._record_request(request=request, request_state="denied") class AccessGrantsDummy(AccessGrantsPort): @@ -253,21 +259,21 @@ async def grant_download_access( dao = AccessRequestDaoDummy() -notification_emitter = NotificationEmitterDummy() +event_publisher = EventPublisherDummy() access_grants = AccessGrantsDummy() def reset(): """Reset dummy adapters.""" dao.reset() - notification_emitter.reset() + event_publisher.reset() access_grants.reset() repository = AccessRequestRepository( config=config, access_request_dao=dao, - notification_emitter=notification_emitter, + event_publisher=event_publisher, access_grants=access_grants, ) @@ -304,24 +310,11 @@ async def test_can_create_request(): assert dao.last_upsert == request - assert notification_emitter.num_notifications == 2 - notification = notification_emitter.notification_for("steward@ghga.de") - assert notification.recipient == "Data Steward" - assert "access request has been created" in notification.subject - assert ( - notification.text - == "Dr. John Doe requested to download the dataset some-dataset.\n\n" - + "The specified contact email address is: me@john-doe.name" - ) - notification = notification_emitter.notification_for("me@john-doe.name") - assert notification.recipient == "Dr. John Doe" - assert "Your data download access request" in notification.subject - assert ( - notification.text - == "Your request to download the dataset some-dataset has been registered.\n\n" - + "You should be contacted by one of our data stewards" - + " in the next three workdays." + # the 'publish_request_created' method should have been called, get events for request + expected_event = MockAccessRequestEvent( + request.user_id, request.dataset_id, "created" ) + assert event_publisher.events == [expected_event] assert access_grants.last_grant == "nothing granted so far" @@ -365,7 +358,8 @@ async def test_silently_correct_request_that_is_too_early(): assert request.access_ends == creation_data.access_ends assert dao.last_upsert == request - assert notification_emitter.num_notifications == 2 + # There should be one event published which communicates the state of the request + assert len(event_publisher.events) == 1 async def test_cannot_create_request_too_much_in_advance(): @@ -388,7 +382,7 @@ async def test_cannot_create_request_too_much_in_advance(): await repository.create(creation_data, auth_context=auth_context_doe) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 async def test_cannot_create_request_too_short(): @@ -411,7 +405,7 @@ async def test_cannot_create_request_too_short(): await repository.create(creation_data, auth_context=auth_context_doe) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 async def test_cannot_create_request_too_long(): @@ -434,7 +428,7 @@ async def test_cannot_create_request_too_long(): await repository.create(creation_data, auth_context=auth_context_doe) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 async def test_can_get_all_requests_as_data_steward(): @@ -550,25 +544,10 @@ async def test_set_status_to_allowed(): assert changed_dict.pop("changed_by") == "id-of-rod-steward@ghga.de" assert changed_dict == original_dict - assert notification_emitter.num_notifications == 2 - notification = notification_emitter.notification_for("steward@ghga.de") - assert notification.recipient == "Data Steward" - assert "download access has been allowed" in notification.subject - assert ( - notification.text - == "The request by Dr. John Doe to download the dataset\n" - + "new-dataset has now been registered as allowed\n" - + "and the access has been granted." - ) - notification = notification_emitter.notification_for("me@john-doe.name") - assert notification.recipient == "Dr. John Doe" - assert "Your data download access request has been accepted" in notification.subject - assert ( - notification.text - == "We are glad to inform you that your request to download the dataset\n" - + "new-dataset has been accepted.\n\n" - + "You can now start download the dataset as explained in the GHGA Data Portal." + expected_event = MockAccessRequestEvent( + changed_request.user_id, changed_request.dataset_id, "allowed" ) + assert event_publisher.events == [expected_event] assert ( access_grants.last_grant == "to id-of-john-doe@ghga.de for new-dataset" @@ -600,7 +579,7 @@ async def test_set_status_to_allowed_with_error_when_granting_access(): changed_request = dao.last_upsert assert changed_request is not None assert changed_request == original_request - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 async def test_set_status_to_allowed_when_it_is_already_allowed(): @@ -620,7 +599,7 @@ async def test_set_status_to_allowed_when_it_is_already_allowed(): ) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 assert access_grants.last_grant == "nothing granted so far" @@ -658,7 +637,7 @@ async def test_set_status_of_non_existing_request(): ) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 assert access_grants.last_grant == "nothing granted so far" @@ -674,5 +653,5 @@ async def test_set_status_when_not_a_data_steward(): ) assert dao.last_upsert is None - assert notification_emitter.num_notifications == 0 + assert event_publisher.num_events == 0 assert access_grants.last_grant == "nothing granted so far"