From 043c3a05a0ca0a0b13ea3c60379b002cdc03611d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Jun 2026 22:37:59 -0400 Subject: [PATCH 1/2] MNT: account for deprecation is pydandtic and sqlachemy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bluesky_httpserver/schemas.py:36 bluesky_httpserver/schemas.py:36: PydanticDeprecatedSince20: Pydantic V1 style `@validator` validators are deprecated. You should migrate to Pydantic V2 style `@field_validator` validators, see the migration guide for more details. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.11/migration/ @pydantic.validator("error", always=True) ../../../../.virtualenvs/sys313/lib64/python3.13/site-packages/pydantic/fields.py:1093 ../../../../.virtualenvs/sys313/lib64/python3.13/site-packages/pydantic/fields.py:1093 /home/tcaswell/.virtualenvs/sys313/lib64/python3.13/site-packages/pydantic/fields.py:1093: PydanticDeprecatedSince20: Using extra keyword arguments on `Field` is deprecated and will be removed. Use `json_schema_extra` instead. (Extra keys: 'example'). Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.11/migration/ warn( bluesky_httpserver/database/base.py:5 _httpserver/database/base.py:5: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) Base = declarative_base() -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html Aided by 🤖 --- bluesky_httpserver/database/base.py | 2 +- bluesky_httpserver/schemas.py | 14 ++++++++------ requirements.txt | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bluesky_httpserver/database/base.py b/bluesky_httpserver/database/base.py index 9da1233..f8e4cc8 100644 --- a/bluesky_httpserver/database/base.py +++ b/bluesky_httpserver/database/base.py @@ -1,4 +1,4 @@ -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import declarative_base # Everything imports this so we put it in its own module to # avoid circular imports. diff --git a/bluesky_httpserver/schemas.py b/bluesky_httpserver/schemas.py index c52d8f2..db323b4 100644 --- a/bluesky_httpserver/schemas.py +++ b/bluesky_httpserver/schemas.py @@ -33,9 +33,11 @@ class Response(generic_model, Generic[DataT, LinksT, MetaT]): links: Optional[LinksT] = None meta: Optional[MetaT] = None - @pydantic.validator("error", always=True) - def check_consistency(cls, v, values): - if v is not None and values["data"] is not None: + @pydantic.field_validator("error", mode="after") + @classmethod + def check_consistency(cls, v, info): + values = info.data + if v is not None and values.get("data") is not None: raise ValueError("must not provide both data and error") if v is None and values.get("data") is None: raise ValueError("must provide data or error") @@ -291,7 +293,7 @@ class APIKeyRequestParams(pydantic.BaseModel): # Provide an example for expires_in. Otherwise, OpenAPI suggests lifetime=0. # If the user is not reading carefully, they will be frustrated when they # try to use the instantly-expiring API key! - expires_in: Optional[int] = pydantic.Field(..., example=600) # seconds - # scopes: Optional[List[str]] = pydantic.Field(..., example=["inherit"]) - scopes: Optional[List[str]] = pydantic.Field(default=["inherit"], example=["inherit"]) + expires_in: Optional[int] = pydantic.Field(..., json_schema_extra={"example": 600}) # seconds + # scopes: Optional[List[str]] = pydantic.Field(..., json_schema_extra={"example": ["inherit"]}) + scopes: Optional[List[str]] = pydantic.Field(default=["inherit"], json_schema_extra={"example": ["inherit"]}) note: Optional[str] = None diff --git a/requirements.txt b/requirements.txt index f465abd..c0a9526 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,11 +5,11 @@ fastapi ldap3 orjson pamela -pydantic +pydantic>2 pydantic-settings python-jose pyzmq -sqlalchemy +sqlalchemy>2 starlette typing-extensions;python_version<'3.8' uvicorn From b776fc6a7e4f229bdd1b8f0699e43d859f35bbd9 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Jun 2026 22:40:33 -0400 Subject: [PATCH 2/2] MNT: move to httpx2 from httpx pydantic has forked upstream https://github.com/pydantic/httpx2 and is planning to maintain it going forward. --- bluesky_httpserver/authenticators.py | 8 ++++---- bluesky_httpserver/authorization/api_access.py | 4 ++-- requirements.txt | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bluesky_httpserver/authenticators.py b/bluesky_httpserver/authenticators.py index 61c2da4..b270035 100644 --- a/bluesky_httpserver/authenticators.py +++ b/bluesky_httpserver/authenticators.py @@ -221,11 +221,11 @@ async def exchange_code(token_uri, auth_code, client_id, client_secret, redirect token_url ([type]): [description] auth_code ([type]): [description] """ - if not modules_available("httpx"): - raise ModuleNotFoundError("This authenticator requires 'httpx'. (pip install httpx)") - import httpx + if not modules_available("httpx2"): + raise ModuleNotFoundError("This authenticator requires 'httpx2'. (pip install httpx2)") + import httpx2 - response = httpx.post( + response = httpx2.post( url=token_uri, data={ "grant_type": "authorization_code", diff --git a/bluesky_httpserver/authorization/api_access.py b/bluesky_httpserver/authorization/api_access.py index 54263d9..55a3fd1 100644 --- a/bluesky_httpserver/authorization/api_access.py +++ b/bluesky_httpserver/authorization/api_access.py @@ -5,7 +5,7 @@ import time as ttime from collections.abc import Iterable -import httpx +import httpx2 import jsonschema import yaml @@ -610,7 +610,7 @@ async def update_access_info(self): Send a single request to the API server and update locally stored access control info. """ access_api = f"/instrument/{self._instrument.lower()}/qserver/access" - async with httpx.AsyncClient(base_url=self._base_url, timeout=self._http_timeout) as client: + async with httpx2.AsyncClient(base_url=self._base_url, timeout=self._http_timeout) as client: response = await client.get(access_api) response.raise_for_status() groups = response.json() diff --git a/requirements.txt b/requirements.txt index c0a9526..033186a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ alembic bluesky-queueserver bluesky-queueserver-api fastapi +httpx2 ldap3 orjson pamela