From 6ac852cae463dfd9d9ccdc4e89a6ebb438ca78b4 Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Mon, 12 Jan 2026 13:03:19 -0600 Subject: [PATCH 1/8] Handle edge case where Form Extraction bounding boxes are floats --- indico_toolkit/results/normalization.py | 7 +++++++ tests/data/results/classify_extract_unreviewed.json | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/indico_toolkit/results/normalization.py b/indico_toolkit/results/normalization.py index 72cec52..b713c6c 100644 --- a/indico_toolkit/results/normalization.py +++ b/indico_toolkit/results/normalization.py @@ -54,6 +54,13 @@ def normalize_prediction_dict(task_type: TaskType, prediction: "Any") -> None: ) and not has(prediction, list, "spans"): prediction["spans"] = [] + # Form Extraction bounding boxes may very rarely have a `.0` decimal place, + # which causes them to fail strict validation. + if task_type == TaskType.FORM_EXTRACTION: + for edge in ("top", "left", "right", "bottom"): + if has(prediction, float, edge): + prediction[edge] = int(prediction[edge]) + # Form Extractions added in review may lack bounding box information. # These values will match `NULL_BOX`. if task_type == TaskType.FORM_EXTRACTION and not has(prediction, int, "top"): diff --git a/tests/data/results/classify_extract_unreviewed.json b/tests/data/results/classify_extract_unreviewed.json index 3652076..8c4b01b 100644 --- a/tests/data/results/classify_extract_unreviewed.json +++ b/tests/data/results/classify_extract_unreviewed.json @@ -175,7 +175,7 @@ }, "field_id": 876470, "location_type": "exact", - "top": 2352, + "top": 2352.0, "bottom": 2398, "left": 206, "right": 264, @@ -212,7 +212,7 @@ "field_id": 876471, "location_type": "exact", "top": 2401, - "bottom": 2446, + "bottom": 2446.0, "left": 207, "right": 261, "page_num": 0, @@ -249,7 +249,7 @@ "location_type": "exact", "top": 2351, "bottom": 2395, - "left": 416, + "left": 416.0, "right": 472, "page_num": 0, "checked": true, @@ -286,7 +286,7 @@ "top": 2399, "bottom": 2448, "left": 416, - "right": 473, + "right": 473.0, "page_num": 0, "checked": false, "type": "checkbox", From ba123a3ca3b2d275a0ad0a04a6b6396c99c03f1a Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Mon, 12 Jan 2026 15:02:52 -0600 Subject: [PATCH 2/8] Update filters for `AutoReviewPoller` and `DownstreamPoller` to skip deleted and retrieved submissions --- indico_toolkit/polling/queries.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/indico_toolkit/polling/queries.py b/indico_toolkit/polling/queries.py index e67c257..8618a03 100644 --- a/indico_toolkit/polling/queries.py +++ b/indico_toolkit/polling/queries.py @@ -11,7 +11,13 @@ class SubmissionIdsPendingAutoReview(GraphQLRequest): # type: ignore[misc, no-a query SubmissionIdsPendingAutoReview($workflowIds: [Int]) { submissions( desc: false - filters: { status: PENDING_AUTO_REVIEW } + filters: { + AND: [ + { status: PENDING_AUTO_REVIEW } + { filesDeleted: false } + { retrieved: false } + ] + } limit: 1000 orderBy: ID workflowIds: $workflowIds @@ -39,13 +45,16 @@ class SubmissionIdsPendingDownstream(GraphQLRequest): # type: ignore[misc, no-a submissions( desc: false filters: { - AND: { - retrieved: false - OR: [ - { status: COMPLETE } - { status: FAILED } - ] - } + AND: [ + { + OR: [ + { status: COMPLETE } + { status: FAILED } + ] + } + { filesDeleted: false } + { retrieved: false } + ] } limit: 1000 orderBy: ID From 72c848829a3c367d6f28a56b1a2cc56d6ca32559 Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Thu, 22 Jan 2026 10:36:02 -0600 Subject: [PATCH 3/8] Merge `retry()`'s `InnerReturnType` and `OuterReturnType` --- indico_toolkit/retry.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/indico_toolkit/retry.py b/indico_toolkit/retry.py index 4acd9c4..836387e 100644 --- a/indico_toolkit/retry.py +++ b/indico_toolkit/retry.py @@ -10,8 +10,7 @@ from typing import ParamSpec, TypeVar ArgumentsType = ParamSpec("ArgumentsType") - OuterReturnType = TypeVar("OuterReturnType") - InnerReturnType = TypeVar("InnerReturnType") + ReturnType = TypeVar("ReturnType") def retry( @@ -20,7 +19,7 @@ def retry( wait: float = 1, backoff: float = 4, jitter: float = 0.5, -) -> "Callable[[Callable[ArgumentsType, OuterReturnType]], Callable[ArgumentsType, OuterReturnType]]": # noqa: E501 +) -> "Callable[[Callable[ArgumentsType, ReturnType]], Callable[ArgumentsType, ReturnType]]": # noqa: E501 """ Decorate a function or coroutine to retry when it raises specified errors, apply exponential backoff and jitter to the wait time, @@ -46,15 +45,15 @@ def wait_time(times_retried: int) -> float: @overload def retry_decorator( - decorated: "Callable[ArgumentsType, Awaitable[InnerReturnType]]", - ) -> "Callable[ArgumentsType, Awaitable[InnerReturnType]]": ... + decorated: "Callable[ArgumentsType, Awaitable[ReturnType]]", + ) -> "Callable[ArgumentsType, Awaitable[ReturnType]]": ... @overload def retry_decorator( - decorated: "Callable[ArgumentsType, InnerReturnType]", - ) -> "Callable[ArgumentsType, InnerReturnType]": ... + decorated: "Callable[ArgumentsType, ReturnType]", + ) -> "Callable[ArgumentsType, ReturnType]": ... def retry_decorator( - decorated: "Callable[ArgumentsType, InnerReturnType]", - ) -> "Callable[ArgumentsType, Awaitable[InnerReturnType]] | Callable[ArgumentsType, InnerReturnType]": # noqa: E501 + decorated: "Callable[ArgumentsType, ReturnType]", + ) -> "Callable[ArgumentsType, Awaitable[ReturnType]] | Callable[ArgumentsType, ReturnType]": # noqa: E501 """ Decorate either a function or coroutine as appropriate. """ @@ -63,7 +62,7 @@ def retry_decorator( @wraps(decorated) async def retrying_coroutine( # type: ignore[return] *args: "ArgumentsType.args", **kwargs: "ArgumentsType.kwargs" - ) -> "InnerReturnType": + ) -> "ReturnType": for times_retried in range(count + 1): try: return await decorated(*args, **kwargs) # type: ignore[no-any-return] @@ -79,7 +78,7 @@ async def retrying_coroutine( # type: ignore[return] @wraps(decorated) def retrying_function( # type: ignore[return] *args: "ArgumentsType.args", **kwargs: "ArgumentsType.kwargs" - ) -> "InnerReturnType": + ) -> "ReturnType": for times_retried in range(count + 1): try: return decorated(*args, **kwargs) From 0ab81424b500f06955d2c7214f5aa0b005d9faea Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Fri, 23 Jan 2026 08:51:11 -0600 Subject: [PATCH 4/8] Replace `Prediction.copy()` with `copy.deepcopy()` via `__deepcopy__` --- .../results/predictions/documentextraction.py | 29 ++++++++++--------- .../results/predictions/prediction.py | 18 +++++++----- .../results/predictions/summarization.py | 18 ++++++------ .../results/predictions/unbundling.py | 20 ++++++------- indico_toolkit/results/result.py | 13 ++++++++- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/indico_toolkit/results/predictions/documentextraction.py b/indico_toolkit/results/predictions/documentextraction.py index c942ab7..94d1f0b 100644 --- a/indico_toolkit/results/predictions/documentextraction.py +++ b/indico_toolkit/results/predictions/documentextraction.py @@ -1,5 +1,5 @@ -from copy import copy, deepcopy -from dataclasses import dataclass, field, replace +from copy import copy +from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any from ...etloutput import ( @@ -131,6 +131,19 @@ def table_cells(self, table_cells: "Iterable[tuple[Table, Cell]]") -> None: self.tables.append(table) self.cells.append(cell) + def __deepcopy__(self, memo: Any) -> "Self": + """ + Supports `copy.deepcopy(prediction)` without copying immutable objects. + This provides a significant time and memory improvement when OCR is assigned. + """ + new_instance = super().__deepcopy__(memo) + new_instance.groups = copy(self.groups) + new_instance.spans = copy(self.spans) + new_instance.tokens = copy(self.tokens) + new_instance.tables = copy(self.tables) + new_instance.cells = copy(self.cells) + return new_instance + @staticmethod def from_dict( document: "Document", @@ -190,15 +203,3 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction - - def copy(self) -> "Self": - return replace( - self, - groups=copy(self.groups), - spans=copy(self.spans), - tokens=copy(self.tokens), - tables=copy(self.tables), - cells=copy(self.cells), - confidences=copy(self.confidences), - extras=deepcopy(self.extras), - ) diff --git a/indico_toolkit/results/predictions/prediction.py b/indico_toolkit/results/predictions/prediction.py index 6edf03c..869addb 100644 --- a/indico_toolkit/results/predictions/prediction.py +++ b/indico_toolkit/results/predictions/prediction.py @@ -1,5 +1,5 @@ from copy import copy, deepcopy -from dataclasses import dataclass, replace +from dataclasses import dataclass from typing import TYPE_CHECKING, Any if TYPE_CHECKING: @@ -28,15 +28,17 @@ def confidence(self) -> float: def confidence(self, value: float) -> None: self.confidences[self.label] = value + def __deepcopy__(self, memo: Any) -> "Self": + """ + Supports `copy.deepcopy(prediction)` without copying immutable objects. + """ + new_instance = copy(self) + new_instance.confidences = copy(self.confidences) + new_instance.extras = deepcopy(self.extras, memo) + return new_instance + def to_dict(self) -> "dict[str, Any]": """ Create a prediction dictionary for auto review changes. """ raise NotImplementedError() - - def copy(self) -> "Self": - return replace( - self, - confidences=copy(self.confidences), - extras=deepcopy(self.extras), - ) diff --git a/indico_toolkit/results/predictions/summarization.py b/indico_toolkit/results/predictions/summarization.py index 6f2fa3e..fa3f677 100644 --- a/indico_toolkit/results/predictions/summarization.py +++ b/indico_toolkit/results/predictions/summarization.py @@ -1,4 +1,4 @@ -from copy import copy, deepcopy +from copy import copy from dataclasses import dataclass, replace from typing import TYPE_CHECKING, Any @@ -70,6 +70,14 @@ def span(self, span: "Span") -> None: """ self.citation = replace(self.citation, span=span) + def __deepcopy__(self, memo: Any) -> "Self": + """ + Supports `copy.deepcopy(prediction)` without copying immutable objects. + """ + new_instance = super().__deepcopy__(memo) + new_instance.citations = copy(self.citations) + return new_instance + @staticmethod def from_dict( document: "Document", @@ -125,11 +133,3 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction - - def copy(self) -> "Self": - return replace( - self, - citations=copy(self.citations), - confidences=copy(self.confidences), - extras=deepcopy(self.extras), - ) diff --git a/indico_toolkit/results/predictions/unbundling.py b/indico_toolkit/results/predictions/unbundling.py index 7e998dd..242cad2 100644 --- a/indico_toolkit/results/predictions/unbundling.py +++ b/indico_toolkit/results/predictions/unbundling.py @@ -1,5 +1,5 @@ -from copy import copy, deepcopy -from dataclasses import dataclass, replace +from copy import copy +from dataclasses import dataclass from typing import TYPE_CHECKING, Any from ...etloutput import Span @@ -25,6 +25,14 @@ def pages(self) -> "tuple[int, ...]": """ return tuple(span.page for span in self.spans) + def __deepcopy__(self, memo: Any) -> "Self": + """ + Supports `copy.deepcopy(prediction)` without copying immutable objects. + """ + new_instance = super().__deepcopy__(memo) + new_instance.spans = copy(self.spans) + return new_instance + @staticmethod def from_dict( document: "Document", @@ -55,11 +63,3 @@ def to_dict(self) -> "dict[str, Any]": "confidence": self.confidences, "spans": [span.to_dict() for span in self.spans], } - - def copy(self) -> "Self": - return replace( - self, - spans=copy(self.spans), - confidences=copy(self.confidences), - extras=deepcopy(self.extras), - ) diff --git a/indico_toolkit/results/result.py b/indico_toolkit/results/result.py index cb55259..73459ed 100644 --- a/indico_toolkit/results/result.py +++ b/indico_toolkit/results/result.py @@ -1,6 +1,8 @@ -from dataclasses import dataclass +from copy import deepcopy +from dataclasses import dataclass, replace from functools import partial from itertools import chain +from typing import TYPE_CHECKING, Any from . import predictions as prediction from .document import Document @@ -11,6 +13,9 @@ from .task import Task from .utils import get +if TYPE_CHECKING: + from typing_extensions import Self + @dataclass(frozen=True, order=True) class Result: @@ -44,6 +49,12 @@ def admin_review(self) -> "PredictionList[Prediction]": def final(self) -> "PredictionList[Prediction]": return self.predictions.where(review=self.reviews[-1] if self.reviews else None) + def __deepcopy__(self, memo: Any) -> "Self": + """ + Supports `copy.deepcopy(result)` without copying immutable objects. + """ + return replace(self, predictions=deepcopy(self.predictions, memo)) + @staticmethod def from_dict(result: object) -> "Result": """ From af42845adc82515924bbf342739f3e4da1cbe55f Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Fri, 23 Jan 2026 08:59:30 -0600 Subject: [PATCH 5/8] Support `copy.replace(prediction, **attrs)` including properties --- .../results/predictions/classification.py | 4 +++ .../results/predictions/documentextraction.py | 4 +++ .../results/predictions/extraction.py | 4 +++ .../results/predictions/formextraction.py | 4 +++ .../results/predictions/prediction.py | 26 +++++++++++++++++++ .../results/predictions/summarization.py | 4 +++ .../results/predictions/unbundling.py | 4 +++ 7 files changed, 50 insertions(+) diff --git a/indico_toolkit/results/predictions/classification.py b/indico_toolkit/results/predictions/classification.py index b76b17e..667cb5a 100644 --- a/indico_toolkit/results/predictions/classification.py +++ b/indico_toolkit/results/predictions/classification.py @@ -40,3 +40,7 @@ def to_dict(self) -> "dict[str, Any]": "label": self.label, "confidence": self.confidences, } + + +# Unshadow `Prediction.__replace__`. +del Classification.__replace__ diff --git a/indico_toolkit/results/predictions/documentextraction.py b/indico_toolkit/results/predictions/documentextraction.py index 94d1f0b..d0cfbeb 100644 --- a/indico_toolkit/results/predictions/documentextraction.py +++ b/indico_toolkit/results/predictions/documentextraction.py @@ -203,3 +203,7 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction + + +# Unshadow `Prediction.__replace__`. +del DocumentExtraction.__replace__ diff --git a/indico_toolkit/results/predictions/extraction.py b/indico_toolkit/results/predictions/extraction.py index 3e5c1d8..16e5cce 100644 --- a/indico_toolkit/results/predictions/extraction.py +++ b/indico_toolkit/results/predictions/extraction.py @@ -33,3 +33,7 @@ def reject(self) -> None: def unreject(self) -> None: self.rejected = False + + +# Unshadow `Prediction.__replace__`. +del Extraction.__replace__ diff --git a/indico_toolkit/results/predictions/formextraction.py b/indico_toolkit/results/predictions/formextraction.py index daa4124..391459f 100644 --- a/indico_toolkit/results/predictions/formextraction.py +++ b/indico_toolkit/results/predictions/formextraction.py @@ -117,3 +117,7 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction + + +# Unshadow `Prediction.__replace__`. +del FormExtraction.__replace__ diff --git a/indico_toolkit/results/predictions/prediction.py b/indico_toolkit/results/predictions/prediction.py index 869addb..081d809 100644 --- a/indico_toolkit/results/predictions/prediction.py +++ b/indico_toolkit/results/predictions/prediction.py @@ -37,8 +37,34 @@ def __deepcopy__(self, memo: Any) -> "Self": new_instance.extras = deepcopy(self.extras, memo) return new_instance + def __replace__override__(self, **attributes: Any) -> "Self": + """ + Supports `copy.replace(prediction, **attrs)` on Python 3.13+ + + Unlike `dataclasses.replace(**attrs)` this performs a deep copy and allows + assigning properties in addition to attributes. + + E.g. + >>> dataclasses.replace(prediction, confidence=1.0) + Shallow copy and raises TypeError(...) + >>> copy.replace(prediction, confidence=1.0) + Deep copy and returns Prediction(confidence=1.0, ...) + """ + new_instance = deepcopy(self) + + for attribute, value in attributes.items(): + setattr(new_instance, attribute, value) + + return new_instance + def to_dict(self) -> "dict[str, Any]": """ Create a prediction dictionary for auto review changes. """ raise NotImplementedError() + + +# `dataclass()` doesn't (yet) provide a way to override the generated `__replace__`. +# It must be overridden after class generation and unshadowed on all subclasses. +Prediction.__replace__ = Prediction.__replace__override__ # type:ignore[method-assign] +del Prediction.__replace__override__ diff --git a/indico_toolkit/results/predictions/summarization.py b/indico_toolkit/results/predictions/summarization.py index fa3f677..b198fce 100644 --- a/indico_toolkit/results/predictions/summarization.py +++ b/indico_toolkit/results/predictions/summarization.py @@ -133,3 +133,7 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction + + +# Unshadow `Prediction.__replace__`. +del Summarization.__replace__ diff --git a/indico_toolkit/results/predictions/unbundling.py b/indico_toolkit/results/predictions/unbundling.py index 242cad2..269b152 100644 --- a/indico_toolkit/results/predictions/unbundling.py +++ b/indico_toolkit/results/predictions/unbundling.py @@ -63,3 +63,7 @@ def to_dict(self) -> "dict[str, Any]": "confidence": self.confidences, "spans": [span.to_dict() for span in self.spans], } + + +# Unshadow `Prediction.__replace__`. +del Unbundling.__replace__ From 652efe2e8eb80d6ae91ccab80398c8b2dcf974df Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Mon, 26 Jan 2026 09:19:29 -0600 Subject: [PATCH 6/8] Fix `__replace__` handling on Python <=3.12 --- indico_toolkit/results/predictions/__init__.py | 12 ++++++++++++ indico_toolkit/results/predictions/classification.py | 4 ---- .../results/predictions/documentextraction.py | 4 ---- indico_toolkit/results/predictions/extraction.py | 4 ---- indico_toolkit/results/predictions/formextraction.py | 4 ---- indico_toolkit/results/predictions/prediction.py | 7 ++++--- indico_toolkit/results/predictions/summarization.py | 4 ---- indico_toolkit/results/predictions/unbundling.py | 4 ---- 8 files changed, 16 insertions(+), 27 deletions(-) diff --git a/indico_toolkit/results/predictions/__init__.py b/indico_toolkit/results/predictions/__init__.py index dff7d51..555cf5c 100644 --- a/indico_toolkit/results/predictions/__init__.py +++ b/indico_toolkit/results/predictions/__init__.py @@ -1,3 +1,4 @@ +import sys from typing import TYPE_CHECKING from ..normalization import normalize_prediction_dict @@ -55,3 +56,14 @@ def from_dict( return Unbundling.from_dict(document, task, review, prediction) else: raise ValueError(f"unsupported task type {task.type!r}") + + +# `dataclass()` doesn't (yet) provide a way to configure the generated `__replace__` +# method on Python 3.13+. Unshadow `Prediction.__replace__` in generated subclasses. +if sys.version_info >= (3, 13): + del Classification.__replace__ + del DocumentExtraction.__replace__ + del Extraction.__replace__ + del FormExtraction.__replace__ + del Summarization.__replace__ + del Unbundling.__replace__ diff --git a/indico_toolkit/results/predictions/classification.py b/indico_toolkit/results/predictions/classification.py index 667cb5a..b76b17e 100644 --- a/indico_toolkit/results/predictions/classification.py +++ b/indico_toolkit/results/predictions/classification.py @@ -40,7 +40,3 @@ def to_dict(self) -> "dict[str, Any]": "label": self.label, "confidence": self.confidences, } - - -# Unshadow `Prediction.__replace__`. -del Classification.__replace__ diff --git a/indico_toolkit/results/predictions/documentextraction.py b/indico_toolkit/results/predictions/documentextraction.py index d0cfbeb..94d1f0b 100644 --- a/indico_toolkit/results/predictions/documentextraction.py +++ b/indico_toolkit/results/predictions/documentextraction.py @@ -203,7 +203,3 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction - - -# Unshadow `Prediction.__replace__`. -del DocumentExtraction.__replace__ diff --git a/indico_toolkit/results/predictions/extraction.py b/indico_toolkit/results/predictions/extraction.py index 16e5cce..3e5c1d8 100644 --- a/indico_toolkit/results/predictions/extraction.py +++ b/indico_toolkit/results/predictions/extraction.py @@ -33,7 +33,3 @@ def reject(self) -> None: def unreject(self) -> None: self.rejected = False - - -# Unshadow `Prediction.__replace__`. -del Extraction.__replace__ diff --git a/indico_toolkit/results/predictions/formextraction.py b/indico_toolkit/results/predictions/formextraction.py index 391459f..daa4124 100644 --- a/indico_toolkit/results/predictions/formextraction.py +++ b/indico_toolkit/results/predictions/formextraction.py @@ -117,7 +117,3 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction - - -# Unshadow `Prediction.__replace__`. -del FormExtraction.__replace__ diff --git a/indico_toolkit/results/predictions/prediction.py b/indico_toolkit/results/predictions/prediction.py index 081d809..87db94f 100644 --- a/indico_toolkit/results/predictions/prediction.py +++ b/indico_toolkit/results/predictions/prediction.py @@ -64,7 +64,8 @@ def to_dict(self) -> "dict[str, Any]": raise NotImplementedError() -# `dataclass()` doesn't (yet) provide a way to override the generated `__replace__`. -# It must be overridden after class generation and unshadowed on all subclasses. -Prediction.__replace__ = Prediction.__replace__override__ # type:ignore[method-assign] +# `dataclass()` doesn't (yet) provide a way to override the generated `__replace__` +# method on Python 3.13+. It must be overridden after class generation and unshadowed +# on all derived classes. +Prediction.__replace__ = Prediction.__replace__override__ # type:ignore del Prediction.__replace__override__ diff --git a/indico_toolkit/results/predictions/summarization.py b/indico_toolkit/results/predictions/summarization.py index b198fce..fa3f677 100644 --- a/indico_toolkit/results/predictions/summarization.py +++ b/indico_toolkit/results/predictions/summarization.py @@ -133,7 +133,3 @@ def to_dict(self) -> "dict[str, Any]": prediction["rejected"] = True return prediction - - -# Unshadow `Prediction.__replace__`. -del Summarization.__replace__ diff --git a/indico_toolkit/results/predictions/unbundling.py b/indico_toolkit/results/predictions/unbundling.py index 269b152..242cad2 100644 --- a/indico_toolkit/results/predictions/unbundling.py +++ b/indico_toolkit/results/predictions/unbundling.py @@ -63,7 +63,3 @@ def to_dict(self) -> "dict[str, Any]": "confidence": self.confidences, "spans": [span.to_dict() for span in self.spans], } - - -# Unshadow `Prediction.__replace__`. -del Unbundling.__replace__ From 6d04245aaded15e5447b2c54f98f0ca965a7c80e Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Fri, 30 Jan 2026 08:52:04 -0600 Subject: [PATCH 7/8] Update readme, bump version, and lock --- CHANGELOG.md | 25 ++++++++++++++++++++++++- indico_toolkit/__init__.py | 2 +- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f502a1..0dd7490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions match the minimum IPA version required to use functionality. +## [v7.2.3] - 2026-01-30 + +### Added + +- Filter out deleted and retrieved submissions when polling in `AutoReviewPoller` and + `DownstreamPoller`. This makes submissions that can't be processed automatically drop + off (deleted submsissions), and provides an API-based mechanism to force a + problematic submission to drop off Auto Review without failing it in the DB + (marking retrieved). +- Normalize edge cases where Form Extraction bounding boxes are `float`s. +- Support idiomatic `copy.deepcopy()` and `copy.replace()` of `Prediction`s and + `PredicitonList`s via `__deepcopy__` and `__replace__`. + +### Changed + +- Simplify type annotations for `retry()` decorator. + +### Removed + +- `Prediction.copy()` in favor of `copy.deepcopy()` and `copy.replace()`. + + ## [v7.2.2] - 2025-10-14 ### Added @@ -293,7 +315,8 @@ This is the first major version release tested to work on Indico 6.X. - Row Association now also sorting on 'bbtop'. -[v7.2.1]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v7.2.1...v7.2.2 +[v7.2.3]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v7.2.2...v7.2.3 +[v7.2.2]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v7.2.1...v7.2.2 [v7.2.1]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v7.2.0...v7.2.1 [v7.2.0]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v6.14.2...v7.2.0 [v6.14.2]: https://github.com/IndicoDataSolutions/indico-toolkit-python/compare/v6.14.1...v6.14.2 diff --git a/indico_toolkit/__init__.py b/indico_toolkit/__init__.py index a10b5a2..abc9ff3 100644 --- a/indico_toolkit/__init__.py +++ b/indico_toolkit/__init__.py @@ -21,4 +21,4 @@ "ToolkitStaggeredLoopError", "ToolkitStatusError", ) -__version__ = "7.2.2" +__version__ = "7.2.3" diff --git a/poetry.lock b/poetry.lock index 4eadf62..b39666e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "aiodns" @@ -157,7 +157,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -689,7 +689,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -1781,7 +1781,7 @@ files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -markers = {main = "python_version < \"3.11\""} +markers = {main = "python_version == \"3.10\""} [[package]] name = "typish" diff --git a/pyproject.toml b/pyproject.toml index 046fd46..68fc368 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ readme = "README.md" urls = { source = "https://github.com/IndicoDataSolutions/Indico-Solutions-Toolkit" } requires-python = ">=3.10" -version = "7.2.2" +version = "7.2.3" dependencies = ["indico-client (>=6.14.0,<7.0.0)"] [project.optional-dependencies] From b42e59101110f0e31d31bd973534d63f2a0e9231 Mon Sep 17 00:00:00 2001 From: Michael Welborn Date: Tue, 3 Feb 2026 07:24:31 -0600 Subject: [PATCH 8/8] Allow dependent projects to install `indico-client` v7.X (But still keep v6.14 locked as the default) --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index b39666e..a6bbf81 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1936,4 +1936,4 @@ snapshots = ["pandas"] [metadata] lock-version = "2.1" python-versions = ">=3.10" -content-hash = "6917bdbcc33359ce37b5b0c1db336cddad6159680fc08d3c24d047666873c281" +content-hash = "d1c7150e350af38aae77f5334afa3d74dbf2e860790595e882651d817c690e0b" diff --git a/pyproject.toml b/pyproject.toml index 68fc368..5352ba3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ readme = "README.md" urls = { source = "https://github.com/IndicoDataSolutions/Indico-Solutions-Toolkit" } requires-python = ">=3.10" version = "7.2.3" -dependencies = ["indico-client (>=6.14.0,<7.0.0)"] +dependencies = ["indico-client (>=6.14.0,<8.0.0)"] [project.optional-dependencies] all = ["pandas (>=2.2.3,<3.0.0)", "plotly (>=5.2.1,<6.0.0)", "tqdm (>=4.50.0,<5.0.0)"]