Skip to content

Initial support for file upload#71

Open
minitriga wants to merge 14 commits into
stablefrom
001-object-file-support
Open

Initial support for file upload#71
minitriga wants to merge 14 commits into
stablefrom
001-object-file-support

Conversation

@minitriga

@minitriga minitriga commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Initial support for file upload

Summary

Add CoreFileObject support to nornir-infrahub with two new Nornir task functions:

  • upload_file_object — Upload a local file (or raw bytes) to an Infrahub node that inherits from CoreFileObject. Creates the node if it doesn't exist, updates it if the file changed, and skips the upload entirely when the SHA-1 checksum matches (idempotent). On update, data attribute changes are persisted even when the file content is unchanged.
  • download_file_object — Download file content from a CoreFileObject node. Returns base64-encoded binary plus a UTF-8 text view for known text MIME types. Optionally writes to a local save_to path; skips the write when the local file already matches the server checksum.

Mirrors the CoreFileObject support added to the Ansible collection in opsmill/infrahub-ansible#317.

Changes

  • nornir_infrahub/plugins/tasks/file_object.py — new module with the two task functions, helpers (_validate_file_object_kind, _lookup_existing_object, _resolve_upload_source), and the TEXT_MIME_TYPES constant. Idempotency and checksum comparison are delegated to the SDK via node.upload_if_changed(source, name) and node.matches_local_checksum(source).
  • nornir_infrahub/plugins/tasks/__init__.py — export the two new functions.
  • nornir_infrahub/utils.py — small get_client(task) helper shared between the file_object tasks (ready for artifact.py to adopt as a follow-up).
  • tests/unit/test_file_object.py — 28 unit tests covering create, update, idempotent skip, attribute-only updates on the skip path, rejection of relationship and unknown keys in data on update, invalid kind, missing file, explicit object_id miss, ambiguous filename lookup, text/binary MIME download, save_to dir-vs-file handling, local-match skip, and overwrite-on-mismatch.
  • pyproject.toml — bump infrahub-sdk to >=1.20.1,<2 (first release shipping upload_if_changed / matches_local_checksum / UploadResult from opsmill/infrahub-sdk-python#963); scope PLR0911/PLR0912/PLR0913 ignores to the tasks directory; keep the three ty rule disables that cover broad SDK-dynamic-type patterns and drop the two that inline # type: ignore comments already cover.
  • docs/ — auto-generated plugin reference for the two new tasks plus generator fixes the rebase surfaced (pipe-escaping in type names, multi-line description flattening, corrected relative path in _plugin_index.mdx, MD012/MD060 disables folded into the canonical .markdownlint.yml and the redundant .markdownlint.json removed).

Usage

from nornir_infrahub.plugins.tasks import upload_file_object, download_file_object

# Upload a file (forwards **kwargs to client.create — e.g. allow_upsert, timeout)
result = nr.run(
    task=upload_file_object,
    kind="AvdHostvarFile",
    file_path="/path/to/hostvars.json",
)

# Download a file (save_to writes to disk; binary/text always returned)
result = nr.run(
    task=download_file_object,
    kind="AvdHostvarFile",
    object_id="abcd-1234-efgh-5678",
    save_to="/tmp/downloads/",
)

Behavior notes:

  • **kwargs are forwarded to client.create and only apply on the create path; they are not used when updating an existing object.
  • On update, the data dict only sets attributes; passing a relationship key or an unknown key fails the task instead of being silently dropped (use the SDK directly to change relationships on existing nodes).
  • A typo'd explicit object_id fails the task rather than silently creating a new node; upsert-on-miss is still in effect for hfid and the filename fallback.

Test plan

  • Unit tests pass (45/45 — 28 new file_object + 17 existing inventory)
  • ruff check / ruff format --check pass
  • pylint 10.00/10
  • ty check passes
  • markdown-lint / vale / docs build pass on CI across Python 3.10–3.13
  • Integration tested against a live Infrahub via test: add integration test suite for InfrahubInventory (NORNIR-5) #79's testcontainers harness. tests/integration/test_file_object.py adds a Testing.TestFile schema inheriting from CoreFileObject and covers upload lifecycle (create → idempotent skip → update), download round-trip, download-skip on local match, metadata-on-skip persistence, explicit-object_id not-found failure, and unknown-data-key rejection. 6/6 pass locally against testcontainers in ~70s; excluded from CI by test: add integration test suite for InfrahubInventory (NORNIR-5) #79's -m 'not integration' addopts (Docker-only). Run with pytest tests/integration/test_file_object.py -m integration.

@minitriga minitriga requested a review from a team as a code owner April 10, 2026 10:42
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 10, 2026

Copy link
Copy Markdown

Deploying nornir-infrahub with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9de8e3b
Status: ✅  Deploy successful!
Preview URL: https://f9a351b0.nornir-infrahub.pages.dev
Branch Preview URL: https://001-object-file-support.nornir-infrahub.pages.dev

View logs

@minitriga minitriga force-pushed the 001-object-file-support branch from a9bccf9 to e155a78 Compare April 10, 2026 12:21
@wvandeun

Copy link
Copy Markdown
Contributor

We should not use the term node but object

)


def _get_client(task: Task) -> InfrahubClientSync:

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.

We should probably move this a utils module. This could be useful in other contexts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved to nornir_infrahub/utils.py as get_client(task) and imported from file_object.py. Good for the artifact.py tasks to adopt too; left that for a follow-up to keep this PR focused. (commit b35f894)

@wvandeun

Copy link
Copy Markdown
Contributor

The SDK provides a capability to create a FileObject from a bytes object as well. We should probably add support for this https://docs.infrahub.app/python-sdk/sdk_ref/infrahub_sdk/node#upload_from_bytes

data = data or {}

# Look up existing node
existing_node = None

@wvandeun wvandeun Apr 19, 2026

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.

Do we need to implement this here? AFAIK the SDK behaves in a idempotent way already when using allow_upsert=True. Ofcourse this requires that you have setup a proper unique constraint and/or HFID

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

allow_upsert=True avoids the duplicate-create, but it still re-uploads the file bytes on every call and doesn't let us report changed=False to Nornir — the whole point of the client-side SHA-1 compare is (a) skip the upload entirely when the content hasn't changed, and (b) drive Nornir's changed flag correctly (matches how nornir_utils.write_file behaves). I'd like to keep the checksum-skip for those reasons. That said, allow_upsert is now reachable via the new **kwargs passthrough, so callers who do want upsert semantics on create can opt in.

data: dict[str, Any] | None = None,
node_id: str | None = None,
hfid: list[str] | None = None,
branch: str | None = None,

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.

Should we support kwargs here as well, to pass arguments to client.create

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added **kwargs passthrough on upload_file_object. It forwards to client.create(...), so callers can pass allow_upsert, timeout, request_context, etc. New test test_upload_forwards_kwargs_to_create covers it.

def upload_file_object(
task: Task,
kind: str,
file_path: str,

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.

Use Path for file_path?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. file_path now accepts str | Path and we normalise to Path internally.

kind: str,
node_id: str | None = None,
hfid: list[str] | None = None,
dest: str | None = None,

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.

Use Path here? Also we could come up with a better name for this argument that is a little bit more descriptive?

Also we can probably make this behave in a idempotent fashion? IE the task result should report only if a change was made when a file really changed? Similar to how the write_file in nornir_utils behaves?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All three addressed:

  • Path: save_to accepts str | Path.
  • Rename: destsave_to (more descriptive — "where to save the file locally").
  • Idempotent: download_file_object now compares the local file's SHA-1 to the server checksum before downloading. If they match the write is skipped and changed=False; otherwise we download, overwrite, and changed=True. Same model as nornir_utils.plugins.tasks.files.write_file. Covered by new tests test_download_skips_write_when_local_matches and test_download_overwrites_when_local_differs.

@minitriga

Copy link
Copy Markdown
Contributor Author

@wvandeun — review feedback addressed in b35f894. Summary:

Top-level comments

  • "object" not "node"node_idobject_id, internal node/existing_node/new_nodeobj/existing_obj/new_obj, and docstrings/messages updated throughout.
  • upload_from_bytes supportupload_file_object now accepts either file_path (disk) or content + file_name (raw bytes). The two are mutually exclusive; the SHA-1 idempotency and **kwargs passthrough work for both paths.

Inline comments — replied on each thread; in short:

  • _get_clientnornir_infrahub.utils.get_client.
  • file_path and save_to accept str | Path.
  • dest renamed to save_to.
  • download_file_object is now idempotent (skips the write and reports changed=False when the local file's SHA-1 matches the server checksum).
  • **kwargs forwarded to client.createallow_upsert, timeout, request_context all reachable.
  • Kept the client-side checksum-skip on upload; explained in the thread (TL;DR: allow_upsert=True still re-uploads bytes and always reports changed=True, which is not what we want here).

Tests: 38/38 pass. Ruff, ruff format, ty, pylint all clean.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 19 files

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread .vale/styles/Infrahub/sentence-case.yml Outdated

```python
from nornir import InitNornir
from nornir.core.plugins.inventory import InventoryPluginRegister

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.

these 2 imports should not be required

from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory

def main():
# Register the custom InfrahubInventory plugin

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.

this is not required

@wvandeun

Copy link
Copy Markdown
Contributor

If we need the idempotent behavior for both uploading and downloading, then it should not really be part of the nornir plugin, but it should be provided by the SDK itself. If not we are going to have to re-implement this in all integrations.

@BeArchiTek BeArchiTek requested review from lancamat1 and removed request for lancamat1 May 19, 2026 13:32
@lancamat1 lancamat1 force-pushed the 001-object-file-support branch from 585dcf2 to 982962f Compare May 27, 2026 10:24
lancamat1 added a commit that referenced this pull request May 27, 2026
…ical .markdownlint.yml

PR #71 originally added a root .markdownlint.json, but the repo's canonical
config is .markdownlint.yml (docusaurus-aware: MD033/MD007/MD029/MD047 etc.).
markdownlint-cli prefers .json over .yml, so our file shadowed the canonical
one and #55's guide/topic docs failed markdown-lint after the rebase. Remove
the .json and add the two rules the generated plugin reference tables need
(MD012 multiple-blanks, MD060 table padding) to .markdownlint.yml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lancamat1 added a commit that referenced this pull request May 27, 2026
…ical .markdownlint.yml

PR #71 originally added a root .markdownlint.json, but the repo's canonical
config is .markdownlint.yml (docusaurus-aware: MD033/MD007/MD029/MD047 etc.).
markdownlint-cli prefers .json over .yml, so our file shadowed the canonical
one and #55's guide/topic docs failed markdown-lint after the rebase. Remove
the .json and add the two rules the generated plugin reference tables need
(MD012 multiple-blanks, MD060 table padding) to .markdownlint.yml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lancamat1 lancamat1 force-pushed the 001-object-file-support branch from 1043710 to 8c45a30 Compare May 27, 2026 10:31

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 16 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread nornir_infrahub/plugins/tasks/file_object.py Outdated
Comment thread docs/docs/references/plugins/_plugin_index.mdx Outdated
Comment thread nornir_infrahub/plugins/tasks/file_object.py Outdated
Comment thread docs/docs/references/plugins/infrahub_inventory.mdx Outdated
Comment thread docs/docs/references/plugins/file_object_tasks.mdx Outdated
@lancamat1

Copy link
Copy Markdown
Contributor

Testing status

This refactor (moving the CoreFileObject upload/download tasks onto the new infrahub-sdk primitives upload_if_changed / matches_local_checksum, requiring infrahub-sdk ≥ 1.20.1) is currently covered by unit tests only (mocked SDK boundary). It has not yet been integration-tested against a live Infrahub.

Integration coverage is intentionally deferred until #79 (the testcontainers-based integration harness) merges to stable. Once it lands, I'll rebase this branch onto stable and add tests/integration/test_file_object.py — a CoreFileObject-inheriting schema fixture exercising upload-create → idempotent-skip → update → download → download-skip against a live Infrahub.

Holding off on merge until that integration pass is done.

@minitriga minitriga left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code Review

Overview

Adds two new Nornir tasks (upload_file_object, download_file_object) for Infrahub CoreFileObject nodes, mirroring opsmill/infrahub-ansible#317. Includes a shared get_client() helper, 28 unit tests, doc-template fixes, and an SDK bump. Overall well-built: idempotency semantics are correct, error handling follows the Nornir Result(failed=True) pattern, and test coverage is thorough. SDK interaction details verified below.

Verified correctness ✅

  • SDK methods exist and behave as assumed. upload_if_changed() / matches_local_checksum() are absent in 1.19.0 and present in 1.20.1 (checked both), so the bump to >=1.20.1 is genuinely required.
  • Attrs + file both changed is safe. Attributes are staged via setattr before upload_if_changed(), and the SDK's upload_if_changed() calls self.save() on upload — staged attrs persist. The explicit save() on the skip path covers the other branch. Tests cover both (TestUploadAttrsOnSkip).
  • Download checksum guard is correct. matches_local_checksum() raises ValueError when there's no server checksum, but the code guards with server_checksum and ... first.

Issues

1. Relationships in data are silently dropped on update (medium)

The update path in file_object.py only applies keys found in existing_obj._schema.attribute_names:

if attr_name in existing_obj._schema.attribute_names:

On create, relationships in data flow through client.create(data=data) fine. On update, they're silently ignored. The PR's own usage example passes data={"artifact": artifact_id} — presumably a relationship — so the documented example works on first run and silently no-ops on subsequent runs. Either handle relationship_names too, or document that data only updates attributes on existing objects (and ideally warn/fail on unknown keys instead of skipping).

2. Nonexistent explicit object_id falls through to create (medium)

_lookup_existing_object returns None on NodeNotFoundError regardless of how the object was identified. If a user passes a typo'd UUID, the task creates a new node instead of failing. Upsert-on-miss is defensible for hfid or the file_name fallback, but an explicit UUID that doesn't exist is almost certainly an error — consider failing there.

3. PR description is stale (low)

  • Says bump to >=1.19.0; the diff bumps to >=1.20.1 (correctly).
  • Names upload_from_path(), download_file(), is_file_object() as the motivating methods; the code actually depends on upload_if_changed() and matches_local_checksum() (1.20.x-only).

4. Global ty rule disables are too blunt (low)

pyproject.toml adds project-wide invalid-return-type = "ignore" and invalid-assignment = "ignore", while file_object.py also carries inline # type: ignore[return-type] / [assignment] comments for the same spots. The inline ignores make the global disables redundant — and the global ones silence those rules for all future code. Prefer keeping only the inline ignores.

5. Minor nits

  • _lookup_existing_object only catches NodeNotFoundError; the file_name__value fallback will raise an unhandled IndexError from the SDK if multiple nodes share a file name (Nornir still marks the task failed, but with a raw traceback rather than a clean message).
  • str(save_to).endswith("/") won't detect a trailing \ on Windows — save_to_path.is_dir() covers existing dirs, so this only affects not-yet-created directory paths.
  • download_file_object holds full content in memory and embeds base64 in every Result — Nornir aggregates results across hosts, so this can get heavy for large binaries across many hosts. Maybe worth a docstring note.
  • **kwargs is forwarded only on the create path, not update — documented that way, just noting the asymmetry (e.g. timeout won't apply to updates).

Code quality & conventions 👍

  • utils.get_client() matches the existing node._client access pattern in artifact.py — a follow-up refactoring artifact.py onto the helper would be nice.
  • Pylint per-file ignores (PLR0911/0912/0913) are scoped to tasks/** with explanatory comments — appropriate.
  • Doc template fixes (pipe-escaping union types, newline flattening in table cells, empty-params guard) are real bug fixes for the generator, and the broken ./references/plugins/ link prefix in readme.mdx.j2 is correctly fixed for where _plugin_index.mdx lands.
  • Tests are mock-based but assert the right seams (create kwargs passthrough, no-save on no-change, overwrite vs. skip on download). Gaps: no test for the file_name__value fallback lookup, and no test pinning the relationship-on-update behavior from issue 1.

Verdict

Approve with minor changes. Issues 1 and 2 are real behavioral surprises worth fixing (or at minimum documenting) before merge; the rest is polish. The core upload/download logic, idempotency handling, and SDK usage are correct — verified against infrahub-sdk 1.20.1 source.


🤖 Generated with Claude Code

minitriga and others added 7 commits June 4, 2026 12:14
Add upload_file_object and download_file_object Nornir task functions
for managing files on Infrahub nodes that inherit from CoreFileObject.

Upload supports SHA-1 checksum idempotency (skip re-upload when file
is unchanged). Download returns base64-encoded binary content with
UTF-8 text decoding for text MIME types, with optional local save.

- Bump infrahub-sdk lower bound to >=1.19.0
- Add 13 unit tests covering create, update, skip, download, and error cases
- Add plugin reference documentation and sidebar entry
- Fix markdownlint, yamllint, and vale for CI compliance
- rename node_id/dest to object_id/save_to (use "object" not "node")
- accept str | Path for file_path and save_to
- add upload_from_bytes support via content/file_name args
- forward **kwargs to client.create (allow_upsert, timeout, ...)
- download_file_object is now idempotent: skip write when local SHA-1 matches server
- move _get_client into nornir_infrahub.utils for reuse

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- plugin.mdx.j2 now escapes `|` in type names and flattens newlines in
  descriptions so types like `str | Path` and multi-line descriptions
  render as valid markdown tables (fixes markdown-lint MD055/MD056).
- .vale/styles/Infrahub/sentence-case.yml: update stale "CoreFileObject
  node" exception to "CoreFileObject object" to match the renamed heading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#55 reworked the docs generator and added _plugin_index.mdx. Regenerate
all plugin reference docs with the current template (escapes pipes in
type names, flattens multi-line descriptions) and add the CoreFileObject
section to the manually-maintained readme.mdx index.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…al_checksum

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lancamat1 and others added 7 commits June 4, 2026 12:15
…ical .markdownlint.yml

PR #71 originally added a root .markdownlint.json, but the repo's canonical
config is .markdownlint.yml (docusaurus-aware: MD033/MD007/MD029/MD047 etc.).
markdownlint-cli prefers .json over .yml, so our file shadowed the canonical
one and #55's guide/topic docs failed markdown-lint after the rebase. Remove
the .json and add the two rules the generated plugin reference tables need
(MD012 multiple-blanks, MD060 table padding) to .markdownlint.yml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- _lookup_existing_object now catches only NodeNotFoundError, so real API
  errors surface instead of silently triggering a create (review #1)
- upload_file_object persists data attribute changes even when the file
  checksum matches, instead of dropping them on the skip path (review #2)
- document **kwargs as (Any, optional) so generated docs show the right
  type and optionality (review #3)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- mark schema_mappings/group_mappings optional in the InfrahubInventory
  docstring so the generated table shows Required=No, matching the
  Optional[...] = None signature (review #4)
- generate _plugin_index.mdx links relative to its own location instead of
  prepending references/plugins/, which produced broken doubled paths (review #5)
- regenerate plugin reference docs; file_object_tasks.mdx now shows the
  **kwargs row as type Any / Required No (review #3)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- reject relationship and unknown keys in 'data' on update instead of
  silently dropping them; document that 'data' on update only sets
  attributes (review #1)
- re-raise NodeNotFoundError when the caller passed an explicit object_id,
  so a typo'd UUID fails instead of creating a new node (review #2)
- convert the SDK's IndexError on ambiguous filename lookup into a clean
  failed Result naming the conflict (review #5a)
- detect Windows trailing separator on save_to (review #5b)
- docstring notes: download memory/base64 footprint (review #5c) and
  **kwargs being create-only (review #5d)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pyproject's [tool.ty.rules] disabled invalid-return-type and invalid-assignment
project-wide while file_object.py also carried inline # type: ignore[...]
comments at every relevant spot — the globals silenced those rules for all
future code unnecessarily. Drop the two globals (keep the three that cover
broader SDK-dynamic-type patterns) and move the existing return-type ignore
on _validate_file_object_kind from the def line to the actual return line
so ty honours it (review #4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
upload_file_object's create path passes data={} to client.create when the
caller supplies neither data nor **kwargs, but the SDK rejects that with
'Either data or a list of keywords must be provided' — a bare upload
fails before upload_if_changed gets to set the real file_name.

Seed data['file_name'] from the upload source when there is nothing else
to send. upload_if_changed refreshes file_name from the actual content
before save, so the seed is invisible in the persisted object.

Discovered by the new CoreFileObject integration tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds tests/integration/test_file_object.py running against a live Infrahub
spun up by #79's testcontainers harness. Loads a TestingTestFile schema
that inherits from CoreFileObject plus a label attribute, and exercises:

- upload_lifecycle: create → idempotent skip → update on content change
- download_returns_content: bytes round-trip via base64
- download_skip_when_local_matches: second download is a no-op
- data_attr_persists_on_skip: label is saved even when the file content
  is unchanged (verifies the metadata-on-skip behaviour)
- explicit_object_id_not_found_fails: typo'd UUID fails cleanly
- unknown_key_in_data_fails_on_update: bogus data key is rejected

Run with 'pytest -m integration' (requires Docker). All 6 pass locally
against the testcontainers Infrahub in ~70s.

Closes the integration-testing follow-up referenced in the testing-status
comment on PR #71 (post-#79).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lancamat1 lancamat1 force-pushed the 001-object-file-support branch from c19c27e to 9de8e3b Compare June 4, 2026 10:24
@lancamat1

Copy link
Copy Markdown
Contributor

Integration tests landed

Following up on the testing-status note — now that #79 is merged, this PR has been rebased onto stable and tests/integration/test_file_object.py was added in 9de8e3b, extending the testcontainers harness with a Testing.TestFile kind that inherits from CoreFileObject.

Six integration tests, all passing locally against testcontainers in ~70s:

  • upload lifecycle (create → idempotent skip → update on content change)
  • download round-trip via base64
  • download skip when the local file already matches the server checksum
  • data attribute persisted on the file-unchanged skip path (review Add CI files #2)
  • explicit object_id not-found fails cleanly (review Add CI files #2)
  • unknown data key on update fails cleanly (review Copy project from main repo #1)

The run also surfaced a real pre-existing UX bug: client.create(data={}) is rejected by the SDK, so a bare upload_file_object(task=…, kind=…, file_path=…) failed before upload_if_changed could set the real file_name. Fixed in b18c4f2 by seeding data['file_name'] from the source when both data and **kwargs are empty (upload_if_changed refreshes it before save, so the seed is invisible).

Run integration tests with: pytest tests/integration/test_file_object.py -m integration (requires Docker; not in CI by design).

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.

3 participants