From 752ed692957b58c2e5d9a0e145bf55ab87403356 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Fri, 19 Jul 2024 16:56:55 -0700 Subject: [PATCH 1/3] Separate safir-arq from safir Move the safir.arq module into a separate package that can be independently distributed on PyPI. This allows the arq support library to be installed in contexts where the Safir dependency is too heavy-weight. Convert all of the submodules of Safir into directories so that they can include py.typed files now that the safir package is a namespace-only package. When importing across the namespace package, avoid using relative imports just for clarity. Use relative imports only within the same subpackage of Safir. --- .github/workflows/ci.yaml | 14 ++++- noxfile.py | 8 ++- pyproject.toml | 3 +- safir-arq/LICENSE | 1 + safir-arq/README.md | 13 +++++ safir-arq/pyproject.toml | 51 +++++++++++++++++++ .../src/safir/arq/__init__.py | 14 +++-- .../src/safir/arq}/py.typed | 0 safir/pyproject.toml | 2 +- safir/src/safir/__init__.py | 22 -------- .../safir/{asyncio.py => asyncio/__init__.py} | 2 +- safir/src/safir/asyncio/py.typed | 0 .../src/safir/{click.py => click/__init__.py} | 0 safir/src/safir/click/py.typed | 0 safir/src/safir/database/py.typed | 0 .../{datetime.py => datetime/__init__.py} | 0 safir/src/safir/datetime/py.typed | 0 safir/src/safir/dependencies/arq.py | 2 +- safir/src/safir/dependencies/db_session.py | 2 +- safir/src/safir/dependencies/py.typed | 0 .../safir/{fastapi.py => fastapi/__init__.py} | 4 +- safir/src/safir/fastapi/py.typed | 0 safir/src/safir/{gcs.py => gcs/__init__.py} | 0 safir/src/safir/gcs/py.typed | 0 safir/src/safir/github/py.typed | 0 .../{kubernetes.py => kubernetes/__init__.py} | 0 safir/src/safir/kubernetes/py.typed | 0 .../safir/{logging.py => logging/__init__.py} | 0 safir/src/safir/logging/py.typed | 0 .../{metadata.py => metadata/__init__.py} | 0 safir/src/safir/metadata/py.typed | 0 safir/src/safir/middleware/py.typed | 0 .../safir/{models.py => models/__init__.py} | 0 safir/src/safir/models/py.typed | 0 .../{pydantic.py => pydantic/__init__.py} | 2 +- safir/src/safir/pydantic/py.typed | 0 .../src/safir/{redis.py => redis/__init__.py} | 2 +- safir/src/safir/redis/py.typed | 0 safir/src/safir/slack/py.typed | 0 safir/src/safir/slack/webhook.py | 5 +- safir/src/safir/testing/kubernetes.py | 2 +- safir/src/safir/testing/py.typed | 0 safir/tests/safir_test.py | 8 --- 43 files changed, 105 insertions(+), 52 deletions(-) create mode 120000 safir-arq/LICENSE create mode 100644 safir-arq/README.md create mode 100644 safir-arq/pyproject.toml rename safir/src/safir/arq.py => safir-arq/src/safir/arq/__init__.py (98%) rename {safir/src/safir => safir-arq/src/safir/arq}/py.typed (100%) delete mode 100644 safir/src/safir/__init__.py rename safir/src/safir/{asyncio.py => asyncio/__init__.py} (99%) create mode 100644 safir/src/safir/asyncio/py.typed rename safir/src/safir/{click.py => click/__init__.py} (100%) create mode 100644 safir/src/safir/click/py.typed create mode 100644 safir/src/safir/database/py.typed rename safir/src/safir/{datetime.py => datetime/__init__.py} (100%) create mode 100644 safir/src/safir/datetime/py.typed create mode 100644 safir/src/safir/dependencies/py.typed rename safir/src/safir/{fastapi.py => fastapi/__init__.py} (98%) create mode 100644 safir/src/safir/fastapi/py.typed rename safir/src/safir/{gcs.py => gcs/__init__.py} (100%) create mode 100644 safir/src/safir/gcs/py.typed create mode 100644 safir/src/safir/github/py.typed rename safir/src/safir/{kubernetes.py => kubernetes/__init__.py} (100%) create mode 100644 safir/src/safir/kubernetes/py.typed rename safir/src/safir/{logging.py => logging/__init__.py} (100%) create mode 100644 safir/src/safir/logging/py.typed rename safir/src/safir/{metadata.py => metadata/__init__.py} (100%) create mode 100644 safir/src/safir/metadata/py.typed create mode 100644 safir/src/safir/middleware/py.typed rename safir/src/safir/{models.py => models/__init__.py} (100%) create mode 100644 safir/src/safir/models/py.typed rename safir/src/safir/{pydantic.py => pydantic/__init__.py} (99%) create mode 100644 safir/src/safir/pydantic/py.typed rename safir/src/safir/{redis.py => redis/__init__.py} (99%) create mode 100644 safir/src/safir/redis/py.typed create mode 100644 safir/src/safir/slack/py.typed create mode 100644 safir/src/safir/testing/py.typed delete mode 100644 safir/tests/safir_test.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6a5de45c..448f2e90 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -108,13 +108,20 @@ jobs: with: fetch-depth: 0 # full history for setuptools_scm - - name: Build and publish + - name: Test building and publishing safir uses: lsst-sqre/build-and-publish-to-pypi@v2 with: python-version: ${{ env.PYTHON_VERSION }} upload: false working-directory: "safir" + - name: Test building and publishing safir-arq + uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + upload: false + working-directory: "safir-arq" + pypi: name: Upload release to PyPI @@ -137,3 +144,8 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} working-directory: "safir" + + - uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + working-directory: "safir-arq" diff --git a/noxfile.py b/noxfile.py index 0238c11a..aa6fc478 100644 --- a/noxfile.py +++ b/noxfile.py @@ -24,7 +24,9 @@ def _install(session: nox.Session) -> None: """Install the application and all dependencies into the session.""" session.install("--upgrade", "uv") - session.install("-e", "./safir[arq,db,dev,gcs,kubernetes,redis]") + session.install( + "-e", "./safir-arq", "./safir[arq,db,dev,gcs,kubernetes,redis]" + ) def _install_dev(session: nox.Session, bin_prefix: str = "") -> None: @@ -105,9 +107,13 @@ def typing(session: nox.Session) -> None: session.run( "mypy", *session.posargs, + "--namespace-packages", + "--explicit-package-bases", "noxfile.py", "safir/src", + "safir-arq/src", "safir/tests", + env={"MYPYPATH": "safir/src:safir:safir-arq/src"}, ) diff --git a/pyproject.toml b/pyproject.toml index dd845afa..491f7cc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ extend = "ruff-shared.toml" "noxfile.py" = [ "T201", # print makes sense as output from nox rules ] -"safir/src/safir/**" = [ +"*/src/safir/**" = [ "N818", # Exception is correct in some cases, others are part of API ] "safir/src/safir/testing/**" = [ @@ -70,6 +70,7 @@ extend = "ruff-shared.toml" ] [tool.ruff.lint.isort] +detect-same-package = false known-first-party = ["safir", "tests"] split-on-trailing-comma = false diff --git a/safir-arq/LICENSE b/safir-arq/LICENSE new file mode 120000 index 00000000..ea5b6064 --- /dev/null +++ b/safir-arq/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/safir-arq/README.md b/safir-arq/README.md new file mode 100644 index 00000000..af899257 --- /dev/null +++ b/safir-arq/README.md @@ -0,0 +1,13 @@ +# safir-arq + +safir-arq is a subpackage of Safir, Rubin Observatory's library for building [FastAPI](https://fastapi.tiangolo.com/) services for the [Rubin Science Platform (Phalanx)](https://github.com/lsst-sqre/phalanx) and [Roundtable](https://github.com/lsst-sqre/roundtable) Kubernetes clusters. +It is a separate PyPI module so that it can be used as a dependency in contexts where the full Safir dependency is undesirable. + +safir-arq is available from [PyPI](https://pypi.org/project/safir-arq/): + +```sh +pip install safir-arq +``` + +safir-arq is developed and tested in conjunction with Safir. +Read more about Safir at https://safir.lsst.io. diff --git a/safir-arq/pyproject.toml b/safir-arq/pyproject.toml new file mode 100644 index 00000000..d632c1d7 --- /dev/null +++ b/safir-arq/pyproject.toml @@ -0,0 +1,51 @@ +[project] +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ +name = "safir-arq" +description = "arq support for the Rubin Observatory SQuaRE framework, Safir." +license = {file = "LICENSE"} +readme= "README.md" +keywords = [ + "rubin", + "lsst", +] +# https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX", + "Typing :: Typed", +] +requires-python = ">=3.11" +dependencies = [ + "arq>=0.23,<1", + "pydantic>2,<3", + "pydantic-core", +] +dynamic = ["version"] + +[[project.authors]] +name = "Association of Universities for Research in Astronomy, Inc. (AURA)" +email = "sqre-admin@lists.lsst.org" + +[project.urls] +Homepage = "https://safir.lsst.io" +Source = "https://github.com/lsst-sqre/safir" +"Change log" = "https://safir.lsst.io/changelog.html" +"Issue tracker" = "https://github.com/lsst-sqre/safir/issues" + +[build-system] +requires = [ + "setuptools>=61", + "wheel", + "setuptools_scm[toml]>=6.2" +] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +root = ".." diff --git a/safir/src/safir/arq.py b/safir-arq/src/safir/arq/__init__.py similarity index 98% rename from safir/src/safir/arq.py rename to safir-arq/src/safir/arq/__init__.py index 2e5ef20d..f329a53a 100644 --- a/safir/src/safir/arq.py +++ b/safir-arq/src/safir/arq/__init__.py @@ -6,7 +6,7 @@ import asyncio import uuid from dataclasses import dataclass -from datetime import datetime +from datetime import UTC, datetime from enum import Enum from typing import Any, Self @@ -17,8 +17,6 @@ from pydantic import SecretStr from pydantic_core import Url -from .datetime import current_datetime - __all__ = [ "ArqJobError", "JobNotQueued", @@ -515,7 +513,7 @@ async def enqueue( name=task_name, args=task_args, kwargs=task_kwargs, - enqueue_time=current_datetime(microseconds=True), + enqueue_time=datetime.now(tz=UTC), status=JobStatus.queued, queue_name=queue_name, ) @@ -545,8 +543,8 @@ async def abort_job( kwargs=job_metadata.kwargs, status=job_metadata.status, enqueue_time=job_metadata.enqueue_time, - start_time=current_datetime(microseconds=True), - finish_time=current_datetime(microseconds=True), + start_time=datetime.now(tz=UTC), + finish_time=datetime.now(tz=UTC), result=asyncio.CancelledError(), success=False, queue_name=queue_name, @@ -614,8 +612,8 @@ async def set_complete( kwargs=job_metadata.kwargs, status=job_metadata.status, enqueue_time=job_metadata.enqueue_time, - start_time=current_datetime(microseconds=True), - finish_time=current_datetime(microseconds=True), + start_time=datetime.now(tz=UTC), + finish_time=datetime.now(tz=UTC), result=result, success=success, queue_name=queue_name, diff --git a/safir/src/safir/py.typed b/safir-arq/src/safir/arq/py.typed similarity index 100% rename from safir/src/safir/py.typed rename to safir-arq/src/safir/arq/py.typed diff --git a/safir/pyproject.toml b/safir/pyproject.toml index 5c93a0c6..e9d1e670 100644 --- a/safir/pyproject.toml +++ b/safir/pyproject.toml @@ -38,7 +38,7 @@ dynamic = ["version"] [project.optional-dependencies] arq = [ - "arq>=0.23,<1" + "safir-arq", ] db = [ "asyncpg<1", diff --git a/safir/src/safir/__init__.py b/safir/src/safir/__init__.py deleted file mode 100644 index 486c9a01..00000000 --- a/safir/src/safir/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Safir is the Rubin Observatory's library for building FastAPI services -for the Rubin Science Platform. -""" - -__all__ = ["__version__", "version_info"] - -from importlib.metadata import PackageNotFoundError, version - -__version__: str -"""The version string of Safir (PEP 440 / SemVer compatible).""" - -try: - __version__ = version(__name__) -except PackageNotFoundError: - # package is not installed - __version__ = "0.0.0" - -version_info = __version__.split(".") -"""The decomposed version, split across "``.``." - -Use this for version comparison. -""" diff --git a/safir/src/safir/asyncio.py b/safir/src/safir/asyncio/__init__.py similarity index 99% rename from safir/src/safir/asyncio.py rename to safir/src/safir/asyncio/__init__.py index 12b53338..7681e7c5 100644 --- a/safir/src/safir/asyncio.py +++ b/safir/src/safir/asyncio/__init__.py @@ -9,7 +9,7 @@ from types import EllipsisType from typing import Generic, ParamSpec, TypeVar -from .datetime import current_datetime +from safir.datetime import current_datetime #: Parameter spec for functions decorated by `run_with_asyncio`. P = ParamSpec("P") diff --git a/safir/src/safir/asyncio/py.typed b/safir/src/safir/asyncio/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/click.py b/safir/src/safir/click/__init__.py similarity index 100% rename from safir/src/safir/click.py rename to safir/src/safir/click/__init__.py diff --git a/safir/src/safir/click/py.typed b/safir/src/safir/click/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/database/py.typed b/safir/src/safir/database/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/datetime.py b/safir/src/safir/datetime/__init__.py similarity index 100% rename from safir/src/safir/datetime.py rename to safir/src/safir/datetime/__init__.py diff --git a/safir/src/safir/datetime/py.typed b/safir/src/safir/datetime/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/dependencies/arq.py b/safir/src/safir/dependencies/arq.py index 161aab53..5d1a4d6c 100644 --- a/safir/src/safir/dependencies/arq.py +++ b/safir/src/safir/dependencies/arq.py @@ -4,7 +4,7 @@ from arq.connections import RedisSettings -from ..arq import ArqMode, ArqQueue, MockArqQueue, RedisArqQueue +from safir.arq import ArqMode, ArqQueue, MockArqQueue, RedisArqQueue __all__ = ["ArqDependency", "arq_dependency"] diff --git a/safir/src/safir/dependencies/db_session.py b/safir/src/safir/dependencies/db_session.py index 845ef795..ef664b72 100644 --- a/safir/src/safir/dependencies/db_session.py +++ b/safir/src/safir/dependencies/db_session.py @@ -5,7 +5,7 @@ from pydantic import SecretStr from sqlalchemy.ext.asyncio import AsyncEngine, async_scoped_session -from ..database import create_async_session, create_database_engine +from safir.database import create_async_session, create_database_engine __all__ = ["DatabaseSessionDependency", "db_session_dependency"] diff --git a/safir/src/safir/dependencies/py.typed b/safir/src/safir/dependencies/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/fastapi.py b/safir/src/safir/fastapi/__init__.py similarity index 98% rename from safir/src/safir/fastapi.py rename to safir/src/safir/fastapi/__init__.py index ddad1bb8..26ebd611 100644 --- a/safir/src/safir/fastapi.py +++ b/safir/src/safir/fastapi/__init__.py @@ -7,8 +7,8 @@ from fastapi import Request, status from fastapi.responses import JSONResponse -from .models import ErrorLocation -from .slack.webhook import SlackIgnoredException +from safir.models import ErrorLocation +from safir.slack.webhook import SlackIgnoredException __all__ = [ "ClientRequestError", diff --git a/safir/src/safir/fastapi/py.typed b/safir/src/safir/fastapi/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/gcs.py b/safir/src/safir/gcs/__init__.py similarity index 100% rename from safir/src/safir/gcs.py rename to safir/src/safir/gcs/__init__.py diff --git a/safir/src/safir/gcs/py.typed b/safir/src/safir/gcs/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/github/py.typed b/safir/src/safir/github/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/kubernetes.py b/safir/src/safir/kubernetes/__init__.py similarity index 100% rename from safir/src/safir/kubernetes.py rename to safir/src/safir/kubernetes/__init__.py diff --git a/safir/src/safir/kubernetes/py.typed b/safir/src/safir/kubernetes/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/logging.py b/safir/src/safir/logging/__init__.py similarity index 100% rename from safir/src/safir/logging.py rename to safir/src/safir/logging/__init__.py diff --git a/safir/src/safir/logging/py.typed b/safir/src/safir/logging/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/metadata.py b/safir/src/safir/metadata/__init__.py similarity index 100% rename from safir/src/safir/metadata.py rename to safir/src/safir/metadata/__init__.py diff --git a/safir/src/safir/metadata/py.typed b/safir/src/safir/metadata/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/middleware/py.typed b/safir/src/safir/middleware/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/models.py b/safir/src/safir/models/__init__.py similarity index 100% rename from safir/src/safir/models.py rename to safir/src/safir/models/__init__.py diff --git a/safir/src/safir/models/py.typed b/safir/src/safir/models/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/pydantic.py b/safir/src/safir/pydantic/__init__.py similarity index 99% rename from safir/src/safir/pydantic.py rename to safir/src/safir/pydantic/__init__.py index 9220994b..f6daf717 100644 --- a/safir/src/safir/pydantic.py +++ b/safir/src/safir/pydantic/__init__.py @@ -16,7 +16,7 @@ ) from pydantic_core import Url -from .datetime import parse_timedelta +from safir.datetime import parse_timedelta P = ParamSpec("P") T = TypeVar("T") diff --git a/safir/src/safir/pydantic/py.typed b/safir/src/safir/pydantic/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/redis.py b/safir/src/safir/redis/__init__.py similarity index 99% rename from safir/src/safir/redis.py rename to safir/src/safir/redis/__init__.py index ee901f5e..2a6100d7 100644 --- a/safir/src/safir/redis.py +++ b/safir/src/safir/redis/__init__.py @@ -15,7 +15,7 @@ from cryptography.fernet import Fernet from pydantic import BaseModel -from .slack.blockkit import ( +from safir.slack.blockkit import ( SlackCodeBlock, SlackException, SlackMessage, diff --git a/safir/src/safir/redis/py.typed b/safir/src/safir/redis/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/slack/py.typed b/safir/src/safir/slack/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/src/safir/slack/webhook.py b/safir/src/safir/slack/webhook.py index f472c4b7..90a47d4b 100644 --- a/safir/src/safir/slack/webhook.py +++ b/safir/src/safir/slack/webhook.py @@ -12,8 +12,9 @@ from starlette.exceptions import HTTPException as StarletteHTTPException from structlog.stdlib import BoundLogger -from ..datetime import current_datetime, format_datetime_for_logging -from ..dependencies.http_client import http_client_dependency +from safir.datetime import current_datetime, format_datetime_for_logging +from safir.dependencies.http_client import http_client_dependency + from .blockkit import ( SlackCodeBlock, SlackException, diff --git a/safir/src/safir/testing/kubernetes.py b/safir/src/safir/testing/kubernetes.py index 22b15d7f..2fd82c1d 100644 --- a/safir/src/safir/testing/kubernetes.py +++ b/safir/src/safir/testing/kubernetes.py @@ -45,7 +45,7 @@ V1Status, ) -from ..asyncio import AsyncMultiQueue +from safir.asyncio import AsyncMultiQueue __all__ = [ "MockKubernetesApi", diff --git a/safir/src/safir/testing/py.typed b/safir/src/safir/testing/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/safir/tests/safir_test.py b/safir/tests/safir_test.py deleted file mode 100644 index e3a5e22e..00000000 --- a/safir/tests/safir_test.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Tests for safir, the top-level import.""" - -import safir - - -def test_version() -> None: - assert isinstance(safir.__version__, str) - assert isinstance(safir.version_info, list) From c2239b7f3c38716d395a54ad199c0aeb3e1b202d Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Tue, 23 Jul 2024 18:19:25 -0700 Subject: [PATCH 2/3] Remove Makefile Now that we've switched to nox, the rules in the Makefile are obsolete and in some cases incorrect. Delete it. --- Makefile | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 83832e4a..00000000 --- a/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -.PHONY: help -help: - @echo "Make targets for Safir:" - @echo "make clean - Remove generated files" - @echo "make init - Set up dev environment (install pre-commit hooks)" - @echo "make linkcheck - Check for broken links in documentation" - @echo "make update - Update pre-commit dependencies and run make init" - @echo "make update-deps - Update pre-commit dependencies" - -.PHONY: clean -clean: - rm -rf .tox - rm -rf docs/_build - rm -rf docs/api - -.PHONY: init -init: - pip install --upgrade uv - uv pip install --upgrade pre-commit tox tox-uv - uv pip install --upgrade -e ".[arq,db,dev,gcs,kubernetes]" - pre-commit install - rm -rf .tox - -# This is defined as a Makefile target instead of only a tox command because -# if the command fails we want to cat output.txt, which contains the -# actually useful linkcheck output. tox unfortunately doesn't support this -# level of shell trickery after failed commands. -.PHONY: linkcheck -linkcheck: - sphinx-build --keep-going -n -T -b linkcheck docs \ - docs/_build/linkcheck \ - || (cat docs/_build/linkcheck/output.txt; exit 1) - -# update and update-deps aren't that meaningful for PyPI packages that do -# not have pinned Python package dependencies, but provide the same targets -# as the FastAPI Safir app template so that people can use the same command -# to update everything at the start of development (here, just pre-commit). -.PHONY: update -update: update-deps init - -.PHONY: update-deps -update-deps: - pip install --upgrade uv - uv pip install --upgrade pre-commit - pre-commit autoupdate From 55a5bd2c72b8db5191aa6db44766c4798863c541 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Wed, 24 Jul 2024 15:59:44 -0700 Subject: [PATCH 3/3] Add change log entry for safir-arq change --- changelog.d/20240724_155805_rra_DM_45281.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/20240724_155805_rra_DM_45281.md diff --git a/changelog.d/20240724_155805_rra_DM_45281.md b/changelog.d/20240724_155805_rra_DM_45281.md new file mode 100644 index 00000000..b653816f --- /dev/null +++ b/changelog.d/20240724_155805_rra_DM_45281.md @@ -0,0 +1,3 @@ +### New features + +- `safir.arq` is now available as a separate PyPI package, `safir-arq`, so that it can be installed in environments where the full Safir dependency may be too heavy-weight or conflict with other packages. The `safir[arq]` dependency will continue to work as before (by installing `safir-arq` behind the scenes).