Skip to content

Update version to 1.5.4.dev1 and enhance query parameter handling#185

Merged
rogelioLpz merged 8 commits into
mainfrom
feat/users-filter-by-ids-and-blocked
Jun 4, 2026
Merged

Update version to 1.5.4.dev1 and enhance query parameter handling#185
rogelioLpz merged 8 commits into
mainfrom
feat/users-filter-by-ids-and-blocked

Conversation

@julietteceb16

@julietteceb16 julietteceb16 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for ids query parameter with comma-separated values across query endpoints.
    • Improved pagination to properly handle and preserve ids filters in next page URLs.
  • Tests

    • Added test coverage for comma-separated ID queries and pagination behavior.
  • Chores

    • Updated version to 1.5.4.dev3.
    • Dropped Python 3.9 support; added Python 3.13 support (now requires Python ≥3.10).
    • Updated cuenca-validations dependency.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

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

This PR refactors query parameter handling in REST API handlers across Chalice and FastAPI frameworks. It introduces a new utility module (agave/core/query_params.py) that centralizes parsing of comma-separated list fields from HTTP query strings into Pydantic model instances. The utilities include build_query_dict() for constructing model kwargs with list field conversion, validate_query_params() for validation, and query_params_for_url() for serializing lists back to comma-separated form. Both framework handlers now use these utilities and updated pagination URL generation. Filter support is extended to handle ids field via a Mongo id__in constraint. Comprehensive unit and integration tests validate the new utilities and integration points. Package metadata is updated with a new development version and Python 3.10 minimum requirement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • rogelioLpz
  • pachCode
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The pull request title mentions updating version to 1.5.4.dev1, but the actual version change in agave/version.py is to 1.5.4.dev3. Update the PR title to reflect the actual version change (1.5.4.dev3), or clarify if the version number in the title was intentional but differs from the implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/users-filter-by-ids-and-blocked

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

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (bd63e02) to head (dc0ec8b).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #185   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           19        20    +1     
  Lines          716       772   +56     
  Branches        75        88   +13     
=========================================
+ Hits           716       772   +56     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/core/test_query_params.py (2)

75-79: ⚡ Quick win

Add a regression test for the empty-query mapping path.

Please add a test that calls validate_query_params(EmptyQueryMapping(), SampleQuery) and asserts it does not raise and keeps all optional fields as None.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/test_query_params.py` around lines 75 - 79, Add a new regression
test that calls validate_query_params with an EmptyQueryMapping and SampleQuery
and asserts it does not raise and returns a mapping where all optional
SampleQuery fields are None; specifically, create a test function (e.g.,
test_validate_query_params_accepts_empty_mapping) that does: result =
validate_query_params(EmptyQueryMapping(), SampleQuery) and then asserts no
exception and that for each optional field defined on SampleQuery the result
mapping has a key with value None (use the same field names as in SampleQuery to
locate them).

48-53: ⚡ Quick win

Rename this test to match its behavior.

The current name says “rejects invalid list”, but it verifies successful normalization to ['a', 'b']. This is misleading during failure triage.

Suggested rename
-def test_validate_query_params_rejects_invalid_list() -> None:
+def test_validate_query_params_normalizes_list_fields() -> None:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/test_query_params.py` around lines 48 - 53, Rename the test
function test_validate_query_params_rejects_invalid_list to a name that reflects
its behavior (it verifies normalization rather than rejection), e.g.
test_validate_query_params_normalizes_list_field or
test_validate_query_params_returns_list_for_multiple_values; update the function
definition and any references accordingly so the test still calls
QueryParams('ids=a&ids=b'), invokes validate_query_params(query, SampleQuery),
and asserts validated.ids == ['a', 'b'].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agave/core/query_params.py`:
- Around line 64-72: EmptyQueryMapping is missing iteration support required by
build_query_dict and the QueryParamMapping protocol; implement __iter__ to yield
no keys and __len__ to return 0 (and keep existing __contains__, get, getlist)
so EmptyQueryMapping behaves like an empty mapping when iterated by
build_query_dict, e.g., add an __iter__(self) -> Iterator[str] that yields
nothing and a __len__(self) -> int that returns 0 to satisfy consumers expecting
an iterable mapping.

In `@tests/blueprint/test_blueprint.py`:
- Around line 289-297: The test test_query_with_repeated_ids_param currently
only checks status and presence of 'items' but doesn't assert filtering; modify
it to parse resp.json(), extract the list under 'items', and assert that every
returned item's id is one of the requested ids ('US1' or 'US2') and that no
other ids are present (optionally assert the set of returned ids equals
{'US1','US2'} or that length matches expected). Use the existing
client.get('/accounts?ids=US1&ids=US2') response object (resp) and its
resp.json() to perform these assertions so the test fails if ids filtering is
ignored.

---

Nitpick comments:
In `@tests/core/test_query_params.py`:
- Around line 75-79: Add a new regression test that calls validate_query_params
with an EmptyQueryMapping and SampleQuery and asserts it does not raise and
returns a mapping where all optional SampleQuery fields are None; specifically,
create a test function (e.g., test_validate_query_params_accepts_empty_mapping)
that does: result = validate_query_params(EmptyQueryMapping(), SampleQuery) and
then asserts no exception and that for each optional field defined on
SampleQuery the result mapping has a key with value None (use the same field
names as in SampleQuery to locate them).
- Around line 48-53: Rename the test function
test_validate_query_params_rejects_invalid_list to a name that reflects its
behavior (it verifies normalization rather than rejection), e.g.
test_validate_query_params_normalizes_list_field or
test_validate_query_params_returns_list_for_multiple_values; update the function
definition and any references accordingly so the test still calls
QueryParams('ids=a&ids=b'), invokes validate_query_params(query, SampleQuery),
and asserts validated.ids == ['a', 'b'].
🪄 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: 4f0f809e-0417-47be-b8f1-e535d2006294

📥 Commits

Reviewing files that changed from the base of the PR and between bd63e02 and 6a81f13.

📒 Files selected for processing (8)
  • agave/chalice/rest_api.py
  • agave/core/filters.py
  • agave/core/query_params.py
  • agave/fastapi/rest_api.py
  • agave/version.py
  • examples/validators.py
  • tests/blueprint/test_blueprint.py
  • tests/core/test_query_params.py

Comment thread agave/core/query_params.py Outdated
Comment thread tests/blueprint/test_blueprint.py Outdated
Comment on lines +289 to +297
def test_query_with_repeated_ids_param(
client_fixture: str,
request: pytest.FixtureRequest,
) -> None:
client = request.getfixturevalue(client_fixture)
resp = client.get('/accounts?ids=US1&ids=US2')
assert resp.status_code == 200
assert 'items' in resp.json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test does not verify that ids filtering actually works.

At Line 294, this test sends repeated ids, but current assertions only check status and key existence, so it still passes if ids is ignored.

✅ Suggested test hardening
 `@pytest.mark.parametrize`(
     "client_fixture", ["fastapi_client", "chalice_client"]
 )
 `@pytest.mark.usefixtures`('accounts')
 def test_query_with_repeated_ids_param(
     client_fixture: str,
     request: pytest.FixtureRequest,
+    accounts: list[Account],
 ) -> None:
     client = request.getfixturevalue(client_fixture)
-    resp = client.get('/accounts?ids=US1&ids=US2')
+    account_ids = [accounts[0].id, accounts[1].id]
+    query = '&'.join(f'ids={account_id}' for account_id in account_ids)
+    resp = client.get(f'/accounts?{query}')
     assert resp.status_code == 200
-    assert 'items' in resp.json()
+    json_body = resp.json()
+    assert 'items' in json_body
+    returned_ids = {item['id'] for item in json_body['items']}
+    assert returned_ids == set(account_ids)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/blueprint/test_blueprint.py` around lines 289 - 297, The test
test_query_with_repeated_ids_param currently only checks status and presence of
'items' but doesn't assert filtering; modify it to parse resp.json(), extract
the list under 'items', and assert that every returned item's id is one of the
requested ids ('US1' or 'US2') and that no other ids are present (optionally
assert the set of returned ids equals {'US1','US2'} or that length matches
expected). Use the existing client.get('/accounts?ids=US1&ids=US2') response
object (resp) and its resp.json() to perform these assertions so the test fails
if ids filtering is ignored.

Comment thread agave/core/query_params.py Outdated
Comment on lines +7 to +14
try:
from types import UnionType
except ImportError: # Python < 3.10
UnionType = None # type: ignore[misc, assignment]

_UNION_ORIGINS: Tuple[Any, ...] = (
(Union, UnionType) if UnionType is not None else (Union,)
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Quitar soporte para <3.10 y dejar más simple el import

Comment thread agave/core/query_params.py Outdated
params[name] = query_mapping.getlist(name)
else:
params[name] = query_mapping.get(name)
return model_cls(**params)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Estuve revisando y al parecer no hay una forma estandar de pasar listas de parametros.
Veo que estas adoptando esta forma
endpoint?param=value_1&param=value2

Creo que es más simple si lo haces separado por ,
endpoint?param=value_1,value2

El parametro lo puedes poner como str y hacer la conversión aqui en agave

class (QueryParams):
  ids:str 

Asi te evitas estas funciones auxiliares para aceptar la lista

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agave/core/filters.py`:
- Line 11: The function generic_query currently uses a mutable default argument
excluded: list[str] = []; change the signature to use excluded:
Optional[list[str]] = None and inside generic_query initialize excluded = [] if
excluded is None to avoid sharing the same list across calls; update any type
hints/imports as needed and ensure all places that reference excluded still work
with the possibly None input before initialization.

In `@agave/core/query_params.py`:
- Around line 58-64: The function query_params_for_url currently uses
query.model_dump() without excluding None, which lets Optional fields become the
string "None" when later urlencoded; change it to omit None-valued keys by
calling query.model_dump(exclude_none=True) (or filter out keys with value is
None from the params dict) before the list-annotation handling so that Optional
fields are not serialized into the query string; keep the existing
_is_list_annotation and join logic intact after the None-filtering.

In `@tests/core/test_query_params.py`:
- Around line 65-67: The test only checks list serialization but not
None-handling; update
test_query_params_for_url_serializes_list_as_comma_separated to also assert that
None-valued fields from SampleQuery (e.g., a field like cursor or other optional
attribute) are not emitted as the string "None" by calling
query_params_for_url(query) and asserting the result either does not contain
that key or that result.get('cursor') is None, ensuring query_params_for_url
preserves omission of None values rather than serializing them to "None".
🪄 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: 7f537742-beea-4b56-b59d-4b5a7f1ec75a

📥 Commits

Reviewing files that changed from the base of the PR and between b217854 and 0246bc6.

📒 Files selected for processing (10)
  • .github/workflows/test.yml
  • agave/chalice/rest_api.py
  • agave/core/filters.py
  • agave/core/query_params.py
  • agave/fastapi/rest_api.py
  • agave/version.py
  • requirements.txt
  • setup.py
  • tests/blueprint/test_blueprint.py
  • tests/core/test_query_params.py
✅ Files skipped from review due to trivial changes (2)
  • requirements.txt
  • agave/version.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/blueprint/test_blueprint.py
  • agave/chalice/rest_api.py

Comment thread agave/core/filters.py
return list(ids)


def generic_query(query: QueryParams, excluded: list[str] = []) -> Q:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix mutable default argument.

The default argument excluded: list[str] = [] is a mutable object that is shared across all function calls. If any caller mutates this list, it will affect subsequent calls. Replace with None and initialize inside the function.

🔧 Proposed fix
-def generic_query(query: QueryParams, excluded: list[str] = []) -> Q:
+def generic_query(query: QueryParams, excluded: list[str] | None = None) -> Q:
+    if excluded is None:
+        excluded = []
     filters = Q()

As per coding guidelines, the static analysis tool Ruff flagged this with rule B006: "Do not use mutable data structures for argument defaults."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def generic_query(query: QueryParams, excluded: list[str] = []) -> Q:
def generic_query(query: QueryParams, excluded: list[str] | None = None) -> Q:
if excluded is None:
excluded = []
filters = Q()
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 11-11: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agave/core/filters.py` at line 11, The function generic_query currently uses
a mutable default argument excluded: list[str] = []; change the signature to use
excluded: Optional[list[str]] = None and inside generic_query initialize
excluded = [] if excluded is None to avoid sharing the same list across calls;
update any type hints/imports as needed and ensure all places that reference
excluded still work with the possibly None input before initialization.

Comment thread agave/core/query_params.py
Comment thread tests/core/test_query_params.py
@julietteceb16 julietteceb16 requested a review from rogelioLpz June 2, 2026 22:29
@rogelioLpz rogelioLpz merged commit da3c2ae into main Jun 4, 2026
11 checks passed
@rogelioLpz rogelioLpz deleted the feat/users-filter-by-ids-and-blocked branch June 4, 2026 21:39
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