From 236765db6472ce9cee11a38bbc690c2491d82756 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Fri, 30 Aug 2024 15:16:43 +0100 Subject: [PATCH 1/3] Fix new B039 error in flake8-bugbear 24.8.19 See: https://github.com/PyCQA/flake8-bugbear/releases/tag/24.8.19 https://docs.astral.sh/ruff/rules/mutable-contextvar-default/ --- lib/galaxy/dependencies/pinned-lint-requirements.txt | 10 +++++----- lib/galaxy/model/base.py | 7 +++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/dependencies/pinned-lint-requirements.txt b/lib/galaxy/dependencies/pinned-lint-requirements.txt index 6aa97f2ab26d..c043874bc5bc 100644 --- a/lib/galaxy/dependencies/pinned-lint-requirements.txt +++ b/lib/galaxy/dependencies/pinned-lint-requirements.txt @@ -1,7 +1,7 @@ -attrs==23.2.0 -flake8==7.1.0 -flake8-bugbear==24.4.26 +attrs==24.2.0 +flake8==7.1.1 +flake8-bugbear==24.8.19 mccabe==0.7.0 -pycodestyle==2.12.0 +pycodestyle==2.12.1 pyflakes==3.2.0 -ruff==0.6.1 +ruff==0.6.3 diff --git a/lib/galaxy/model/base.py b/lib/galaxy/model/base.py index db82a2f9ca78..e1243dda67f5 100644 --- a/lib/galaxy/model/base.py +++ b/lib/galaxy/model/base.py @@ -38,8 +38,7 @@ # of a request (which run within a threadpool) to see changes to the ContextVar # state. See https://github.com/tiangolo/fastapi/issues/953#issuecomment-586006249 # for details -_request_state: Dict[str, str] = {} -REQUEST_ID = ContextVar("request_id", default=_request_state.copy()) +REQUEST_ID: ContextVar[Union[Dict[str, str], None]] = ContextVar("request_id", default=None) @contextlib.contextmanager @@ -112,12 +111,12 @@ def new_session(self): def request_scopefunc(self): """ - Return a value that is used as dictionary key for sqlalchemy's ScopedRegistry. + Return a value that is used as dictionary key for SQLAlchemy's ScopedRegistry. This ensures that threads or request contexts will receive a single identical session from the ScopedRegistry. """ - return REQUEST_ID.get().get("request") or threading.get_ident() + return REQUEST_ID.get({}).get("request") or threading.get_ident() @staticmethod def set_request_id(request_id): From fdb61acfdfd036f79dd599c0836c9ece08bfaaf3 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Fri, 30 Aug 2024 15:18:51 +0100 Subject: [PATCH 2/3] Fix DeprecationWarning from httpx Fix the following warning when running unit tests: ``` test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py::test_request_scoped_sa_session_single_request test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py::test_request_scoped_sa_session_exception test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py::test_request_scoped_sa_session_concurrent_requests_sync test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py::test_request_scoped_sa_session_concurrent_requests_async test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py::test_request_scoped_sa_session_concurrent_requests_and_background_thread /usr/users/ga002/soranzon/software/nsoranzo_galaxy/.venv/lib/python3.10/site-packages/httpx/_client.py:1426: DeprecationWarning: The 'app' shortcut is now deprecated. Use the explicit style 'transport=ASGITransport(app=...)' instead. ``` See: https://github.com/encode/httpx/blob/master/CHANGELOG.md#0270-21st-february-2024 --- .../test_request_scoped_sqlalchemy_sessions.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py b/test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py index 31d90e28a129..fbe4fd9cae6d 100644 --- a/test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py +++ b/test/unit/webapps/test_request_scoped_sqlalchemy_sessions.py @@ -8,7 +8,10 @@ import pytest from fastapi import FastAPI from fastapi.param_functions import Depends -from httpx import AsyncClient +from httpx import ( + ASGITransport, + AsyncClient, +) from starlette_context import context as request_context from galaxy.app_unittest_utils.galaxy_mock import MockApp @@ -16,6 +19,7 @@ app = FastAPI() add_request_id_middleware(app) +transport = ASGITransport(app=app) GX_APP = None @@ -96,7 +100,7 @@ def assert_scoped_session_is_thread_local(gx_app): @pytest.mark.asyncio async def test_request_scoped_sa_session_single_request(): - async with AsyncClient(app=app, base_url="http://test") as client: + async with AsyncClient(base_url="http://test", transport=transport) as client: response = await client.get("/") assert response.status_code == 200 assert response.json() == {"msg": "Hello World"} @@ -106,7 +110,7 @@ async def test_request_scoped_sa_session_single_request(): @pytest.mark.asyncio async def test_request_scoped_sa_session_exception(): - async with AsyncClient(app=app, base_url="http://test") as client: + async with AsyncClient(base_url="http://test", transport=transport) as client: with pytest.raises(UnexpectedException): await client.get("/internal_server_error") assert GX_APP @@ -115,7 +119,7 @@ async def test_request_scoped_sa_session_exception(): @pytest.mark.asyncio async def test_request_scoped_sa_session_concurrent_requests_sync(): - async with AsyncClient(app=app, base_url="http://test") as client: + async with AsyncClient(base_url="http://test", transport=transport) as client: awaitables = (client.get("/sync_wait") for _ in range(10)) result = await asyncio.gather(*awaitables) uuids = [] @@ -129,7 +133,7 @@ async def test_request_scoped_sa_session_concurrent_requests_sync(): @pytest.mark.asyncio async def test_request_scoped_sa_session_concurrent_requests_async(): - async with AsyncClient(app=app, base_url="http://test") as client: + async with AsyncClient(base_url="http://test", transport=transport) as client: awaitables = (client.get("/async_wait") for _ in range(10)) result = await asyncio.gather(*awaitables) uuids = [] @@ -147,7 +151,7 @@ async def test_request_scoped_sa_session_concurrent_requests_and_background_thre target = functools.partial(assert_scoped_session_is_thread_local, GX_APP) with concurrent.futures.ThreadPoolExecutor() as pool: background_pool = loop.run_in_executor(pool, target) - async with AsyncClient(app=app, base_url="http://test") as client: + async with AsyncClient(base_url="http://test", transport=transport) as client: awaitables = (client.get("/async_wait") for _ in range(10)) result = await asyncio.gather(*awaitables) uuids = [] From be29ccac6a1d675e0271c9da124ef236e4c95a6a Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Fri, 30 Aug 2024 18:16:28 +0100 Subject: [PATCH 3/3] Temporarily ignore 2 mypy type-var errors caused by the more precise type annotation of ``numpy.number`` comparison operators introduced in NumPy 2.1, see: https://github.com/python/typeshed/issues/12562 Fix the following errors when running test_galaxy_packages on Python >=3.10: ``` galaxy/tool_util/verify/__init__.py:503: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "floating[Any]" [type-var] iou_list.append(max(cc1_iou_list)) ^~~~~~~~~~~~~~~~~ galaxy/tool_util/verify/__init__.py:532: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "floating[Any]" [type-var] return min(_multiobject_intersection_over_union(mask1, mask2, pin_labels)) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` --- lib/galaxy/tool_util/verify/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/tool_util/verify/__init__.py b/lib/galaxy/tool_util/verify/__init__.py index 4096c4c9d865..fdc4c2044428 100644 --- a/lib/galaxy/tool_util/verify/__init__.py +++ b/lib/galaxy/tool_util/verify/__init__.py @@ -471,8 +471,8 @@ def files_contains(file1, file2, attributes=None): def _singleobject_intersection_over_union( - mask1: "numpy.typing.NDArray", - mask2: "numpy.typing.NDArray", + mask1: "numpy.typing.NDArray[numpy.bool_]", + mask2: "numpy.typing.NDArray[numpy.bool_]", ) -> "numpy.floating": return numpy.logical_and(mask1, mask2).sum() / numpy.logical_or(mask1, mask2).sum() @@ -483,7 +483,7 @@ def _multiobject_intersection_over_union( pin_labels: Optional[List[int]] = None, repeat_reverse: bool = True, ) -> List["numpy.floating"]: - iou_list = [] + iou_list: List[numpy.floating] = [] for label1 in numpy.unique(mask1): cc1 = mask1 == label1 @@ -494,13 +494,13 @@ def _multiobject_intersection_over_union( # Otherwise, use the object with the largest IoU value, excluding the pinned labels. else: - cc1_iou_list = [] + cc1_iou_list: List[numpy.floating] = [] for label2 in numpy.unique(mask2[cc1]): if pin_labels is not None and label2 in pin_labels: continue cc2 = mask2 == label2 cc1_iou_list.append(_singleobject_intersection_over_union(cc1, cc2)) - iou_list.append(max(cc1_iou_list)) + iou_list.append(max(cc1_iou_list)) # type: ignore[type-var, unused-ignore] # https://github.com/python/typeshed/issues/12562 if repeat_reverse: iou_list.extend(_multiobject_intersection_over_union(mask2, mask1, pin_labels, repeat_reverse=False)) @@ -511,7 +511,7 @@ def _multiobject_intersection_over_union( def intersection_over_union( mask1: "numpy.typing.NDArray", mask2: "numpy.typing.NDArray", pin_labels: Optional[List[int]] = None ) -> "numpy.floating": - """Compute the intersection over union (IoU) for the objects in two masks containing lables. + """Compute the intersection over union (IoU) for the objects in two masks containing labels. The IoU is computed for each uniquely labeled image region (object), and the overall minimum value is returned (i.e. the worst value). To compute the IoU for each object, the corresponding object in the other mask needs to be determined. @@ -529,7 +529,7 @@ def intersection_over_union( count = sum(label in mask for mask in (mask1, mask2)) count_str = {1: "one", 2: "both"} assert count == 2, f"Label {label} is pinned but missing in {count_str[2 - count]} of the images." - return min(_multiobject_intersection_over_union(mask1, mask2, pin_labels)) + return min(_multiobject_intersection_over_union(mask1, mask2, pin_labels)) # type: ignore[type-var, unused-ignore] # https://github.com/python/typeshed/issues/12562 def _parse_label_list(label_list_str: Optional[str]) -> List[int]: