Connector Options Reference
Polymo has no config file and no runtime. You describe a connector once in
the UI, and every field you fill in changes what
the exported Python script looks like. This page documents each option, the
JSON key it maps to (the same shape generate() and parse_config() accept
in Python, and what the UI's saved *.polymo.json files use
internally), and what it generates.
You will only write this JSON by hand if you are calling polymo.generate()
directly instead of using the form — see the API reference. Most
people never touch it.
Shape of a connector
A connector is one RestSourceConfig: a base URL, optional auth, and a
single stream (path + how to page through it, select records, and shape the
schema). In JSON form:
{
"version": "0.1",
"source": {
"type": "rest",
"base_url": "https://api.example.com"
},
"stream": {
"path": "/v1/items",
"infer_schema": true
}
}
That is enough for generate() to produce a script that fetches
https://api.example.com/v1/items once and infers columns from the
response — the same as filling in just Base URL and Stream Path in
the UI and leaving everything else at its default.
Base configuration
- Base URL (
source.base_url) — the root of every request. The generated script stores it as theBASE_URLconstant. Leave off the trailing slash. - Table name (
stream.name) — becomes the dp table name (@dp.table(name=...)), sanitized to a SQL identifier at export time. Optional: leave it blank and Polymo derives one from the stream path instead (e.g./v1/items→v1_items). - Stream Path (
stream.path) — appended toBASE_URLand stored asPATH. Must start with/. You can use placeholders like/repos/{owner}/{repo}; the UI's reader options are wired to fill these in — see Reader options below. - Streaming table (
stream.streaming) — every generated@dp.tableingests through a small SparkDataSourceregistered inline in the script — Lakeflow Declarative Pipelines requires a Spark data source either way, so batch tables get one too (see How the batch table reads below). Turning this on switches which kind: the inline source becomes aSimpleDataSourceStreamReaderand the table reads viaspark.readStream.format(...)instead ofspark.read.format(...). Requires an explicit schema andoffsetorpagepagination; not compatible with incremental sync or a partition strategy. - Response format (
stream.response_format,stream.xml_record_path) — see XML responses below.
Authentication
Set an Auth Type in the UI's Authentication section. It is not persisted in a saved config — you re-enter secrets each time you reopen a connector.
- None (default) — no auth added to requests.
- Bearer Token — the generated script gets an
API_TOKEN = "REPLACE_ME"constant near the top; edit it in place (or swap it for a secret-store lookup, e.g.dbutils.secrets.get(...)) before running the script. Maps tosource.auth.type = "bearer". - API Key — a first-class auth type. Choose a Placement (Header or
Query parameter) and a Name (e.g.
X-API-Keyfor a header, orapi_keyfor a query parameter). Maps tosource.auth = {"type": "api_key", "in": "header" | "query", "name": "<name>"}. The key value is never stored in the config, same as Bearer — the generated script gets anAPI_KEY = "REPLACE_ME"constant near the top (edit it in place, or swap it for a secret-store lookup, same as the Bearer/OAuth2 placeholders); at request time it is applied assession.headers["<name>"] = API_KEY(header placement) orparams["<name>"] = API_KEY(query placement). - OAuth 2.0 (Client Credentials) — maps to
source.auth = {"type": "oauth2", "token_url": ..., "client_id": ..., "scope": [...], "audience": ..., "extra_params": {...}}. The generated script gets aCLIENT_SECRET = "REPLACE_ME"constant and aget_token()helper that performs the client-credentials POST before every request.scopeis space-joined free text in the form;extra_paramsis a JSON object merged into the token request body.
Databricks secret-scope references
Instead of leaving the generated API_TOKEN / API_KEY / CLIENT_SECRET
constant as "REPLACE_ME", any auth secret slot can carry a reference to a
Databricks secret scope: add a secret object alongside the other auth
fields —
(the same shape works for api_key and oauth2's client_secret slot).
Both scope and key are required and must be non-empty — this is a
reference only; the config, the generated script, and the UI's
logs never contain the secret value itself. Codegen resolves it at runtime
via a generated _dbx_secret(scope, key) helper instead:
_dbx_secret is emitted once per script, only when at least one slot
references a secret. It resolves the value on the driver via
DBUtils(SparkSession.getActiveSession()).secrets.get(scope, key), and
raises a clear RuntimeError if called outside a Databricks cluster (no
active Spark session) — swap the call for the literal value if you need to
run the script somewhere else.
The same reference shape works for {{ options.<name> }} placeholders (see
Query parameters & headers below): set
stream.option_secrets.<name> to {"scope": ..., "key": ...} and the
resulting OPT_<NAME> constant resolves via _dbx_secret too. An
option_secrets entry for a name that isn't actually referenced anywhere is
harmless — it's simply unused, not a config error.
stream:
headers:
Authorization: "Basic {{ options.api_key_b64 }}"
option_secrets:
api_key_b64:
scope: my-scope
key: api-key-b64
The UI preview cannot resolve real Databricks secrets outside a
Databricks cluster: previewing a secret-ref config without a session token
sends the same "REPLACE_ME" dummy the unresolved-placeholder path already
does, so the request still fires (and fails/succeeds against the real API
exactly as an unresolved placeholder would); supplying a session token in
the preview UI overrides the relevant auth slot (bearer/api_key/oauth2)
with that token, the same way it does for a plain placeholder. OPT_*
secret refs have no override in preview and always get the dummy.
Setting a secret reference in the UI happens through the
Secret source toggle on each auth field (Bearer/API Key/OAuth2 Client
Secret) — switch it from "enter for preview / placeholder in export" to
"Databricks secret scope" and pick a scope + key from the dropdowns (backed
by databricks secrets list-scopes/list-secrets for the profile chosen on
the Deploy tab). See
Deploy to Databricks → Secrets for the UI walkthrough.
At deploy time, this reference resolves differently in a bundle project
than in the exported script above. src/<pkg>/client.py — the file the
Deploy tab bootstraps under a
project's src/ directory — ships as an installed wheel, so Spark
reconstructs its DataSource on every executor with a fresh
import <pkg>.client and no Spark session available; a module-level
_dbx_secret(...) call there would fail on every read. So a secret-ref slot
in client.py is instead typed API_TOKEN: str | None = None (the
_dbx_secret helper function itself is still generated, just not called at
module scope), and pipelines/<stream>.py — which only ever runs on the
driver — calls it there and threads the resolved value through as a
DataSource reader option (secret_api_token, etc.). Spark reconstructs the
reader fresh on every executor (it pickles the driver's reader object by
reference and re-imports client.py there, wiping any module state the
driver had set), so src/<pkg>/source.py installs the resolved option onto
client's globals twice: once on the driver when the reader is built
(covering schema inference), and again on the worker at the top of every
read()/readBetweenOffsets() call, immediately before that call's fetch.
If a slot is still None once a fetch path actually needs it, the generated
code raises a clear RuntimeError naming the slot instead of silently
sending None to the real API. Nothing about deploying the bundle itself
(databricks bundle deploy) touches secret values — deploy only uploads
source files and cluster/pipeline definitions; the secret is resolved only
once the pipeline actually runs (databricks bundle run).
UC service-credential secret references
An alternative to a Databricks secret scope: resolve the secret through a
Unity Catalog service credential
and an Azure Key Vault secret instead. The same four auth secret slots
(bearer's token, api_key's value, oauth2's client_secret) can carry a
uc_secret object in place of secret —
source:
auth:
type: bearer
uc_secret:
credential: my-service-credential
vault_url: https://my-vault.vault.azure.net/
secret_name: my-key
credential, vault_url, and secret_name are all required and must be
non-empty — again, a reference only; the value itself never appears in
the config. secret and uc_secret are mutually exclusive on the same
auth slot — setting both raises a config error. Codegen resolves it at
runtime via a generated _uc_secret(credential, vault_url, secret_name)
helper instead:
_uc_secret is emitted once per script, only when the auth slot references
a UC secret. It calls
dbutils.credentials.getServiceCredentialsProvider(credential) to get an
Azure-SDK-compatible credential, then uses it to authenticate an Azure Key
Vault SecretClient(vault_url=..., credential=...) and fetch
secret_name. It raises a clear RuntimeError if called outside a
Databricks cluster (no active Spark session), if dbutils/the Key Vault SDK
aren't available, or if the resolved secret has no value — the error
message names the required azure-keyvault-secrets package. Unlike
_dbx_secret, there is no {{ options.* }} placeholder equivalent —
option_secrets stays scope-only (see above); use auth.uc_secret for the
primary auth secret slot.
Setting a uc_secret reference in the UI happens through the same
Secret source toggle described above — pick UC credential (Key
Vault) and fill in the credential, vault URL, and secret name. See
Deploy to Databricks → Secrets for the UI
walkthrough.
At deploy time, this reference resolves the same way a Databricks
secret-scope reference does (see above): src/<pkg>/client.py keeps the
slot typed str | None = None, and pipelines/<stream>.py calls
_uc_secret driver-side and threads the resolved value through as a
DataSource reader option, resolved only once the pipeline actually runs
(never at databricks bundle deploy time). The generated pyproject.toml
adds azure-keyvault-secrets as a dependency automatically whenever a
uc_secret reference is present, so the built wheel (see Deploy to
Databricks → Project bootstrap) carries
what _uc_secret needs.
Query parameters & headers
- Params (
stream.params) → thePARAMSconstant in the generated script, sent on every request. - Headers (
stream.headers) → theHEADERSconstant. - Values may reference
{{ options.<name> }}(see Reader options). If<name>was supplied as a Spark reader option at generation time, it is resolved once into a literal value baked into the script — no templating left at runtime. If it was not supplied (the common case for an exported script, since/api/generatepasses no options), the reference instead becomes anOPT_<NAME>placeholder constant near the top of the script (defaulting to"REPLACE_ME"), interpolated intoPARAMS/HEADERS/PATHwherever it was referenced — edit that constant after export, or wire it up to a secret store the same way as the Bearer/OAuth2 placeholders.
Filter pushdown
The Filter pushdown panel (stream.pushdown_params) maps DataFrame
column names to API query parameter names:
With a mapping in place, the generated _Reader implements Spark's
pushFilters() (Python Data Source filter pushdown, Spark 4.1+): an
equality filter on a mapped column is taken out of Spark's post-read
filtering and sent to the API as that query parameter on every request the
read makes — the sequential fetch, every static window, and the page probe
and per-page fetches of the pagination strategy alike.
spark.read.format("posts_source").load().filter(col("status") == "active")
# -> GET /posts?status=active
What is pushed and what is not:
- Pushed:
EqualToon a top-level column listed inpushdown_params, with a non-null value (rendered withstr(), soowner_id == 42becomes?owner=42). - Left to Spark (returned from
pushFiltersunchanged): every other filter shape —In, comparisons,Not,IsNull, string matches — plus equality on an unmapped column, on a nested column (a.b), or againstnull. Those still apply; they just run after the read.
Precedence: a pushed value overrides an explicit stream.params entry
of the same name (you asked for the filter, so it wins). Mapping a column
to a parameter the fetch loop assigns itself (page/offset/limit, the
pagination or incremental cursor_param, a param_range partition param)
or to a query-placed api_key name is a config error, as is mapping two
columns to one parameter.
Runtime note: Spark only calls pushFilters when
spark.sql.python.filterPushdown.enabled is on (it defaults to off in
Spark 4.1/4.2, and a reader that implements pushFilters is rejected while
it is off). The standalone script sets that conf on the session itself,
next to the dataSource.register(...) call; a bundle never sets Spark conf
from source code — its databricks.yml declares it under the pipeline
resource instead (configuration: {spark.sql.python.filterPushdown.enabled:
"true"}), so it arrives as pipeline configuration. Filter pushdown is batch-only; a
streaming table with pushdown_params is rejected at generation time. When
the mapping is empty the generated script contains no pushdown code at all.
One Spark quirk to know about when experimenting in a notebook: Spark 4.2
keeps the reader that accepted a pushed filter on the DataFrame object
load() returned, so a later query on that same DataFrame with a
different (or no) filter still sends the first pushed parameter. Call
spark.read.format(...).load() again for each distinct filter. A pipeline
table never hits this: its function body loads the source exactly once.
Reader options
The Spark reader options panel supplies values used to resolve path
placeholders ({user_id} in /users/{user_id}/posts) and any
{{ options.* }} references in params/headers/auth fields, all at
generation time. Fill in owner=dan1elt0m here and the generated script's
PATH/PARAMS constants already have dan1elt0m baked in — there is
nothing left to pass at runtime.
Pagination
Pick a strategy in the Pagination & incremental settings panel
(stream.pagination.type):
none— one page only.offset— increments an offset parameter (offset_param, defaultoffset) bypage_sizeon each request until an empty page comes back.start_offsetsets the initial value.page— increments a page counter (page_param, defaultpage), optionally capping page size vialimit_param(defaultper_page).start_pagesets the initial value.cursor— reads the next cursor from the response body (cursor_path, a dotted path such asmeta.next_cursor) or a response header (cursor_header), and sends it back viacursor_param(defaultcursor).next_url_pathis an alternative for APIs that return a fully qualified "next" link instead of a bare cursor value.link_header— followsLink: <...>; rel="next"response headers.
Every strategy stops once the API returns an empty page. This is not
configurable: the generated script's fetch loop always breaks on an empty
page. stop_on_empty_response is accepted for config compatibility but has
no effect on generated code.
total_pages_path / total_pages_header (under Partition-aware
pagination hints) let the generated script know when to stop without
waiting for an empty page — it's an extra stop condition, evaluated inside
the same sequential fetch loop. Together with total_records_path /
total_records_header they also drive the pagination partition strategy,
which plans one Spark partition per page from them — see
Partitioning below.
How the batch table reads
Every generated @dp.table — batch or streaming — ingests through a Spark
DataSource registered inline in the script, because Lakeflow Declarative
Pipelines requires a Spark data source rather than a bare Python value; a
batch table can no longer just build a DataFrame in Python and return it.
Concretely, the script defines a RestSource(DataSource) (its schema()
returns the DDL, its reader() builds a _Reader) and a
_Reader(DataSourceReader) whose read() calls fetch_records() and
yields one tuple per record; the table body is just
spark.read.format("<name>_source").load(). When the connector has no
partition strategy, _Reader reads everything in a single partition; when
it does (see Partitioning below), _Reader.partitions()
returns one InputPartition per window or page and read() fetches only
that partition's records — Spark runs each partition on whichever executor
it schedules it to, in parallel.
Partitioning
Set a Partition strategy (stream.partition.strategy) to change how
the generated @dp.table function fetches data:
- none (default) — plain sequential fetch, as above.
- pagination — one partition per page, planned at read time. The
driver (
_Reader.partitions()) fetches the first page once through a generated_probe_total_pages(), resolves the page count from the pagination hints, and returns oneInputPartitionper page; each partition'sread()then fetches exactly that page viafetch_page()(page = start_page + ifor page pagination,offset = start_offset + i * page_sizefor offset pagination). Requires page or offset pagination with apage_sizeand at least one hint — otherwise the generated script is the plain sequential loop and contains no probe code at all. Hint precedence is the 0.x one:total_pages_path, thentotal_pages_header, thentotal_records_path, thentotal_records_header(records becomeceil(total / page_size)pages). A hint that resolves to nothing, or to 0 or 1 pages, falls back to a single partition that reads the whole stream sequentially. - param_range — generates one static request "window" per value at
generation time; the inline Data Source (see
above) turns each into its own
InputPartition, so windows are fetched in parallel across executors. Supply either explicitvalues(comma-separated) or a range (range_start,range_end,range_step, andrange_kind: datefor date ranges).value_template/extra_templateshape how each value is injected into request parameters. - endpoints — one window per path. Each entry is
/pathorname:/path. Also parallelized, one partition per endpoint. Windows carry only thepath; generated records are flat, with noendpoint_namefield ordatawrapper — see Endpoints partitioning changed if you're migrating a 0.xendpointsconnector.
Partition strategies are batch-only; a streaming table with any strategy
other than none is rejected at generation time.
Incremental sync
Fill in Cursor param and Cursor field (stream.incremental.cursor_param
/ cursor_field) to make the generated script track a cursor between runs;
Mode is a free-text label stored alongside the cursor. The remaining
options mirror the 0.x reader options of the same name, now as config:
Option (stream.incremental.*) |
Generated constant | Default | Meaning |
|---|---|---|---|
cursor_param |
CURSOR_PARAM |
— | Query parameter the stored cursor is sent as. Applied with setdefault, so an explicit stream.params entry (or a partition window's extra_params) with the same name wins. |
cursor_field |
CURSOR_FIELD |
— | Response field whose highest value becomes the next cursor. Dotted paths (meta.updated_at) walk nested objects. |
mode |
— | null |
Free-text label written into the state entry (updated_at, created_at, ...). |
state_path |
STATE_PATH |
<stream>_state.json next to the script |
Where the cursor lives. A plain path or file:// URL is a local file — /Volumes/main/raw/state/orders.json on Databricks (FUSE) is the usual choice, since every executor must reach it. Any other URL scheme (s3://, gs://, abfss://, dbfs://) goes through fsspec; install the matching backend (s3fs, gcsfs, adlfs) on the cluster. |
start_value |
START_VALUE |
None |
Seed sent as the cursor while nothing is stored yet. Ignored once the state file has a value. |
state_key |
STATE_KEY |
<stream>@<base_url> |
Entry key inside the state file, so several connectors can share one file. |
The state file is a JSON document with one entry per key, merged into on every write (other keys are preserved):
{
"streams": {
"issues@https://api.github.com": {
"cursor_param": "since",
"cursor_field": "updated_at",
"cursor_value": "2024-03-22T18:15:00Z",
"mode": "updated_at",
"updated_at": "2024-03-22T18:16:05Z"
}
}
}
Reading is lenient — a 0.x state file is picked up as-is: the entry may sit
under streams or at the top level, may be a dict with cursor_value (or
the older value) or a bare scalar; a missing file or unparseable JSON
simply means "no cursor yet" (and start_value applies).
How the generated code uses it:
fetch_records()(andfetch_page()for thepaginationstrategy) reads the cursor once up front — the stored value, elseSTART_VALUE— and sends it asCURSOR_PARAMon every page of the run.- The inline Data Source's
read()tracks the maximumcursor_fieldvalue it yields (compared as strings — ISO timestamps sort correctly; zero-pad numeric cursors) and commits it once the partition is done. This is the one deliberate difference from the 0.x runtime, which kept the last-seen value. - The commit is monotone:
_write_state()re-reads the stored value and only writes when the new cursor is higher. Withparam_range,endpointsorpaginationpartitioning,read()runs once per partition on whichever executor owns it, so partitions may commit in any order — the file still ends at the global maximum, and a partition that saw nothing newer never regresses it. A localstate_paththerefore has to be one every executor can reach (a Volume path), or use a remote URL. - For the
paginationstrategy the driver resolves the cursor once inpartitions()and hands the same value to the probe and to every page partition, so all pages of a run are fetched against the cursor the page count was planned with.
The UI's Preview never touches your state: it generates the same
fetch_records() against a throwaway state path, so it always shows a
first run (seeded from start_value if set) and never writes a file.
Error handling & retries
The Error handling panel (stream.error_handler) controls the
generated script's retry loop:
max_retries— attempts before giving up (default 5).retry_statuses— HTTP statuses to retry, e.g.5XX,429.retry_on_timeout/retry_on_connection_errors— retry onrequeststimeout/connection exceptions too.backoff.initial_delay_seconds,backoff.max_delay_seconds,backoff.multiplier— exponential backoff between attempts.
Leave the panel untouched to keep the defaults.
Record selector
For APIs that nest the record list inside the response body, the Record
selector panel (stream.record_selector) configures how the generated
_records() function digs it out:
field_path— a list of keys/*wildcards, e.g.["data", "items"]walkspayload["data"]["items"].record_filter— a boolean Python expression evaluated per record, e.g.record.get('status') == 'open'.cast_to_schema_types— cast each top-level scalar column to the type declared inschema(below) before it reaches Spark:"42"→INT,"10.90"→DOUBLE/DECIMAL,"true"→BOOLEAN, ISO 8601 text →TIMESTAMP/DATE, and a dict or list under aSTRINGcolumn → its JSON text. This is what lets an XML API (where every value is text) or a JSON API that quotes its numbers fit a typed schema — without it, Spark rejects the mismatched value. The generated_records()applies a smallCASTStable with one_to_*helper per type actually used; a value that can't be cast is passed on unchanged, so Spark still reports it. Requires an explicitschema(it is a no-op with an inferred one);ARRAY/MAP/STRUCT/VARIANTcolumns pass through as-is, andrecord_filterruns before casting, on the raw values.
field_path and record_filter are JSON-only and have no effect on XML
responses (see below); cast_to_schema_types applies to both.
Schema
Toggle Infer schema (stream.infer_schema) to skip pinning a DDL. The
inline Data Source's schema() (see How the batch table
reads above) then calls a generated
_infer_schema() helper, which samples up to 50 records from
fetch_records() (split evenly across windows for a param_range /
endpoints connector, so a column that only appears in one window's
records is still picked up) and derives a DDL string from their Python
types — widening a column that mixes ints and floats to DOUBLE, and
falling back to STRING for any other type conflict or for null/dict/list
values. This is good for getting started, but it costs an extra request
every time the table runs, and the shape can shift if the API's response
shape changes. An explicit schema is recommended for anything beyond
initial exploration.
Turn it off and fill in Schema (stream.schema) to pin an explicit
Spark SQL DDL string, comma-separated name TYPE pairs:
Supported scalar types: string / varchar / char / text, boolean /
bool, double / float64, float / real, tinyint, smallint, int
/ integer, bigint / long, timestamp, date, variant, and
decimal(precision, scale) (or bare decimal / numeric for
decimal(38, 18)).
Nested types are supported: ARRAY<T>, MAP<K, V>, and
STRUCT<name: T, ...> (a struct field's name: T colon is optional —
name T works too, matching Spark's own DDL grammar), any of which can
nest inside each other to any depth, e.g.:
id INT, tags ARRAY<STRING>, meta MAP<STRING, STRING>,
address STRUCT<street: STRING, zip: STRING>,
history ARRAY<STRUCT<at: TIMESTAMP, note: STRING>>
Field names — top-level or inside a STRUCT<...> — can be backtick-quoted
(`first name` STRING) when they contain characters a bare identifier
can't (spaces, commas, etc.).
The validator (_validate_ddl_syntax/_validate_type_expr in config.py)
checks this grammar without needing pyspark installed, so it runs during
generate() even from a bare pip install polymo. It's a syntax check
only — it doesn't guarantee Spark itself will accept the string, though in
practice every DDL string this validator accepts is valid input to
StructType.fromDDL, since it deliberately mirrors that grammar.
With an explicit schema, dict/list values from the API pass straight
through to Spark as-is for STRUCT/ARRAY/MAP columns — the column
needs the real structure, not a stringified copy of it. With an
inferred schema, nested values are still JSON-encoded into a STRING
column by the generated _cell() helper, because schema inference never
produces a nested type (it only ever infers BOOLEAN/BIGINT/DOUBLE/
STRING), so a raw dict/list value would otherwise crash the read.
Any other mismatch between a declared column's type and the actual value
the API returns (e.g. a field you typed as INT that sometimes comes back
as text, or a STRUCT whose declared fields don't match the dict's keys)
is not handled — Spark raises when it can't fit the value into the column,
same as it always has. Fix it in your schema, not by relying on the
generated code to coerce it for you.
XML responses
Set Response format to XML and fill in an XML record path
(stream.xml_record_path) — an ElementTree.findall()-style path selecting
each record element, e.g. .//contact or contacts/contact. The generated
script parses the response with xml.etree.ElementTree and, for each
matched element, builds a record dict from its attributes (prefixed @) and
its direct children ({child.tag: child.text}).
Every value an XML record carries is text. Declare the schema you want
and turn on cast_to_schema_types (see Record selector)
to land INT / DOUBLE / BOOLEAN / TIMESTAMP columns instead of
strings.
Keep these gotchas in mind:
- Nested containers flatten to empty/whitespace text, not a value.
child.textonly captures the text immediately inside<child>before its first sub-element — if<child>itself has children,child.textis usuallyNoneor pure whitespace, not the nested content. There is no automatic nested-XML flattening. If the data you need is one level deeper than a record, pointxml_record_pathat that deeper element instead of trying to pull it out through the parent. - Duplicate sibling tags: last one wins. Each child becomes a plain dict
key, so
<item><tag>a</tag><tag>b</tag></item>produces{"tag": "b"}—"a"is silently overwritten. Rename or restructure the XML upstream if you need every occurrence. - Namespaced documents need Clark notation in the record path. For
<ns:contact xmlns:ns="...">,xml_record_pathmust use{http://the/actual/namespace/uri}contact, not thens:contactprefix form —ElementTree.findall()doesn't resolve XML namespace prefixes on its own. The record keys themselves are namespace-free: a child<{http://www.w3.org/2005/Atom}title>becomes thetitlecolumn and a namespaced attribute becomes@name, so your schema uses plain names. - XML responses are incompatible with
cursor_path,next_url_path,total_pages_path,total_records_path, and the record selector'sfield_path— all of those dig through a decoded JSON payload, which doesn't exist for an XML response. Generation fails with a clear error if you combine them.
Runtime options in generated scripts
There are none. Every option above is resolved once, at generation time,
into literal constants in the exported script (BASE_URL, PATH, PARAMS,
HEADERS, SCHEMA, ...). Editing the connector after export means editing
the script directly, or changing the form in the UI and re-exporting.
The one exception is a Databricks secret-scope
reference or a UC service-credential
reference: the call site —
_dbx_secret("scope", "key") or _uc_secret("credential", "vault_url",
"secret_name") — is still baked in as a literal at generation time, but the
secret value it resolves is looked up fresh on the driver every time the
generated script runs.
If you're coming from the 0.x YAML runtime, see the migration guide for the full list of what moved or disappeared, plus a checklist for rebuilding an old connector in the UI.