feat(data-warehouse): implement katana import source - #67834
Merged
Conversation
Contributor
|
Hey @Gilbert09! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
Contributor
|
Reviews (1): Last reviewed commit: "feat(data-warehouse): implement katana i..." | Re-trigger Greptile |
MarconLP
approved these changes
Jul 2, 2026
Implements the Katana Cloud Inventory (Katana MRP) warehouse source end-to-end as a ResumableSource over Katana's v1 REST API. - settings.py: 18-endpoint catalog (products, materials, variants, services, customers, suppliers, sales/purchase/manufacturing orders, sales returns, stock adjustments/transfers, stocktakes, inventory movements, plus full-refresh inventory/price_lists/locations/tax_rates) with per-endpoint primary keys, created_at partition keys, and updated_at/created_at incremental fields. - katana.py: tracked-session transport with page-number pagination, ~1 req/s throttling and Retry-After honoring backoff for the 60 req/min limit, ISO 8601 timestamp filters, and page-level resumable state. - source.py: KatanaSource (alpha, unreleasedSource) with validate_credentials, get_schemas, get_non_retryable_errors, resumable manager wiring, and canonical descriptions. - Tests, SOURCES.md move to Implemented, regenerated generated_configs.py. Generated-By: PostHog Code Task-Id: 48a199c3-48b3-4eb9-90cd-da1466f375a4
Split the retry-wrapped `_fetch_page` into a pure `_request_page` so tests can call it directly instead of reaching for tenacity's `__wrapped__` attribute (invisible to mypy). Pass `response=` when constructing the test HTTPError to match the typed signature. Generated-By: PostHog Code Task-Id: 48a199c3-48b3-4eb9-90cd-da1466f375a4
A 2xx response with valid JSON but no `data` key (changed envelope, maintenance
page) previously read as an empty page and silently ended pagination mid-sync. Treat
a missing `data` key as a retryable error so the sync retries and then fails loudly
instead of losing data. Genuine empty results (`{"data": []}`) still terminate
pagination normally.
Generated-By: PostHog Code
Task-Id: 48a199c3-48b3-4eb9-90cd-da1466f375a4
Register the Katana API key with the tracked HTTP transport (redact_values) in both the credential probe and the sync session so it's masked in logged URLs and captured samples. Replace the bare raise_for_status() 4xx path with a manually built HTTPError whose URL is scrubbed to scheme/host/path (no query, no userinfo), keeping the stable "... for url: https://api.katanamrp.com..." prefix that get_non_retryable_errors() matches on. Read the envelope's data key directly now that _request_page validates it, so a regression in that guard fails loudly instead of silently ending the sync. Generated-By: PostHog Code Task-Id: b1bc573d-e0ed-4097-b013-da9d35b2b2d0
…ed error The 401/403 non-retryable errors are now raised by _request_page with a scrubbed URL rather than response.raise_for_status(); update the comment to match. Generated-By: PostHog Code Task-Id: b1bc573d-e0ed-4097-b013-da9d35b2b2d0
Gilbert09
force-pushed
the
posthog-code/katana-import-source
branch
from
July 3, 2026 04:15
fc7eb17 to
63c9ea2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PostHog customers who run manufacturing and inventory on Katana Cloud Inventory (formerly Katana MRP) have no way to pull their ERP data into the data warehouse. Katana was a scaffolded-only source (registered, empty stub, hidden). This turns it into a working connector so users can join products, orders, and inventory with their product analytics.
Changes
Implements the Katana source end-to-end as a
ResumableSourceover Katana's v1 REST/JSON API (https://api.katanamrp.com/v1, Bearer API key).settings.py— declarative catalog of 18 endpoints. Incremental (server-sideupdated_at/created_attimestamp filters,created_atpartition key): products, materials, variants, services, customers, suppliers, sales/purchase/manufacturing orders, sales returns, stock adjustments/transfers, stocktakes, and inventory movements (created_at only, immutable log). Full refresh only (no server-side timestamp filter): inventory (composite[variant_id, location_id]key), price_lists, locations, tax_rates.katana.py— transport overmake_tracked_session(): page-number pagination (page/limit=250), a ~1 req/s throttle plusRetry-After-honoring backoff for Katana's 60 req/min limit, ISO 8601<field>_minfilters, and page-level resumable state saved after each yielded batch.source.py—KatanaSourcewithvalidate_credentials(cheap/user_infotoken probe),get_schemas,get_non_retryable_errors(401/403), resumable manager wiring, and canonical table/column descriptions. ShippedreleaseStatus=alphaand kept behindunreleasedSource=Trueper the task.canonical_descriptions.py— curated table/column docs from Katana's official API reference.katanainto the Implemented table inSOURCES.mdand regeneratedgenerated_configs.py.Incremental design note
Katana list endpoints always return newest-first by
created_atand expose no sort override, so the source declaressort_mode="desc"(the pipeline finalizes the max-cursor watermark only at the end for desc sources, which stays correct despite the fixed order). The timestamp filter is a plain query param applied to every page, so incremental syncs terminate naturally at the last page with no unbounded history re-walk. Resume is page-based and idempotent (re-reading a page on resume is deduped by the delta merge).How did you test this code?
I (Claude) verified the API contract with live unauthenticated
curlagainstapi.katanamrp.com(confirmed the{"data": [...]}envelope,page/limitparams,created_at_min/updated_at_min/created_at_max/updated_at_maxfilters, newest-first ordering, 60 req/60s rate limit withX-RateLimit-*+Retry-Afterheaders, and 401/400 error shapes) and the published OpenAPI spec at/v1/openapi.json.Automated tests I ran locally (61 new, all passing):
test_katana.py— transport: timestamp formatting, future-cursor clamping, incremental param building (per-endpoint, user-chosen field, full-refresh no-filter), pagination termination on a short page, resume-from-saved-page, per-page filter application, 429/5xx/401 status handling, Retry-After backoff, and per-endpointSourceResponseshape.test_katana_source.py— source class: config fields, category,get_schemasincremental vs full-refresh per endpoint, credential validation, resumable manager binding,source_for_pipelineplumbing, and canonical-description key integrity.I also re-ran
test_generated_configs.py,test_source_config_generator.py, andtest_source_categories.py(all pass), regeneratedgenerated_configs.py(only diff is Katana'sapi_keyfield), and ranruff check/ruff format.I could not run an authenticated end-to-end sync (no Katana API key available), so server-side filter honoring on the less common endpoints is inferred from the OpenAPI spec plus docs rather than a live authenticated smoke test. This is called out in a code comment. Correctness holds either way: the desc + per-page filter + primary-key merge design produces correct data even if a filter is silently ignored (worst case it fetches more than needed).
Docs update
The user-facing doc lives in the posthog.com repo, which isn't checked out in this environment, so
audit_source_docswas not run. The doc is written and ready to land atcontents/docs/cdp/sources/katana.md(matchingdocsUrl); its full content is in the agent context below.🤖 Agent context
Autonomy: Fully autonomous
Authored by Claude Code. Skills invoked:
implementing-warehouse-sources(followed the source.py/settings.py/katana.py split, base-class choice, incremental and tracked-transport rules),documenting-warehouse-sources(doc template), andwriting-testsconventions.Key decisions:
ResumableSource(not+WebhookSource). The research recommended adding webhooks, but full webhook plumbing (HogFunction template, create/delete, resource map) can't be verified without a live account, and the core value is the pull-based incremental sync. Shipped resumable-only and left webhooks as a clear future enhancement rather than landing an untested surface.sort_mode="desc". Katana exposes no sort override and returns newest-first, so ascending was not an option. Verified against the pipeline's desc watermark handling.FINANCE___ACCOUNTING(ERP) over the scaffold'sE_COMMERCE, since the enum lists ERP there and Katana is an MRP/ERP.generated_configs.py(no DB available for the generator initially), then installed deps and re-ran the generator to confirm the output matched exactly.Doc content to add to posthog.com at
contents/docs/cdp/sources/katana.md: