Skip to content

chore: update Python to 3.14 and invenio deps#4058

Open
PascalRepond wants to merge 1 commit intorero:stagingfrom
PascalRepond:rep-invenio-update
Open

chore: update Python to 3.14 and invenio deps#4058
PascalRepond wants to merge 1 commit intorero:stagingfrom
PascalRepond:rep-invenio-update

Conversation

@PascalRepond
Copy link
Copy Markdown
Contributor

@PascalRepond PascalRepond commented Mar 24, 2026

Update Python version to >=3.14.0.
Update invenio dependencies to their latest major versions.
Add max_retries and retry_on_timeout to elasticsearch client config for
better resilience in CI tests.

Adapt code for breaking changes with new Python and invenio versions:

  • timezone: replace pytz with stdlib datetime.UTC and
    zoneinfo.ZoneInfo; replace deprecated datetime.utcnow() with
    datetime.now(UTC); remove pytz.utc.localize() calls since invenio
    records now return timezone-aware datetimes
  • Replace tz.localize() calls with dt.astimezone(tz) followed by
    replace on time components, ensuring DST offsets are applied correctly.
  • Convert datetimes to UTC before subtraction in loan duration
    calculations: unlike pytz (which creates distinct tzinfo objects per
    DST state), ZoneInfo reuses a single object, causing Python to
    perform naive subtraction across DST boundaries and yielding incorrect
    durations. Apply the same astimezone-then-replace pattern to overdue
    fee calculation and loan age computation.
  • permissions: add RoleNeed(role.name) to identity since
    flask-security-invenio 4.x now uses RoleNeed(role.id)
  • webargs: create a local FlaskParser with location="json_or_form" in
    accounts_views.py instead of importing use_args/use_kwargs from
    invenio_accounts (which is locked to location="form"). Cause:
    webargs 5→8 made FlaskParser(location="form") strict; our JSON
    requests were rejected before reaching view logic. Use
    invenio-accounts' FlaskParser so that validation errors return a
    400 status with RESTValidationError instead of webargs' default 422.
  • marshmallow: remove context={"request": request} parameter from
    Schema instantiation (no longer supported)
  • classmethod+property: replace stacked @classmethod/https://github.com/Property
    decorators (removed in Python 3.13) with descriptors
    (files/services.py) and inline computation (users/api.py)
  • typing: replace typing.Optional with PEP 604 X | None syntax
  • Override the default EmailField with a StringField to allow login with
    both username and email, bypassing HTML5 and WTForms email validation.

@PascalRepond PascalRepond force-pushed the rep-invenio-update branch 8 times, most recently from 481d2a4 to e6ef4d3 Compare March 25, 2026 10:53
@coveralls
Copy link
Copy Markdown

coveralls commented Mar 25, 2026

Coverage Status

coverage: 91.827% (+0.002%) from 91.825%
when pulling f6d5590 on PascalRepond:rep-invenio-update
into 935f06d on rero:staging.

@PascalRepond PascalRepond force-pushed the rep-invenio-update branch 8 times, most recently from ff9f772 to 8e9df6b Compare March 26, 2026 15:21
@PascalRepond PascalRepond marked this pull request as ready for review March 26, 2026 15:29
@PascalRepond PascalRepond requested review from jma and rerowep March 26, 2026 15:30
@PascalRepond PascalRepond changed the title chore: update invenio modules chore: update Python to 3.14 and invenio deps Mar 26, 2026
@PascalRepond PascalRepond removed request for jma and rerowep March 26, 2026 15:30
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Bumped project runtime to Python 3.14, loosened/simplified dependency constraints, migrated timezone handling from pytz/timezone.utc to stdlib UTC/zoneinfo, applied PEP 604 unions, and made localized refactors to parsing, identity enrichment, a descriptor, form validation, and many tests/migrations.

Changes

Cohort / File(s) Summary
Python runtime & packaging
/.github/workflows/continuous-integration-test.yml, CLAUDE.md, Dockerfile.base, INSTALL.md, pyproject.toml
Updated Python target to 3.14 across CI/docs/Docker/install; substantially simplified/loosened many dependency constraints and adjusted dev dependency groups in pyproject.toml.
Timezone modernization (app + migrations)
rero_ils/... (e.g. modules/*/*.py, alembic/*, query.py, modules/*/api.py, modules/*/utils.py, modules/*/extensions.py, ...)
Replaced pytz/timezone.utc/datetime.utcnow() usages with datetime.UTC, datetime.now(UTC), and zoneinfo.ZoneInfo(...); updated imports and adjusted small timezone handling differences (including Alembic migration).
Tests updated for UTC/ZoneInfo
tests/*, tests/ui/*, tests/api/*, tests/unit/*, tests/fixtures/*, tests/conftest.py
Converted many tests to use datetime.UTC/zoneinfo.ZoneInfo, updated fixtures and headers, and adjusted SEARCH_CLIENT_CONFIG in tests/conftest.py.
CI/tooling tweak
scripts/test
Extended pip-audit ignore list to include --ignore-vuln CVE-2026-4539 for pygments.
Type/annotation updates
rero_ils/modules/commons/identifiers.py, rero_ils/modules/documents/serializers/ris.py, rero_ils/modules/documents/views.py
Applied PEP 604 union syntax (`T
Request parsing & password schema
rero_ils/accounts_views.py
Introduced module-level FlaskParser(location="json_or_form"), re-bound use_args/use_kwargs from that parser, and stopped passing context={"request": request} to password schema constructors.
Identity enrichment & tests
rero_ils/modules/ext.py, tests/api/test_commons_api.py
on_identity_loaded now explicitly adds RoleNeed(role.name) for each current_user.roles; test adjusted to add persisted RoleNeed by id.
Descriptor refactor
rero_ils/modules/files/services.py
Replaced @classmethod @property`` implementation of max_files_count with a descriptor class `_MaxFilesCount` assigned to `max_files_count`.
User API simplification
rero_ils/modules/users/api.py
Removed User.fields class-level property; User.remove_fields now computes fields inline.
Login form validation
rero_ils/theme/forms.py
Explicitly declared email = StringField(validators=[validators.DataRequired()]) on LoginForm, overriding inherited field.
Misc cleanup & headers
rero_ils/modules/documents/dumpers/indexer.py, many modules
Removed commented-out pytz-localization block, removed unused imports, and updated copyright year ranges across files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: update Python to 3.14 and invenio deps' clearly and concisely summarizes the main changes in the pull request—upgrading Python version and invenio dependencies.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing all major modifications including Python upgrades, dependency updates, timezone/datetime changes, permissions updates, webargs configuration, marshmallow changes, and typing improvements.
Docstring Coverage ✅ Passed Docstring coverage is 98.88% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

@PascalRepond

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

@PascalRepond PascalRepond force-pushed the rep-invenio-update branch 2 times, most recently from 0d8ea0f to bf02f80 Compare March 27, 2026 08:20
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Update Python version to >=3.14.0.
Update invenio dependencies to their latest major versions.
Add max_retries and retry_on_timeout to elasticsearch client config for
better resilience in CI tests.

Adapt code for breaking changes with new Python and invenio versions:
- timezone: replace pytz with stdlib datetime.UTC and
  zoneinfo.ZoneInfo; replace deprecated datetime.utcnow() with
  datetime.now(UTC); remove pytz.utc.localize() calls since invenio
  records now return timezone-aware datetimes
- Replace `tz.localize()` calls with `dt.astimezone(tz)` followed by
  replace on time components, ensuring DST offsets are applied correctly.
- Convert datetimes to UTC before subtraction in loan duration
  calculations: unlike pytz (which creates distinct tzinfo objects per
  DST state), `ZoneInfo` reuses a single object, causing Python to
  perform naive subtraction across DST boundaries and yielding incorrect
  durations. Apply the same astimezone-then-replace pattern to overdue
  fee calculation and loan age computation.
- permissions: add RoleNeed(role.name) to identity since
  flask-security-invenio 4.x now uses RoleNeed(role.id)
- webargs: create a local FlaskParser with location="json_or_form" in
  accounts_views.py instead of importing use_args/use_kwargs from
  invenio_accounts (which is locked to location="form"). Cause:
  webargs 5→8 made FlaskParser(location="form") strict; our JSON
  requests were rejected before reaching view logic. Use
  invenio-accounts' FlaskParser so that validation errors return a
  400 status with RESTValidationError instead of webargs' default 422.
- marshmallow: remove context={"request": request} parameter from
  Schema instantiation (no longer supported)
- classmethod+property: replace stacked @classmethod/@Property
  decorators (removed in Python 3.13) with descriptors
  (files/services.py) and inline computation (users/api.py)
- typing: replace typing.Optional with PEP 604 X | None syntax
- Override the default EmailField with a StringField to allow login with
  both username and email, bypassing HTML5 and WTForms email validation.

Co-Authored-by: Pascal Repond <pascal.repond@rero.ch>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
rero_ils/modules/libraries/api.py (3)

192-204: ⚠️ Potential issue | 🟠 Major

Handle repeating and multi-day open exceptions in _has_is_open().

This branch only checks the initial start_date. An open exception that spans into future days, or one with repeat, is treated as absent once its first occurrence is in the past, so next_open() can raise LibraryNeverOpen even though future open days still exist.

🐛 Proposed fix
-        current_timestamp = datetime.now(UTC)
+        current_date = datetime.now(UTC).date()
         for exception_date in filter(lambda d: d["is_open"], self.get("exception_dates", [])):
             start_date = date_string_to_utc(exception_date["start_date"])
+            end_date = (
+                date_string_to_utc(exception_date["end_date"])
+                if exception_date.get("end_date")
+                else start_date
+            )
+            if exception_date.get("repeat"):
+                return True
             # avoid next_open infinite loop if an open exception date is
             # in the past
-            if start_date > current_timestamp:
+            if end_date.date() > current_date:
                 return True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rero_ils/modules/libraries/api.py` around lines 192 - 204, The _has_is_open
method only checks exception_dates' start_date and misses multi-day or repeating
open exceptions; update _has_is_open to treat an exception as future-open if its
span or any of its repeated occurrences fall after now: for each exception in
exception_dates (from self.get("exception_dates", [])) convert start_date and
end_date (or compute end from duration) via date_string_to_utc and if end_date >
now return True, and if the exception has a repeat rule, compute the next
occurrence(s) using the same repeat logic used by next_open (or a shared helper)
to determine if any future occurrence or span exists—ensure you reference and
reuse date_string_to_utc, _has_is_open, and the repeat handling used by
next_open to avoid duplicating logic and to prevent the infinite-loop guard from
wrongly excluding future open days.

341-362: ⚠️ Potential issue | 🟠 Major

Normalize get_open_days() to library-local calendar days first.

This method is day-based, but it iterates on full timestamps. A same-day window like 09:0018:00 currently runs twice, and count_open() inherits the extra day.

🐛 Proposed fix
         if isinstance(start_date, str):
             start_date = date_string_to_utc(start_date)
         if isinstance(end_date, str):
             end_date = date_string_to_utc(end_date)
+        if start_date.tzinfo is None:
+            start_date = start_date.replace(tzinfo=UTC)
+        if end_date.tzinfo is None:
+            end_date = end_date.replace(tzinfo=UTC)
+        tz = self.get_timezone()
+        current_date = start_date
+        current_day = start_date.astimezone(tz).date()
+        end_day = end_date.astimezone(tz).date()
 
         dates = []
-        end_date += timedelta(days=1)
-        while end_date > start_date:
-            if self.is_open(date=start_date, day_only=True, date_only=True):
-                dates.append(start_date)
-            start_date += timedelta(days=1)
+        while current_day <= end_day:
+            if self.is_open(date=current_date, day_only=True, date_only=True):
+                dates.append(current_date)
+            current_date += timedelta(days=1)
+            current_day = current_date.astimezone(tz).date()
         return dates
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rero_ils/modules/libraries/api.py` around lines 341 - 362, get_open_days
iterates timestamps and can count the same calendar day twice; normalize inputs
to library-local calendar days first: convert start_date/end_date (strings via
date_string_to_utc if needed) to the start of their calendar day (midnight) in
the library timezone and set end_date to the start of the day after the end_date
to make the loop day-based and inclusive; then iterate by whole days and call
self.is_open(date=current_day, day_only=True, date_only=True). Update count_open
to keep using len(self.get_open_days(...)) unchanged.

324-339: ⚠️ Potential issue | 🟠 Major

ensure=True should prefer exception hours.

After the search loop, date may be open because of an exception or with exception-specific hours. _get_opening_hour_by_day() only reads regular opening_hours, so this path can return the wrong time or blow up on None.

🐛 Proposed fix
         if not ensure:
             return date
-        opening_hour = self._get_opening_hour_by_day(date.strftime("%A"))
+        opening_hour = None
+        for exception in self._get_exceptions_matching_date(date, day_only=True):
+            if exception["is_open"] and exception.get("times"):
+                opening_hour = exception["times"][0]["start_time"]
+                break
+        if opening_hour is None:
+            opening_hour = self._get_opening_hour_by_day(date.strftime("%A"))
+        if opening_hour is None:
+            return date
         time = [int(part) for part in opening_hour.split(":")]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rero_ils/modules/libraries/api.py` around lines 324 - 339, The next_open
method can return a wrong time when the found date is open due to an exception
because it unconditionally calls _get_opening_hour_by_day (which only reads
regular opening_hours) and may return None; update next_open to determine the
actual opening time for that specific date by checking for exception hours first
(using the same logic is_open uses for exceptions) and only falling back to
_get_opening_hour_by_day for regular weekdays, and ensure you handle a None
opening hour safely (raise a clear error or skip to next day) so ensure=True
never crashes when exception-specific hours exist; reference next_open, is_open,
and _get_opening_hour_by_day when locating the logic to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pyproject.toml`:
- Around line 89-90: Update the urllib3 dependency spec to include a safe
minimum: replace the existing "urllib3<2.0.0" entry with
"urllib3>=1.26.20,<2.0.0" in pyproject.toml (locate the string "urllib3<2.0.0")
so pre-CVE-2024-37891 vulnerable releases are excluded while maintaining
Elasticsearch 7 compatibility.

---

Outside diff comments:
In `@rero_ils/modules/libraries/api.py`:
- Around line 192-204: The _has_is_open method only checks exception_dates'
start_date and misses multi-day or repeating open exceptions; update
_has_is_open to treat an exception as future-open if its span or any of its
repeated occurrences fall after now: for each exception in exception_dates (from
self.get("exception_dates", [])) convert start_date and end_date (or compute end
from duration) via date_string_to_utc and if end_date > now return True, and if
the exception has a repeat rule, compute the next occurrence(s) using the same
repeat logic used by next_open (or a shared helper) to determine if any future
occurrence or span exists—ensure you reference and reuse date_string_to_utc,
_has_is_open, and the repeat handling used by next_open to avoid duplicating
logic and to prevent the infinite-loop guard from wrongly excluding future open
days.
- Around line 341-362: get_open_days iterates timestamps and can count the same
calendar day twice; normalize inputs to library-local calendar days first:
convert start_date/end_date (strings via date_string_to_utc if needed) to the
start of their calendar day (midnight) in the library timezone and set end_date
to the start of the day after the end_date to make the loop day-based and
inclusive; then iterate by whole days and call self.is_open(date=current_day,
day_only=True, date_only=True). Update count_open to keep using
len(self.get_open_days(...)) unchanged.
- Around line 324-339: The next_open method can return a wrong time when the
found date is open due to an exception because it unconditionally calls
_get_opening_hour_by_day (which only reads regular opening_hours) and may return
None; update next_open to determine the actual opening time for that specific
date by checking for exception hours first (using the same logic is_open uses
for exceptions) and only falling back to _get_opening_hour_by_day for regular
weekdays, and ensure you handle a None opening hour safely (raise a clear error
or skip to next day) so ensure=True never crashes when exception-specific hours
exist; reference next_open, is_open, and _get_opening_hour_by_day when locating
the logic to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd2c31fe-9ebf-47e4-98dd-46f6018fe714

📥 Commits

Reviewing files that changed from the base of the PR and between bf02f80 and f6d5590.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (77)
  • .github/workflows/continuous-integration-test.yml
  • CLAUDE.md
  • Dockerfile.base
  • INSTALL.md
  • pyproject.toml
  • rero_ils/accounts_views.py
  • rero_ils/alembic/2b0af71048a7_add_request_expiration_date.py
  • rero_ils/modules/acquisition/acq_orders/api.py
  • rero_ils/modules/api.py
  • rero_ils/modules/api_harvester/models.py
  • rero_ils/modules/cli/fixtures.py
  • rero_ils/modules/collections/api.py
  • rero_ils/modules/commons/identifiers.py
  • rero_ils/modules/documents/dumpers/indexer.py
  • rero_ils/modules/documents/serializers/ris.py
  • rero_ils/modules/documents/views.py
  • rero_ils/modules/entities/local_entities/indexer.py
  • rero_ils/modules/entities/remote_entities/replace.py
  • rero_ils/modules/ext.py
  • rero_ils/modules/files/services.py
  • rero_ils/modules/holdings/api.py
  • rero_ils/modules/holdings/cli.py
  • rero_ils/modules/holdings/utils.py
  • rero_ils/modules/ill_requests/api.py
  • rero_ils/modules/items/api/api.py
  • rero_ils/modules/items/api/circulation.py
  • rero_ils/modules/items/api/issue.py
  • rero_ils/modules/items/api/record.py
  • rero_ils/modules/items/utils.py
  • rero_ils/modules/libraries/api.py
  • rero_ils/modules/loans/api.py
  • rero_ils/modules/loans/cli.py
  • rero_ils/modules/loans/extensions.py
  • rero_ils/modules/loans/logs/api.py
  • rero_ils/modules/loans/tasks.py
  • rero_ils/modules/loans/utils.py
  • rero_ils/modules/migrations/api.py
  • rero_ils/modules/migrations/data/api.py
  • rero_ils/modules/migrations/data/views.py
  • rero_ils/modules/monitoring/api.py
  • rero_ils/modules/notifications/api.py
  • rero_ils/modules/notifications/tasks.py
  • rero_ils/modules/operation_logs/extensions.py
  • rero_ils/modules/patron_transaction_events/api.py
  • rero_ils/modules/patron_transactions/utils.py
  • rero_ils/modules/selfcheck/api.py
  • rero_ils/modules/selfcheck/utils.py
  • rero_ils/modules/serializers/base.py
  • rero_ils/modules/stats/views.py
  • rero_ils/modules/users/api.py
  • rero_ils/modules/utils.py
  • rero_ils/query.py
  • rero_ils/theme/forms.py
  • scripts/test
  • tests/api/circulation/test_actions_views_checkin.py
  • tests/api/circulation/test_borrow_limits.py
  • tests/api/circulation/test_library_with_no_circulation.py
  • tests/api/holdings/test_provisional_items.py
  • tests/api/items/test_items_rest.py
  • tests/api/loans/test_loans_rest.py
  • tests/api/notifications/test_notifications_rest.py
  • tests/api/selfcheck/test_selfcheck.py
  • tests/api/test_commons_api.py
  • tests/api/test_tasks.py
  • tests/conftest.py
  • tests/fixtures/basics.py
  • tests/fixtures/circulation.py
  • tests/migrations/ui/test_migrations_cli.py
  • tests/ui/circulation/test_actions_auto_extend.py
  • tests/ui/circulation/test_actions_expired_request.py
  • tests/ui/circulation/test_actions_extend.py
  • tests/ui/circulation/test_extend_external.py
  • tests/ui/libraries/test_libraries_api.py
  • tests/ui/loans/test_loans_api.py
  • tests/unit/conftest.py
  • tests/unit/documents/test_documents_dojson_marc21.py
  • tests/utils.py
✅ Files skipped from review due to trivial changes (34)
  • rero_ils/modules/collections/api.py
  • INSTALL.md
  • Dockerfile.base
  • rero_ils/modules/items/api/issue.py
  • rero_ils/modules/loans/cli.py
  • rero_ils/modules/migrations/data/views.py
  • rero_ils/modules/serializers/base.py
  • rero_ils/modules/loans/tasks.py
  • rero_ils/modules/loans/logs/api.py
  • rero_ils/modules/patron_transaction_events/api.py
  • rero_ils/modules/migrations/data/api.py
  • rero_ils/modules/holdings/api.py
  • .github/workflows/continuous-integration-test.yml
  • rero_ils/modules/holdings/utils.py
  • tests/api/circulation/test_borrow_limits.py
  • rero_ils/query.py
  • rero_ils/modules/entities/local_entities/indexer.py
  • tests/fixtures/basics.py
  • rero_ils/modules/ill_requests/api.py
  • tests/api/holdings/test_provisional_items.py
  • rero_ils/modules/items/api/circulation.py
  • rero_ils/modules/notifications/tasks.py
  • rero_ils/modules/notifications/api.py
  • rero_ils/modules/selfcheck/api.py
  • rero_ils/modules/items/utils.py
  • rero_ils/modules/documents/dumpers/indexer.py
  • tests/ui/loans/test_loans_api.py
  • tests/ui/circulation/test_actions_extend.py
  • tests/api/items/test_items_rest.py
  • tests/unit/conftest.py
  • rero_ils/modules/operation_logs/extensions.py
  • rero_ils/modules/commons/identifiers.py
  • tests/fixtures/circulation.py
  • rero_ils/modules/patron_transactions/utils.py
🚧 Files skipped from review as they are similar to previous changes (28)
  • rero_ils/modules/items/api/api.py
  • rero_ils/modules/cli/fixtures.py
  • tests/conftest.py
  • rero_ils/modules/selfcheck/utils.py
  • rero_ils/modules/entities/remote_entities/replace.py
  • tests/api/circulation/test_library_with_no_circulation.py
  • rero_ils/modules/documents/views.py
  • rero_ils/modules/migrations/api.py
  • rero_ils/modules/monitoring/api.py
  • rero_ils/accounts_views.py
  • rero_ils/modules/utils.py
  • tests/api/test_tasks.py
  • tests/migrations/ui/test_migrations_cli.py
  • tests/api/test_commons_api.py
  • rero_ils/modules/users/api.py
  • tests/ui/libraries/test_libraries_api.py
  • rero_ils/alembic/2b0af71048a7_add_request_expiration_date.py
  • tests/api/circulation/test_actions_views_checkin.py
  • tests/ui/circulation/test_extend_external.py
  • rero_ils/modules/loans/utils.py
  • tests/unit/documents/test_documents_dojson_marc21.py
  • rero_ils/modules/api_harvester/models.py
  • rero_ils/theme/forms.py
  • tests/api/notifications/test_notifications_rest.py
  • CLAUDE.md
  • rero_ils/modules/api.py
  • rero_ils/modules/acquisition/acq_orders/api.py
  • rero_ils/modules/loans/api.py

@PascalRepond PascalRepond requested review from jma and rerowep March 27, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants