diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index df544c58..4be4b941 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -43,19 +43,19 @@ jobs: persist-credentials: false - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 # Set up BuildKit Docker container builder to be able to build # multi-platform images and export cache # https://github.com/docker/setup-buildx-action - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -65,7 +65,7 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -73,7 +73,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/requirements-dev.txt b/requirements-dev.txt index 2cdd37dd..572a0ce0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ # uv pip compile requirements-dev.in -o requirements-dev.txt aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.3 +aiohttp==3.13.5 # via # aiohttp-jinja2 # textual-dev @@ -11,9 +11,11 @@ aiohttp-jinja2==1.6 # via textual-serve aiosignal==1.4.0 # via aiohttp +annotated-doc==0.0.4 + # via typer annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.13.0 # via # asyncer # httpx @@ -22,15 +24,15 @@ asgi-lifespan==2.1.0 # via -r requirements-dev.in asttokens==3.0.1 # via stack-data -asyncer==0.0.11 +asyncer==0.0.17 # via -r requirements-dev.in -attrs==25.4.0 +attrs==26.1.0 # via aiohttp -backports-zstd==1.2.0 +backports-zstd==1.4.0 # via hatch bidict==0.23.1 # via python-socketio -black==25.12.0 +black==26.3.1 # via -r requirements-dev.in blinker==1.9.0 # via flask @@ -38,7 +40,7 @@ brotli==1.2.0 # via geventhttpclient bunnet==1.3.0 # via -r requirements-dev.in -certifi==2025.11.12 +certifi==2026.4.22 # via # geventhttpclient # httpcore @@ -48,9 +50,9 @@ cffi==2.0.0 # via cryptography cfgv==3.5.0 # via pre-commit -charset-normalizer==3.4.4 +charset-normalizer==3.4.7 # via requests -click==8.3.1 +click==8.3.3 # via # black # bunnet @@ -61,13 +63,11 @@ click==8.3.1 # typer # userpath # uvicorn -configargparse==1.7.1 - # via - # locust - # locust-cloud -coverage==7.13.0 +configargparse==1.7.5 + # via locust +coverage==7.13.5 # via -r requirements-dev.in -cryptography==46.0.3 +cryptography==48.0.0 # via secretstorage decorator==5.2.1 # via ipython @@ -77,16 +77,18 @@ dnspython==2.8.0 # via pymongo executing==2.2.1 # via stack-data -fastapi-cli==0.0.16 +fastapi-cli==0.0.24 # via -r requirements-dev.in -filelock==3.20.3 - # via virtualenv -flask==3.1.2 +filelock==3.29.0 + # via + # python-discovery + # virtualenv +flask==3.1.3 # via # flask-cors # flask-login # locust -flask-cors==6.0.1 +flask-cors==6.0.2 # via locust flask-login==0.6.3 # via locust @@ -98,23 +100,22 @@ gevent==25.9.1 # via # geventhttpclient # locust - # locust-cloud -geventhttpclient==2.3.7 +geventhttpclient==2.3.9 # via locust -greenlet==3.3.0 +greenlet==3.5.0 # via gevent h11==0.16.0 # via # httpcore # uvicorn # wsproto -hatch==1.16.2 +hatch==1.16.5 # via -r requirements-dev.in hatch-requirements-txt==0.4.1 # via -r requirements-dev.in hatch-vcs==0.5.0 # via -r requirements-dev.in -hatchling==1.28.0 +hatchling==1.29.0 # via # -r requirements-dev.in # hatch @@ -128,9 +129,9 @@ httpx==0.28.1 # via hatch hyperlink==21.0.0 # via hatch -identify==2.6.15 +identify==2.6.19 # via pre-commit -idna==3.11 +idna==3.13 # via # anyio # httpx @@ -139,7 +140,7 @@ idna==3.11 # yarl iniconfig==2.3.0 # via pytest -ipython==9.8.0 +ipython==9.13.0 # via -r requirements-dev.in ipython-pygments-lexers==1.1.1 # via ipython @@ -147,11 +148,11 @@ itsdangerous==2.2.0 # via flask jaraco-classes==3.4.0 # via keyring -jaraco-context==6.0.1 +jaraco-context==6.1.2 # via keyring -jaraco-functools==4.3.0 +jaraco-functools==4.4.0 # via keyring -jedi==0.19.2 +jedi==0.20.0 # via ipython jeepney==0.9.0 # via @@ -166,13 +167,11 @@ keyring==25.7.0 # via hatch lazy-model==0.2.0 # via bunnet -linkify-it-py==2.0.3 +linkify-it-py==2.1.0 # via markdown-it-py -locust==2.42.6 +locust==2.43.4 # via -r requirements-dev.in -locust-cloud==1.29.5 - # via locust -markdown-it-py[linkify]==4.0.0 +markdown-it-py==4.1.0 # via # mdit-py-plugins # rich @@ -188,7 +187,7 @@ mdit-py-plugins==0.5.0 # via textual mdurl==0.1.2 # via markdown-it-py -more-itertools==10.8.0 +more-itertools==11.0.2 # via # jaraco-classes # jaraco-functools @@ -196,17 +195,17 @@ msgpack==1.1.2 # via # locust # textual-dev -multidict==6.7.0 +multidict==6.7.1 # via # aiohttp # yarl mypy-extensions==1.1.0 # via black -nodeenv==1.9.1 +nodeenv==1.10.0 # via # pre-commit # pyright -packaging==25.0 +packaging==26.2 # via # black # hatch @@ -214,9 +213,10 @@ packaging==25.0 # hatchling # pytest # setuptools-scm -parso==0.8.5 + # vcs-versioning +parso==0.8.7 # via jedi -pathspec==0.12.1 +pathspec==1.1.1 # via # black # hatchling @@ -224,20 +224,20 @@ pexpect==4.9.0 # via # hatch # ipython -platformdirs==4.5.1 +platformdirs==4.9.6 # via # black # hatch - # locust-cloud + # python-discovery # textual # virtualenv pluggy==1.6.0 # via # hatchling # pytest -pre-commit==4.5.0 +pre-commit==4.6.0 # via pre-commit-uv -pre-commit-uv==4.2.0 +pre-commit-uv==4.2.1 # via -r requirements-dev.in prompt-toolkit==3.0.52 # via ipython @@ -245,52 +245,55 @@ propcache==0.4.1 # via # aiohttp # yarl -psutil==7.1.3 - # via locust +psutil==7.2.2 + # via + # ipython + # locust ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data -pycparser==2.23 +pycparser==3.0 # via cffi -pydantic==2.12.5 +pydantic==2.13.4 # via # bunnet # lazy-model -pydantic-core==2.41.5 +pydantic-core==2.46.4 # via pydantic -pygments==2.19.2 +pygments==2.20.0 # via # ipython # ipython-pygments-lexers # pytest # rich # textual -pymongo==4.15.5 +pymongo==4.17.0 # via bunnet pyproject-hooks==1.2.0 # via hatch -pyright==1.1.407 +pyright==1.1.409 # via -r requirements-dev.in -pytest==9.0.2 +pytest==9.0.3 # via # -r requirements-dev.in # locust # pytest-asyncio pytest-asyncio==1.3.0 # via -r requirements-dev.in -python-dotenv==1.2.1 +python-discovery==1.3.0 + # via + # hatch + # virtualenv +python-dotenv==1.2.2 # via uvicorn -python-engineio==4.12.3 +python-engineio==4.13.1 # via # locust - # locust-cloud # python-socketio -python-socketio[client]==5.15.0 - # via - # locust - # locust-cloud -pytokens==0.3.0 +python-socketio==5.16.1 + # via locust +pytokens==0.4.1 # via black pyyaml==6.0.3 # via @@ -298,24 +301,26 @@ pyyaml==6.0.3 # uvicorn pyzmq==27.1.0 # via locust -requests==2.32.4 +requests==2.33.1 # via # locust # python-socketio -rich==14.2.0 +rich==15.0.0 # via # hatch # rich-toolkit # textual # textual-serve # typer -rich-toolkit==0.17.0 +rich-toolkit==0.19.7 # via fastapi-cli -ruff==0.14.8 +ruff==0.15.12 # via -r requirements-dev.in secretstorage==3.5.0 # via keyring -setuptools-scm==9.2.2 +setuptools==82.0.1 + # via setuptools-scm +setuptools-scm==10.0.5 # via hatch-vcs shellingham==1.5.4 # via @@ -329,7 +334,7 @@ sniffio==1.3.1 # asyncer stack-data==0.6.3 # via ipython -textual==6.8.0 +textual==8.2.5 # via # textual-dev # textual-serve @@ -341,20 +346,21 @@ toml==0.10.2 # via bunnet tomli-w==1.2.0 # via hatch -tomlkit==0.13.3 +tomlkit==0.14.0 # via hatch -traitlets==5.14.3 +traitlets==5.15.0 # via # ipython # matplotlib-inline -trove-classifiers==2025.12.1.14 +trove-classifiers==2026.4.28.13 # via hatchling -typer==0.20.0 +typer==0.25.1 # via fastapi-cli typing-extensions==4.15.0 # via # aiosignal # anyio + # asyncer # pydantic # pydantic-core # pyright @@ -362,11 +368,10 @@ typing-extensions==4.15.0 # rich-toolkit # textual # textual-dev - # typer # typing-inspection typing-inspection==0.4.2 # via pydantic -uc-micro-py==1.0.3 +uc-micro-py==2.0.0 # via linkify-it-py urllib3==2.6.3 # via @@ -374,28 +379,30 @@ urllib3==2.6.3 # requests userpath==1.9.2 # via hatch -uv==0.9.16 +uv==0.11.10 # via # -r requirements-dev.in # hatch # pre-commit-uv -uvicorn[standard]==0.38.0 +uvicorn==0.46.0 # via fastapi-cli uvloop==0.22.1 # via uvicorn -virtualenv==20.36.1 +vcs-versioning==1.1.1 + # via setuptools-scm +virtualenv==21.3.1 # via # hatch # pre-commit watchfiles==1.1.1 # via uvicorn -wcwidth==0.2.14 +wcwidth==0.7.0 # via prompt-toolkit websocket-client==1.9.0 # via python-socketio -websockets==15.0.1 +websockets==16.0 # via uvicorn -werkzeug==3.1.5 +werkzeug==3.1.8 # via # flask # flask-cors @@ -403,9 +410,9 @@ werkzeug==3.1.5 # locust wsproto==1.3.2 # via simple-websocket -yarl==1.22.0 +yarl==1.23.0 # via aiohttp -zope-event==6.1 +zope-event==6.2 # via gevent -zope-interface==8.1.1 +zope-interface==8.4 # via gevent diff --git a/requirements.txt b/requirements.txt index 558ea077..600e2302 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,12 @@ aiofiles==25.1.0 # via -r requirements.in annotated-doc==0.0.4 - # via fastapi + # via + # fastapi + # typer annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.13.0 # via # httpx # starlette @@ -16,15 +18,15 @@ argon2-cffi-bindings==25.1.0 # via argon2-cffi asgi-correlation-id==4.3.4 # via -r requirements.in -beanie==2.0.1 +beanie==2.1.0 # via -r requirements.in -certifi==2025.11.12 +certifi==2026.4.22 # via # httpcore # httpx cffi==2.0.0 # via argon2-cffi-bindings -click==8.3.1 +click==8.3.3 # via # beanie # typer @@ -33,21 +35,21 @@ decorator==5.2.1 # via gssapi dnspython==2.8.0 # via pymongo -dunamai==1.25.0 +dunamai==1.26.1 # via uv-dynamic-versioning -faker==38.2.0 +faker==40.15.0 # via -r requirements.in -fastapi==0.124.0 +fastapi==0.136.1 # via -r requirements.in -gssapi==1.10.1 +gssapi==1.11.1 # via n2snusertools -gunicorn==23.0.0 +gunicorn==26.0.0 # via -r requirements.in h11==0.16.0 # via # httpcore # uvicorn -hatchling==1.28.0 +hatchling==1.29.0 # via uv-dynamic-versioning httpcore==1.0.9 # via @@ -57,13 +59,13 @@ httpx==0.28.1 # via # -r requirements.in # httpx-socks -httpx-socks[asyncio]==0.11.0 +httpx-socks==0.11.0 # via -r requirements.in -idna==3.11 +idna==3.13 # via # anyio # httpx -jinja-partials==0.3.0 +jinja-partials==0.3.1 # via -r requirements.in jinja2==3.1.6 # via @@ -74,9 +76,9 @@ lazy-model==0.4.0 # via beanie ldap3==2.9.1 # via n2snusertools -linkify-it-py==2.0.3 +linkify-it-py==2.1.0 # via markdown-it-py -markdown-it-py[linkify]==4.0.0 +markdown-it-py==4.1.0 # via # mdit-py-plugins # rich @@ -91,7 +93,7 @@ mdurl==0.1.2 # via markdown-it-py n2snusertools==0.3.10 # via -r requirements.in -packaging==25.0 +packaging==26.2 # via # asgi-correlation-id # dunamai @@ -99,76 +101,76 @@ packaging==25.0 # hatchling passlib==1.7.4 # via -r requirements.in -pathspec==0.12.1 +pathspec==1.1.1 # via hatchling -platformdirs==4.5.1 +platformdirs==4.9.6 # via textual pluggy==1.6.0 # via hatchling prettytable==3.17.0 # via n2snusertools -prometheus-client==0.23.1 +prometheus-client==0.25.0 # via prometheus-fastapi-instrumentator prometheus-fastapi-instrumentator==7.1.0 # via -r requirements.in -pyasn1==0.6.2 +pyasn1==0.6.3 # via ldap3 -pycparser==2.23 +pycparser==3.0 # via cffi -pydantic==2.12.5 +pydantic==2.13.4 # via # -r requirements.in # beanie # fastapi # lazy-model # pydantic-settings -pydantic-core==2.41.5 +pydantic-core==2.46.4 # via pydantic -pydantic-settings==2.12.0 +pydantic-settings==2.14.0 # via -r requirements.in -pygments==2.19.2 +pygments==2.20.0 # via # rich # textual -pymongo==4.15.5 +pymongo==4.17.0 # via # -r requirements.in # beanie -python-dotenv==1.2.1 +python-dotenv==1.2.2 # via pydantic-settings -python-multipart==0.0.20 +python-multipart==0.0.27 # via -r requirements.in -python-socks==2.8.0 +python-socks==2.8.1 # via httpx-socks pyyaml==6.0.3 # via n2snusertools -rich==14.2.0 +rich==15.0.0 # via # -r requirements.in # textual # typer shellingham==1.5.4 # via typer -slack-bolt==1.27.0 +slack-bolt==1.28.0 # via -r requirements.in -slack-sdk==3.39.0 +slack-sdk==3.41.0 # via # -r requirements.in # slack-bolt sniffio==1.3.1 # via httpx-socks -starlette==0.50.0 +starlette==0.52.1 # via # asgi-correlation-id # fastapi # prometheus-fastapi-instrumentator -textual==6.8.0 +textual==8.2.5 # via -r requirements.in -tomlkit==0.13.3 +tomlkit==0.14.0 # via uv-dynamic-versioning -trove-classifiers==2025.12.1.14 +trove-classifiers==2026.4.28.13 # via hatchling -typer==0.20.0 +typer==0.25.1 # via -r requirements.in typing-extensions==4.15.0 # via @@ -179,23 +181,21 @@ typing-extensions==4.15.0 # pydantic-core # starlette # textual - # typer # typing-inspection typing-inspection==0.4.2 # via + # fastapi # pydantic # pydantic-settings -tzdata==2025.2 - # via faker -uc-micro-py==1.0.3 +uc-micro-py==2.0.0 # via linkify-it-py uuid==1.30 # via -r requirements.in -uv-dynamic-versioning==0.11.2 +uv-dynamic-versioning==0.14.0 # via -r requirements.in -uvicorn==0.38.0 +uvicorn==0.46.0 # via -r requirements.in -wcwidth==0.2.14 +wcwidth==0.7.0 # via prettytable -werkzeug==3.1.5 +werkzeug==3.1.8 # via -r requirements.in diff --git a/scripts/create_new_beamline.py b/scripts/create_new_beamline.py index db416eab..bb94336e 100644 --- a/scripts/create_new_beamline.py +++ b/scripts/create_new_beamline.py @@ -13,7 +13,7 @@ async def main(): # Initialize Beanie - await mongodb_setup.init_connection(settings.mongodb_dsn) + _ = await mongodb_setup.init_connection(settings.mongodb_dsn) pass_resources = await pass_service.get_pass_resources() pass_ids = [r["ID"] for r in pass_resources if r["Code"] == BEAMLINE_NAME] diff --git a/scripts/update_beamline.py b/scripts/update_beamline.py index 66c69503..ea2caf6c 100644 --- a/scripts/update_beamline.py +++ b/scripts/update_beamline.py @@ -14,7 +14,7 @@ async def main(): # Initialize Beanie - await mongodb_setup.init_connection(settings.mongodb_dsn) + _ = await mongodb_setup.init_connection(settings.mongodb_dsn) pass_resources = await pass_service.get_pass_resources() pass_ids = [r["ID"] for r in pass_resources if r["Code"] == BEAMLINE_NAME] @@ -27,7 +27,7 @@ async def main(): beamline = await Beamline.find_one(Beamline.pass_id == str(pass_ids[0])) if not beamline: - raise KeyError(f"No beamline found with pass_id {pass_ids[0]}") + raise KeyError(f"No beamline found with pass_id {pass_ids[0]}") print("Current beamline:") print(beamline) @@ -40,7 +40,7 @@ async def main(): workflow="workflow-tla", bluesky="bluesky-tla", operator="xf99id", - lsdc=None + lsdc=None, ) # INCLUDE ADDITIONAL CHANGES TO THE BEAMLINE OBJECT HERE AS NEEDED @@ -56,5 +56,6 @@ async def main(): # Uncomment the line below to actually save the changes to the database # await beamline.save() + if __name__ == "__main__": asyncio.run(main()) diff --git a/src/nsls2api/infrastructure/app_setup.py b/src/nsls2api/infrastructure/app_setup.py index b29b73ee..8e86ef9b 100644 --- a/src/nsls2api/infrastructure/app_setup.py +++ b/src/nsls2api/infrastructure/app_setup.py @@ -21,7 +21,7 @@ async def app_lifespan(app: FastAPI): logger.info(f"NSLS-II API Version: {get_version()}") # Initialize the MongoDB connection - await mongodb_setup.init_connection(settings.mongodb_dsn) + mongo_client = await mongodb_setup.init_connection(settings.mongodb_dsn) # Create a shared httpx client httpx_client_wrapper.start() @@ -35,3 +35,6 @@ async def app_lifespan(app: FastAPI): # Cleanup httpx client await httpx_client_wrapper.stop() + + # Close MongoDB client + await mongo_client.close() diff --git a/src/nsls2api/infrastructure/mongodb_setup.py b/src/nsls2api/infrastructure/mongodb_setup.py index 6a079e71..a2cabd31 100644 --- a/src/nsls2api/infrastructure/mongodb_setup.py +++ b/src/nsls2api/infrastructure/mongodb_setup.py @@ -37,3 +37,5 @@ async def init_connection(mongodb_dsn: MongoDsn): logger.info( f"Connected to {click.style(client.get_default_database().name, fg='green')} database." ) + + return client diff --git a/src/nsls2api/models/proposals.py b/src/nsls2api/models/proposals.py index 93f48427..e3af2769 100644 --- a/src/nsls2api/models/proposals.py +++ b/src/nsls2api/models/proposals.py @@ -4,6 +4,7 @@ import beanie import pydantic import pymongo +from pydantic import ConfigDict from nsls2api.models.slack_models import SlackChannel diff --git a/src/nsls2api/tests/conftest.py b/src/nsls2api/tests/conftest.py index 1d867059..5242c9cf 100644 --- a/src/nsls2api/tests/conftest.py +++ b/src/nsls2api/tests/conftest.py @@ -17,7 +17,7 @@ @pytest_asyncio.fixture(scope="function", autouse=True) async def db(): settings = get_settings() - await init_connection(settings.mongodb_dsn) + client = await init_connection(settings.mongodb_dsn) # Insert a beamline into the database beamline = Beamline( @@ -100,14 +100,17 @@ async def db(): await model.get_pymongo_collection().drop() await model.get_pymongo_collection().drop_indexes() + # Close the MongoDB client to prevent event loop issues + await client.close() -@pytest_asyncio.fixture(scope="function", autouse=True) + +@pytest_asyncio.fixture(scope="function") async def api_key(db): """Generate and return an API key for test authentication.""" return await generate_api_key(username="test_user", usertype=ApiUserType.user) -@pytest_asyncio.fixture(scope="function", autouse=True) +@pytest_asyncio.fixture(scope="function") async def admin_api_key(db): """Generate and return an admin API key for test authentication.""" # Create API key for the admin test user