Skip to content

Add file_object_content_by_id and file_object_content_by_hfid filters#904

Merged
gmazoyer merged 7 commits intoinfrahub-developfrom
gma-20260327-infp504
Mar 30, 2026
Merged

Add file_object_content_by_id and file_object_content_by_hfid filters#904
gmazoyer merged 7 commits intoinfrahub-developfrom
gma-20260327-infp504

Conversation

@gmazoyer
Copy link
Copy Markdown
Contributor

@gmazoyer gmazoyer commented Mar 30, 2026

Why & What

This is a change following #889.

Support all three file object retrieval endpoints: by storage ID (existing), by node UUID, and by HFID. Extract shared ObjectStore file fetching and content-type validation into helpers.

Make InfrahubFilters accept an optional client, checking at call time instead of maintaining separate no-client fallback functions.

Checklist

  • Tests added/updated

Summary by CodeRabbit

  • New Features

    • Added template filters to retrieve file content by node ID and by HFID; filters validate required identifier and kind. Templates can defer providing a client and set it later.
  • Bug Fixes

    • Unified input validation and clearer template errors for missing inputs, permission/auth failures, and non-text (binary) content rejection.
  • Documentation

    • Expanded templating docs with execution contexts, per-context filter availability tables, and usage examples.
  • Tests

    • Updated tests to exercise deferred-client behavior and HFID/kind validation.

Support all three file object retrieval endpoints: by storage ID
(existing), by node UUID, and by HFID. Extract shared `ObjectStore`
file fetching and content-type validation into helpers.

Make `InfrahubFilters` accept an optional client, checking at call
time instead of maintaining separate no-client fallback functions.
CLIENT_FILTER_NAMES is the single source of truth for all
client-dependent filter names.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f29d7502-e759-43f7-aef3-869bc92ab766

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb3854 and 98deaf5.

📒 Files selected for processing (5)
  • docs/_templates/sdk_template_reference.j2
  • docs/docs/python-sdk/reference/templating.mdx
  • infrahub_sdk/template/__init__.py
  • infrahub_sdk/template/infrahub_filters.py
  • tasks.py
✅ Files skipped from review due to trivial changes (1)
  • docs/_templates/sdk_template_reference.j2
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrahub_sdk/template/init.py
  • tasks.py

Walkthrough

Adds two async Jinja2 filters: file_object_content_by_id(node_id: str) and file_object_content_by_hfid(hfid: str | list[str], kind: str = ""), while retaining file_object_content(storage_id). InfrahubFilters now accepts an optional client, exposes get_filter_names(), and enforces client presence at call time via _require_client() and set_client(). ObjectStore/ObjectStoreSync add get_file_by_id and get_file_by_hfid and centralize text-content validation (rejecting non-text content). Jinja2Template always instantiates InfrahubFilters and mutates its client in-place when set; filter registrations and docs were updated with execution-context metadata and examples.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding two new filters for alternative file object content retrieval methods.
Description check ✅ Passed The description covers the key changes (supporting three file retrieval endpoints, refactoring ObjectStore helpers, making InfrahubFilters client-optional) and indicates tests were added/updated.

✏️ 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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 30, 2026

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 98deaf5
Status: ✅  Deploy successful!
Preview URL: https://b3054e11.infrahub-sdk-python.pages.dev
Branch Preview URL: https://gma-20260327-infp504.infrahub-sdk-python.pages.dev

View logs

@github-actions github-actions bot added the type/documentation Improvements or additions to documentation label Mar 30, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 72.22222% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
infrahub_sdk/object_store.py 56.66% 13 Missing ⚠️
infrahub_sdk/template/infrahub_filters.py 80.00% 6 Missing and 1 partial ⚠️
@@                 Coverage Diff                  @@
##           infrahub-develop     #904      +/-   ##
====================================================
- Coverage             81.09%   81.02%   -0.08%     
====================================================
  Files                   121      121              
  Lines                 10592    10628      +36     
  Branches               1581     1584       +3     
====================================================
+ Hits                   8590     8611      +21     
- Misses                 1487     1501      +14     
- Partials                515      516       +1     
Flag Coverage Δ
integration-tests 40.93% <0.00%> (-0.14%) ⬇️
python-3.10 52.54% <43.05%> (-0.14%) ⬇️
python-3.11 52.56% <43.05%> (-0.12%) ⬇️
python-3.12 52.54% <43.05%> (-0.12%) ⬇️
python-3.13 52.56% <43.05%> (-0.12%) ⬇️
python-3.14 54.27% <51.38%> (-0.07%) ⬇️
python-filler-3.12 23.87% <36.11%> (+0.05%) ⬆️

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

Files with missing lines Coverage Δ
infrahub_sdk/template/__init__.py 94.55% <100.00%> (-0.15%) ⬇️
infrahub_sdk/template/filters.py 100.00% <ø> (ø)
infrahub_sdk/template/infrahub_filters.py 88.46% <80.00%> (-7.91%) ⬇️
infrahub_sdk/object_store.py 58.38% <56.66%> (-0.71%) ⬇️

... and 8 files with indirect coverage changes

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

@gmazoyer gmazoyer marked this pull request as ready for review March 30, 2026 08:56
@gmazoyer gmazoyer requested a review from a team as a code owner March 30, 2026 08:56
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.

🧹 Nitpick comments (3)
dev/specs/infp-504-artifact-composition/data-model.md (1)

68-69: Consider showing the complete CLIENT_FILTER_NAMES tuple for clarity.

The ellipsis (...) in the tuple obscures the actual filter names. Since this is specification documentation, showing all four names explicitly would be clearer and serve as better reference material.

Based on context snippet 1 from infrahub_sdk/template/infrahub_filters.py:19-27, the complete list is:

CLIENT_FILTER_NAMES = (
    "artifact_content",
    "file_object_content",
    "file_object_content_by_id",
    "file_object_content_by_hfid",
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev/specs/infp-504-artifact-composition/data-model.md` around lines 68 - 69,
Replace the truncated CLIENT_FILTER_NAMES tuple with the full list of four
filter names so the spec shows exact values; locate the CLIENT_FILTER_NAMES
symbol and expand its value to explicitly include "artifact_content",
"file_object_content", "file_object_content_by_id", and
"file_object_content_by_hfid" instead of using an ellipsis.
dev/specs/infp-504-artifact-composition/spec.md (1)

121-121: Consider updating Key Entities to list all file object filters.

The InfrahubFilters entity description mentions artifact_content and file_object_content but doesn't explicitly list the two new filters (file_object_content_by_id, file_object_content_by_hfid). Consider updating for completeness.

📝 Suggested update
-- **`InfrahubFilters`**: New class that holds an `InfrahubClient` reference and exposes `artifact_content`, `file_object_content`, and any other client-dependent filter methods. Registered into the Jinja2 filter map when a client is provided.
+- **`InfrahubFilters`**: New class that holds an `InfrahubClient` reference and exposes `artifact_content`, `file_object_content`, `file_object_content_by_id`, `file_object_content_by_hfid`, and any other client-dependent filter methods. Registered into the Jinja2 filter map when a client is provided.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev/specs/infp-504-artifact-composition/spec.md` at line 121, Update the
InfrahubFilters Key Entities entry to explicitly list all file-object-related
filters by name: include artifact_content, file_object_content,
file_object_content_by_id, and file_object_content_by_hfid, and mention that
these are registered into the Jinja2 filter map when an InfrahubClient is
provided; locate the description for InfrahubFilters and replace the current
two-filter mention with the full list to ensure completeness.
infrahub_sdk/object_store.py (1)

113-117: URL-encode HFID query parameters to prevent malformed URLs with special characters.

HFID components are joined directly into the query string without URL encoding. If HFID values contain special characters (spaces, &, =, etc.), the URL will be malformed.

🔧 Proposed fix using urllib.parse
+from urllib.parse import quote
+
 async def get_file_by_hfid(self, kind: str, hfid: list[str], tracker: str | None = None) -> str:
     """Retrieve file object content by Human-Friendly ID."""
-    params = "&".join(f"hfid={h}" for h in hfid)
+    params = "&".join(f"hfid={quote(h, safe='')}" for h in hfid)
     url = f"{self.client.address}/api/files/by-hfid/{kind}?{params}"
     return await self._get_file(url=url, identifier=f"{kind}:{'/'.join(hfid)}", tracker=tracker)

Apply the same change to ObjectStoreSync.get_file_by_hfid at lines 199-203.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrahub_sdk/object_store.py` around lines 113 - 117, The HFID query
parameters in async get_file_by_hfid are concatenated raw and must be
URL-encoded to avoid malformed URLs; change the params construction to use
urllib.parse.urlencode (e.g. urlencode([("hfid", h) for h in hfid])) or quote
each hfid before joining, then build the url as before and call _get_file; apply
the identical change to ObjectStoreSync.get_file_by_hfid so both sync and async
variants properly encode HFID values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@dev/specs/infp-504-artifact-composition/data-model.md`:
- Around line 68-69: Replace the truncated CLIENT_FILTER_NAMES tuple with the
full list of four filter names so the spec shows exact values; locate the
CLIENT_FILTER_NAMES symbol and expand its value to explicitly include
"artifact_content", "file_object_content", "file_object_content_by_id", and
"file_object_content_by_hfid" instead of using an ellipsis.

In `@dev/specs/infp-504-artifact-composition/spec.md`:
- Line 121: Update the InfrahubFilters Key Entities entry to explicitly list all
file-object-related filters by name: include artifact_content,
file_object_content, file_object_content_by_id, and file_object_content_by_hfid,
and mention that these are registered into the Jinja2 filter map when an
InfrahubClient is provided; locate the description for InfrahubFilters and
replace the current two-filter mention with the full list to ensure
completeness.

In `@infrahub_sdk/object_store.py`:
- Around line 113-117: The HFID query parameters in async get_file_by_hfid are
concatenated raw and must be URL-encoded to avoid malformed URLs; change the
params construction to use urllib.parse.urlencode (e.g. urlencode([("hfid", h)
for h in hfid])) or quote each hfid before joining, then build the url as before
and call _get_file; apply the identical change to
ObjectStoreSync.get_file_by_hfid so both sync and async variants properly encode
HFID values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 626eb80d-2e4a-439c-95fb-fe277f245a6f

📥 Commits

Reviewing files that changed from the base of the PR and between 8a71161 and 409ffaf.

📒 Files selected for processing (12)
  • dev/specs/infp-504-artifact-composition/contracts/filter-interfaces.md
  • dev/specs/infp-504-artifact-composition/data-model.md
  • dev/specs/infp-504-artifact-composition/quickstart.md
  • dev/specs/infp-504-artifact-composition/spec.md
  • docs/_templates/sdk_template_reference.j2
  • docs/docs/python-sdk/reference/templating.mdx
  • infrahub_sdk/object_store.py
  • infrahub_sdk/template/__init__.py
  • infrahub_sdk/template/filters.py
  • infrahub_sdk/template/infrahub_filters.py
  • tasks.py
  • tests/unit/sdk/test_infrahub_filters.py

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

🧹 Nitpick comments (2)
infrahub_sdk/template/infrahub_filters.py (1)

36-62: Consider consolidating validation logic with _fetch_file_object.

The artifact_content filter duplicates the null/empty validation and exception wrapping logic that _fetch_file_object encapsulates. While the underlying API call differs (object_store.get vs get_file_by_*), the helper could still be reused here.

Optional refactor to reduce duplication
     async def artifact_content(self, storage_id: str) -> str:
         """Retrieve artifact content by storage_id."""
         client = self._require_client(filter_name="artifact_content")
-        if storage_id is None:
-            raise JinjaFilterError(
-                filter_name="artifact_content",
-                message="storage_id is null",
-                hint="ensure the GraphQL query returns a valid storage_id value",
-            )
-        if not storage_id:
-            raise JinjaFilterError(
-                filter_name="artifact_content",
-                message="storage_id is empty",
-                hint="ensure the GraphQL query returns a non-empty storage_id value",
-            )
-        try:
-            return await client.object_store.get(identifier=storage_id)
-        except AuthenticationError as exc:
-            raise JinjaFilterError(
-                filter_name="artifact_content", message=f"permission denied for storage_id: {storage_id}"
-            ) from exc
-        except Exception as exc:
-            raise JinjaFilterError(
-                filter_name="artifact_content",
-                message=f"failed to retrieve content for storage_id: {storage_id}",
-                hint=str(exc),
-            ) from exc
+        return await self._fetch_file_object(
+            filter_name="artifact_content",
+            identifier=storage_id,
+            label="storage_id",
+            fetch=lambda: client.object_store.get(identifier=storage_id),
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrahub_sdk/template/infrahub_filters.py` around lines 36 - 62, The
artifact_content function duplicates null/empty checks and exception wrapping
already handled by _fetch_file_object; refactor by moving the shared
validation/exception logic into _fetch_file_object (or extend _fetch_file_object
to accept a retrieval strategy/callable) and have artifact_content call
_fetch_file_object with a callable that invokes
client.object_store.get(identifier=storage_id); remove the duplicated null/empty
checks and exception handling from artifact_content and ensure JinjaFilterError
still includes filter_name="artifact_content" when propagated.
dev/specs/infp-504-artifact-composition/spec.md (1)

99-106: Add blank lines around the bullet list per markdown guidelines.

The bullet list in FR-004 should have blank lines before and after it for proper markdown formatting.

Proposed fix
 - **FR-004**: The system MUST provide three file object content filters, each retrieving content via a different identifier:
+
   - `file_object_content` — accepts a `storage_id` string, uses `GET /api/files/by-storage-id/{storage_id}`
   - `file_object_content_by_id` — accepts a node UUID string, uses `GET /api/files/{node_id}`
   - `file_object_content_by_hfid` — accepts an HFID string or list and a required `kind` argument, uses `GET /api/files/by-hfid/{kind}?hfid=...`
+
   All three share the same binary content-type rejection and error handling behavior.

As per coding guidelines: "When editing .md or .mdx files, add blank lines before and after lists."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev/specs/infp-504-artifact-composition/spec.md` around lines 99 - 106, The
markdown list under FR-004 needs surrounding blank lines for correct formatting;
edit the FR-004 block in spec.md to insert a blank line immediately before the
bullet list and another blank line after the list (so the three bullets for
file_object_content, file_object_content_by_id, and file_object_content_by_hfid
are separated by blank lines from the paragraphs above and below); ensure the
rest of the FR-00x blocks keep the same spacing convention (FR-005, FR-006,
FR-007) for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@infrahub_sdk/template/infrahub_filters.py`:
- Around line 114-130: In file_object_content_by_hfid, validate that when hfid
is a list every element is a non-empty string before calling _fetch_file_object;
currently hfid_list (created from hfid) can contain empty strings which are sent
to client.object_store.get_file_by_hfid. Update file_object_content_by_hfid to
build hfid_list = hfid if isinstance(hfid, list) else [hfid], then check
all(isinstance(x, str) and x.strip() for x in hfid_list) and raise
JinjaFilterError with filter_name="file_object_content_by_hfid" and an
appropriate message/hint if any element is empty or non-string, and pass
hfid_list as the identifier/argument to _fetch_file_object/get_file_by_hfid.

---

Nitpick comments:
In `@dev/specs/infp-504-artifact-composition/spec.md`:
- Around line 99-106: The markdown list under FR-004 needs surrounding blank
lines for correct formatting; edit the FR-004 block in spec.md to insert a blank
line immediately before the bullet list and another blank line after the list
(so the three bullets for file_object_content, file_object_content_by_id, and
file_object_content_by_hfid are separated by blank lines from the paragraphs
above and below); ensure the rest of the FR-00x blocks keep the same spacing
convention (FR-005, FR-006, FR-007) for consistency.

In `@infrahub_sdk/template/infrahub_filters.py`:
- Around line 36-62: The artifact_content function duplicates null/empty checks
and exception wrapping already handled by _fetch_file_object; refactor by moving
the shared validation/exception logic into _fetch_file_object (or extend
_fetch_file_object to accept a retrieval strategy/callable) and have
artifact_content call _fetch_file_object with a callable that invokes
client.object_store.get(identifier=storage_id); remove the duplicated null/empty
checks and exception handling from artifact_content and ensure JinjaFilterError
still includes filter_name="artifact_content" when propagated.
🪄 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: d2473a20-0f75-43b6-af48-d198b6be56db

📥 Commits

Reviewing files that changed from the base of the PR and between 409ffaf and 9fb3854.

📒 Files selected for processing (5)
  • dev/specs/infp-504-artifact-composition/data-model.md
  • dev/specs/infp-504-artifact-composition/spec.md
  • infrahub_sdk/template/__init__.py
  • infrahub_sdk/template/infrahub_filters.py
  • tests/unit/sdk/test_infrahub_filters.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrahub_sdk/template/init.py
  • tests/unit/sdk/test_infrahub_filters.py

Copy link
Copy Markdown
Contributor

@ogenstad ogenstad left a comment

Choose a reason for hiding this comment

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

LGTM. Looks like CodeRabbit has a point with the comment around "Edge case: empty strings within HFID list are not validated."

| Name | CORE | WORKER | LOCAL |
| ---- | ---- | ------ | ----- |
{% for filter in infrahub %}
| `{{ filter.name }}` | {% if filter.core %}✅{% else %}❌{% endif %} | {% if filter.worker %}✅{% else %}❌{% endif %} | {% if filter.local %}✅{% else %}❌{% endif %} |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it make sense to report on all filters using the same kind of table? I.e. for the previous table we only have the "trusted" option? Might be easier to understand if the format for the trust model is the same for all filter types?

@gmazoyer gmazoyer merged commit 3e93fe2 into infrahub-develop Mar 30, 2026
21 checks passed
@gmazoyer gmazoyer deleted the gma-20260327-infp504 branch March 30, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants