Spark Python Data Source
databricks/databricks-agent-skills
Build custom Python data sources for Apache Spark using the PySpark DataSource API — batch and streaming readers/writers for external systems.
Deep dive into flowfileframe — the Polars-LazyFrame-shaped Python API that builds an in-process flowfilecore FlowGraph as a side effect of every method call — covering the FlowFrame/Expr internals…
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegen --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .claude/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .claude/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegenType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegen --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .agents/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .agents/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegen --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .cursor/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .cursor/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/Edwardvaneechoud/Flowfile.git --path .claude/skills/flowfile-frame-and-codegen--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegen --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .gemini/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .gemini/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegenInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .github/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .github/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frame-and-codegen --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/flowfile-frame-and-codegen .opencode/skills/flowfile-frame-and-codegen && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "flowfile-frame-and-codegen" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frame-and-codegen into .opencode/skills/flowfile-frame-and-codegen/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frame-and-codegen", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
flowfile-frame-and-codegenDeep dive into flowfileframe — the Polars-LazyFrame-shaped Python API that builds an in-process flowfilecore FlowGraph as a side effect of every method call — covering the FlowFrame/Expr internals…
Flowfile Frame And Codegen is an agent skill from Edwardvaneechoud/Flowfile. Deep dive into flowfileframe — the Polars-LazyFrame-shaped Python API that builds an in-process flowfilecore FlowGraph as a side effect of every method call — covering the FlowFrame/Expr internals (reprstr, ffrepr, node-emission decision logic, method-injection decorators, lambda-source extraction), the generated-code contract with core's sandbox, exact lazy-vs-eager semantics, DB/cloud/catalog/Kafka/REST connectors, the native-vs-polars-code parity table, and the make-stubs/check-stubs .pyi pipeline. Use when…
Its SKILL.md is about 12k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Data & Analytics, covering Project scaffolding, DataFrames and Event-driven systems. It works with Polars, Python and Apache Kafka. The repository describes itself as: Flowfile is a visual ETL tool and Python library combining drag-and-drop workflows with Polars dataframes. Build data pipelines visually, define flows programmatically with a… The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit c054c90. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
poetrymakeruffgitmysqlFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
jsonplaceholder.typicode.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Flowfile Frame And Codegen loads about 12k tokens when it runs. Until then it costs about 236 tokens; SKILL.md has 4,508 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from Edwardvaneechoud/Flowfile at commit c054c90, republished under its MIT licence (© Edwardvaneechoud). 4,508 words, ~11,509 tokens.
.claude/skills/flowfile-frame-and-codegen/SKILL.md (or your agent's skills folder).NODE_TYPE_TO_SETTINGS_CLASS, template, frontend component) — that four-layer registration process, including the flowfile_frame decision-to-add-a-method checklist at a shallower level, is flowfile-node-development. Come here only for the internals of how flowfile_frame itself is built (Expr repr machinery, injection decorators, lazy semantics, connectors).flowfile-codegen-parity-campaign. This skill documents today's parity table and the mechanics of extending it; it is not the battle plan.$ffsec$ secrets format — flowfile-architecture-contract.flowfile-change-control (this skill covers the stub generators' mechanics; change-control covers when/why the gate blocks a release).flowfile-testing-and-validation.flowfile run), on-disk storage layout, scheduler — flowfile-run-and-operate.flowfile-debugging-playbook.All facts below verified in-repo as of 2026-07-03 (v0.12.7), branch feature/claude-skills. Re-verification commands are in the final section.
flowfile_frame (import flowfile_frame as ff) gives a Polars-LazyFrame-shaped surface (FlowFrame, Expr/Column) where every method call appends a node to an in-process flowfile_core FlowGraph as a side effect, in addition to (usually) computing data.
It is not a client of any backend HTTP API. It imports flowfile_core directly — flowfile_frame/flowfile_frame/flow_frame.py imports FlowDataEngine, FlowGraph, add_connection, combine_flow_graphs_with_mapping, FlowNode, input_schema, transform_schema straight from flowfile_core. There is no port, no serialization boundary, no auth — it mutates Python objects in the calling process. The graph it builds is the same kind of DAG the Designer UI edits and core executes: it can be saved to a .flowfile, opened in the Designer (open_graph_in_editor, §7), or materialized with .collect().
utils.create_flow_graph() seeds every new graph with track_history=False and, critically, forces flow_graph.flow_settings.execution_location = "local" — the comment in source is explicit: "so that the run time does not attempt to use the flowfile_worker process." This one setting is why several connector reads (§8) don't need a running worker, and why write_*/pivot behave eagerly (§6).
FlowFrame objectA FlowFrame is a thin handle around four fields, all set in __new__ (flow_frame.py:274):
| field | meaning |
|---|---|
flow_graph | the shared in-process FlowGraph |
node_id | the node this frame currently points at |
parent_node_id | upstream node id |
output_handle | which source handle to read — "output-0" by default; "output-1" is e.g. the fail branch of filter_split |
data | the node's resulting Polars LazyFrame — flow_graph.get_node(node_id).get_resulting_data().data_frame |
All initialization happens in __new__; __init__ is a deliberate no-op (flow_frame.py:370, docstring: "Python automatically calls __init__ after __new__... this empty method catches that call and safely does nothing"). __new__ is a factory with three branches: flow_graph+node_id given → wrap an existing node; flow_graph given + non-LazyFrame data → create_from_any_type (new source node); otherwise → build a fresh graph, register a NodeManualInput (Python object) or a runtime dependency (add_dependency_on_polars_lazy_frame, for an existing pl.LazyFrame), then recurse into itself with the new graph/node.
Every graph-mutating method picks one of three ways to represent the operation. Read this before writing or reviewing any flowfile_frame code.
flowfile_core.schemas.input_schema / transform_schema) and calls the matching flow_graph.add_*() (add_select, add_sort, add_join, add_group_by, add_formula, add_output, …). Renders as a first-class editable node in the Designer.FlowFrame._add_polars_code (flow_frame.py:588-684), which creates a NodePolarsCode whose payload is a generated Polars source string (transform_schema.PolarsCodeInput(polars_code=...)). Core re-executes that string in a sandbox at run time (§6) — the string is load-bearing; a wrong one breaks graph execution silently, only surfacing when the graph actually runs._add_polars_code, edge case within path 2). If an expression can't be turned into code (convertable_to_code=False — e.g. a lambda whose source can't be recovered) or the generated code still contains a raw <lambda> at 0x...> repr, the operation runs directly on self.data (getattr(self.data, method_name)(...)) and the resulting LazyFrame is base64-serialized into the node. Source carries an explicit warning: "This will result in a breaking graph when using the the ui." If the result isn't a LazyFrame at all, the node degrades further to output_df = input_df and the real result is injected as a precomputed_result on the node's results.resulting_data (_create_child_frame, flow_frame.py:399-418) — the visual graph now lies about what the node computes, and only a logger.error hints at it.new_node_id = generate_node_id() # utils.py — process-global counter
# decide native vs polars-code (mirror an existing use_polars_code_path / can_use_native flag)
settings = input_schema.NodeXxx(
flow_id=self.flow_graph.flow_id, node_id=new_node_id,
depending_on_id=self.node_id, description=description, is_setup=True,
)
self.flow_graph.add_xxx(settings) # or self._add_polars_code(...)
return self._create_child_frame(new_node_id) # wires the connection, re-reads data_create_child_frame (flow_frame.py:399) calls _add_connection — an input_schema.NodeConnection.create_from_simple_input(...) + add_connection(self.flow_graph, ...), honoring self.output_handle — then immediately calls get_resulting_data() on the new node to obtain the next LazyFrame. Multi-input methods (join, concat, fuzzy_join) skip _create_child_frame and wire "main"/"right" connections manually before constructing the FlowFrame(...) directly.
Node ids are process-global, not per-graph. generate_node_id() (utils.py) increments a module-level dict data = {"c": 0}; set_node_id(n) resets it. Combining two graphs (e.g. via join across graphs, §3) bumps the counter by the combined node count. Consequence, verified live below: node ids inside a single graph can skip values — this is expected, not a bug.
Verified (isolated DB, FLOWFILE_DB_PATH set):
import flowfile_frame as ff
df = ff.from_dict({"a": [1, 2, 3], "b": ["x", "y", "z"]})
out = (df.filter(ff.col("a") > 1)
.with_columns((ff.col("a") * 2).alias("dbl"))
.select("a", "dbl")
.sort("a", descending=True))
for n in out.flow_graph.nodes:
print(n.node_id, n.node_type, type(n.setting_input).__name__)1 manual_input NodeManualInput
2 filter NodeFilter # filter — the predicate's formula form lowers onto the Filter node (see 3d)
4 formula NodeFormula # with_columns via _ff_repr, one native node per expr
5 select NodeSelect
6 sort NodeSortNode id 3 never appears — with_columns burned an id internally before delegating to the formula path. out.collect() returns the correct 2-row pl.DataFrame.
sort — dual-path with an explicit trigger list (flow_frame.py:448)NodeSort / transform_schema.SortByInput(column=…, how="asc"|"desc")) only when every sort key is a plain string or an unaltered Column, and nulls_last is falsy, and multithreaded=True, and maintain_order=False.Expr, an altered Column (aliased/cast), nulls_last, maintain_order, or multithreaded=False flips to polars-code: input_df.sort(<pure expr strs>, descending=…, …).\n#─────SPLIT─────\n\n.select — native vs polars-code (flow_frame.py:1022)Column objects (including alias/cast via Column.to_select_input()) go native: builds a transform_schema.SelectInput list, adds unselected existing columns with a keep flag, emits input_schema.NodeSelect.Expr, Selector, or expression with collected function sources → input_df.select([<expr strs>]) polars-code.select(len().alias("number_of_records")) is pattern-matched by string comparison of the repr (str(expr) == "pl.Expr(len()).alias('number_of_records')") and becomes a native NodeRecordCount.rename(mapping) is implemented as select([col(old).alias(new), …], _keep_missing=True) — its strict kwarg is accepted but silently ignored.join — native vs code, and cross-graph combining (flow_frame.py:686)_should_use_polars_code_for_join (flow_frame.py:781): any of maintain_order, coalesce, nulls_equal=True, validate, or a non-default suffix forces polars-code: input_df_1.join(other=input_df_2, how=…, …) with depending_on_ids=[self.node_id, other.node_id] and two "main" connections.JoinMaps and emits NodeJoin (or NodeCrossJoin for how="cross"), with auto_generate_selection=True, verify_integrity=True; right-side join keys get keep=False. Connections: left → "main", right → "right"._ensure_same_graph (flow_frame.py:791): if self and other live in different FlowGraphs, combine_flow_graphs_with_mapping merges them and mutates both frames' node_id/flow_graph in place. Any third FlowFrame still holding a reference into one of the old graphs is left stale — no re-mapping happens for frames the join doesn't touch directly. Keep frame references you plan to reuse after a cross-graph join to a minimum, or re-derive them from the merged graph.fuzzy_join(other, fuzzy_mappings: list[FuzzyMapping]) (flow_frame.py:3292) is a Flowfile-only extension with no Polars equivalent → NodeFuzzyMatch.group_by() → GroupByFrame (flow_frame.py:2361, flowfile_frame/flowfile_frame/group_frame.py)group_by() itself creates no node — it returns a GroupByFrame carrying the future node id..agg(...) tries to convert every aggregation to a native transform_schema.AggColl row (agg="groupby" for the grouping keys). Conversion requires maintain_order=False, no complex expressions, no Selectors, and every agg_func in the fixed set _NATIVE_AGG_FUNCS = {sum, max, mean, median, min, count, n_unique, first, last, std, var, concat} (group_frame.py:20). Named aggs also accept name=("col", "aggstr") tuples. Success → NodeGroupBy; anything outside the set → polars-code input_df.group_by([...], maintain_order=…).agg(...)..sum()/.mean()/.median() generate …agg(cs.numeric().sum()) and aggregate numeric columns only — a deliberate deviation from Polars' GroupBy.sum(), which aggregates everything it can (_NUMERIC_ONLY_METHODS, group_frame.py:235). .len()/.count()/.head()/.tail()/.first()/.last()/.min()/.max() map straight to .group_by(...).method(...).Also note filter: when every predicate (and **constraints equality) has an _ff_repr — comparisons, &/|/~, is_in, is_null, the mapped .str/.dt helpers — the predicates are joined with and, checked with _formula_parses, and emitted as a native NodeFilter(advanced) whose expression is that formula (the flowfile_formula: str kwarg is always native). Lambdas, unmapped methods, or a formula core cannot parse fall back to a polars-code input_df.filter(...) node. filter_split builds its predicate the same way but raises ValueError when no formula form exists — the engine turns an unparseable advanced_filter into "keep nothing" with only a warning, and a two-output node has no Polars-code fallback. drop is hand-written too: plain names lower onto NodeSelect with keep=False (see §9).
flowfile_frame/flowfile_frame/expr.py, ~1.7k lines)ExprEvery Expr carries two parallel representations plus metadata (expr.py:423-493):
expr: pl.Expr | None — the live Polars expression, used for schema propagation and the serialization fallback; may be None for selector-aggregations._repr_str: str — the load-bearing generated Polars source, e.g. "(pl.col('a') * 2).alias('dbl')". Every method returns a new Expr with the repr extended (_create_next_expr appends .method(args_repr); binary ops produce (left op right) and clear aggregation state)._ff_repr: str | None — an optional flowfile-formula representation ([colname], "str", (a + b), to_integer(...), uppercase(...), …) maintained through operators, casts, and a subset of .str/.dt methods. When every expression in a with_columns call still has _ff_repr + a resolvable column_name, the whole call becomes a chain of native NodeFormula nodes instead of one polars-code node — this is the mechanism behind the formula node in the worked example above.column_name / _initial_column_name (rename tracking), agg_func (drives GroupBy AggColl conversion), is_complex (blocks native group-by conversion), convertable_to_code (False → serialization fallback, §2), _function_sources: list[str] (extracted function/lambda definitions to prepend to generated code).Column(Expr) — pl.col(name) plus a transform_schema.SelectInput; .alias()/.cast() return new Columns with is_altered/data_type_change flags so select/sort/unique can stay native when nothing structural changed.When(Expr) — when().then() mutate in place; .otherwise() returns a plain Expr carrying the full pl.when(...).then(...).otherwise(...) repr and an if c1 then v1 elseif c2 then v2 else v endif _ff_repr when every branch has one (a chain without otherwise renders else null), which is what lets with_columns lower a fluent when onto a Formula node..str → StringMethods, .dt → DateTimeMethods (hand-written), .list → ExprListNameSpace, .name → ExprNameNameSpace.lit(value) uses pl.lit(value, allow_object=True) and repr(value); module-level @agg_function-decorated functions (max/min/first/last/mean/count/implode/explode/sum/corr/cov) render as pl.sum('a')-style calls and set agg_func.add_expr_methods(Expr), invoked at expr.py:1403 (module import time). For every callable on pl.Expr not already defined on Expr and not a dunder/property, it installs a wrapper (adding_expr.py) that calls the real Polars method for schema/error-checking (failures set result_expr=None and log at debug level, not raise), classifies the call via hard-coded agg_methods/complex_methods sets, and appends it to _repr_str. PASSTHROUGH_METHODS = {"map_elements", "map_batches"} (adding_expr.py:16) get callable-source extraction; an unresolvable callable sets convertable_to_code=False with a logged warning.@add_lazyframe_methods on FlowFrame (lazy_methods.py:136, applied at flow_frame.py class definition). Explicitly hand-defined methods always win over the injected ones. Injected pl.LazyFrame methods split in two:PASSTHROUGH_METHODS (lazy_methods.py:9-26) — collect, collect_async, profile, describe, explain, show_graph, fetch, collect_schema, columns, dtypes, schema, width, estimated_size, n_chunks, is_empty, chunk_lengths, get_meta — delegate straight to self.data, no node added. Calling describe() really executes on the underlying LazyFrame right there.output_df = input_df.<method>(<repr'd args>) as a polars-code node (verified: df.tail(2) → output_df = input_df.tail(2); drop used to be one of these but is now hand-written). Every injected method also gains an extra description: str | None kwarg.lazy_methods.py:108-110: if any argument has convertable_to_code=False, the wrapper short-circuits into a new source frame built from the eagerly-computed result — graph lineage is severed at that point (a genuine edge case, not exercised in the test suite as of this writing).flowfile_frame/flowfile_frame/callable_utils.py)inspect.getsource (_get_function_source, callable_utils.py:19), dedented and stored verbatim._extract_lambda_source (callable_utils.py:52) re-synthesizes a named def _lambda_fn_<hash % 100000>(args): return <body> via AST, matching by argument names, and captures closure variables as constant assignments or nested function defs; an unresolvable closure returns (None, None) → convertable_to_code=False.ff.col("n").map_elements(lambda x: x + 1, return_dtype=ff.Int64) produced a polars-code node body:def _lambda_fn_83562(x):
return x + 1
#─────SPLIT─────
output_df = input_df.with_columns([pl.col('n').map_elements(_lambda_fn_83562, return_dtype=Int64).alias('m')])stdin/a REPL (no retrievable source via inspect.getsource) falls into the base64-serialized-LazyFrame fallback (§2, path 3) instead — both variants .collect() to the same data, but only the file-based one produces a portable, UI-editable node. Note return_dtype=Int64 has no pl. prefix in the generated code — it resolves only because core's sandbox injects bare dtype names into its namespace (§6).fold and the generic Polars-function wrapperlazy.py provides polars_function_wrapper (lazy.py:490) — decorator machinery that turns arbitrary pl.* functions into FlowFrame/Expr-returning functions by deep-repr'ing their arguments. fold (lazy.py:681-682, @polars_function_wrapper("fold", return_type="Expr")) is the shipped example; use it as the template if you need to wrap another module-level Polars function the same way.
selectors.py)Selector mirrors polars.selectors; its repr_str renders pl.selectors.<name>()-style strings. Selector aggregation methods (.sum(), .mean(), .std(ddof), …) build an Expr(expr=None, selector=…, agg_func=…) whose repr is <selector_repr>.<func>(…). Selectors always force the polars-code path in select/group_by/unpivot — there is no native-node route for a Selector.
Graph building is schema-eager, data-lazy — with several genuinely eager exceptions worth memorizing.
Baseline: every _create_child_frame/FlowFrame(...) call immediately invokes the new node's get_resulting_data() under a lock. For ordinary transforms this only chains LazyFrame operations (cheap, schema-resolution only) — confirmed above, every node logs "getting resulting data" at build time, not at .collect() time. .collect(*args, **kwargs) (flow_frame.py:2446) just collects the accumulated local LazyFrame and returns a pl.DataFrame — it does not invoke core's run machinery or talk to the worker.
Things that look lazy but are not (all verified live in this session):
| Operation | What actually happens | Evidence |
|---|---|---|
write_csv/sink_csv and friends | add_output's node function calls df.output(..., execute_remote=False) at graph-build time because execution_location == "local" always holds for frame graphs. sink_csv → write_csv, sink_ipc → write_ipc, etc. are literal aliases. | File exists on disk before .collect() is ever called. |
pivot (native path) | Materializes its input during graph building — the node needs the distinct pivot-column values to compute the output schema. | Log line Reading entire file: ~/.flowfile/cache/1/<uuid>.arrow … Got N unique values from external source; leaves a cache file under ~/.flowfile/cache/. |
Passthrough methods (describe, profile, fetch, collect_schema, …) | Run immediately against self.data. | By construction — no node is added for these. |
from_dict(...) / FlowFrame(python_data) | Rows are embedded as NodeManualInput.raw_data_format — the full dataset gets serialized into the saved .flowfile. | Direct read of flow_frame.py source. |
Serialization fallback (§2, path 3) and the lazy_methods non-convertible short-circuit | Execute the real Polars operation at call time to capture a concrete result. | By construction. |
Readers (read_csv, read_parquet, scan_*) are genuinely lazy scans; scan_csv/scan_parquet are literal aliases of read_csv/read_parquet. read_csv uses a native ReceivedTable-based node when parameters fit a documented allowlist, else falls back to generated pl.scan_csv code. Passing a BytesIO as the CSV source degrades all the way to from_dict(pl.read_csv(source)) — fully eager, because a BytesIO handle can't be re-scanned lazily by a saved graph.
cache() (flow_frame.py:2616) sets cache_results=True on the node settings (an execution hint the graph runner honors) and calls self.data.cache() immediately.
Polars-code nodes execute inside flowfile_core/flowfile_core/flowfile/flow_data_engine/polars_code_parser.py's PolarsCodeExecutor. When writing or reviewing a generated string, it must satisfy:
safe_globals (polars_code_parser.py:129): __builtins__ = {}. Available names: pl, cs (= pl.selectors), col, lit, expr, every bare Polars dtype name (Int64, String, Datetime, Struct, …, no pl. prefix needed), a small basic-builtins set (print, len, range, enumerate, zip, list, dict, set, str, int, float, bool, True, False, None), plus time, BytesIO, base64, datetime._validate_code (polars_code_parser.py:191): rejects any code containing an import statement, blocked calls (exec, eval, getattr, open, …), dunder attribute access, and string constants matching a __word__ pattern._wrap_in_function (polars_code_parser.py:239): code is wrapped in _transform(input_df) (or _transform(input_df_1, …, input_df_N) for multi-input nodes). A single-line expression starting with pl./col(/input_df/expr( is returned as-is; any other body must assign the name output_df.input_df; ≥2 inputs → input_df_1..N, in upstream-discovery order. concat also dedupes duplicate sources because add_connection is idempotent.#─────SPLIT───── marker separating prepended function definitions from the operation is purely a flowfile_frame-side cosmetic convention for the UI code editor — comments are stripped before execution, and grep confirms no consumer in core parses that marker.The rule that ties all of §2–§6 together (quoted verbatim from flowfile_frame/CLAUDE.md): "Source string is load-bearing. … A wrong string breaks graph execution silently."
open_graph_in_editor — frame graph to visual flowEntry point: from flowfile import open_graph_in_editor; open_graph_in_editor(frame.flow_graph). FlowFrame itself has no open_in_editor method — you pass its .flow_graph. Signature: open_graph_in_editor(flow_graph, storage_location=None, module_name="flowfile", automatically_open_browser=True) -> bool (flowfile/flowfile/api.py).
execution_location="local" and execution_mode="Development", saves via _save_flow_to_location (a temp temp_flow_<uuid>.yaml if no storage_location given), then restores the original settings object — but the saved file still has execution_location="local" baked in, and flow_settings.path is left pointing at the saved file afterward.start_flowfile_server_process: if GET http://127.0.0.1:63578/docs (host/port env-overridable via FLOWFILE_HOST/FLOWFILE_PORT) doesn't answer, spawns flowfile run ui --no-browser — via poetry run when a Poetry environment is detected, otherwise via the installed console script next to sys.executable. Waits up to 60s; registers an atexit kill hook.POST /auth/token (empty body) → JWT; the flow is imported via the authenticated API to obtain a flow id.http://HOST:PORT/ui/flow/{id} only when the running server reports single-mode (GET /single_mode) and FLOWFILE_MODE == "electron".Import side effect worth knowing about: import flowfile (the top-level flowfile package, not flowfile_frame) sets os.environ["FLOWFILE_WORKER_PORT"] = "63578" and os.environ["FLOWFILE_SINGLE_FILE_MODE"] = "1" at import time, then re-exports the whole frame API (col/lit/when, readers, connections, selectors) plus core classes — this is why docs examples that write import flowfile as ff (rather than import flowfile_frame as ff) work unmodified.
All of these persist through flowfile_core's storage layer (the shared SQLite catalog DB), not local files, and every helper hard-codes user_id = 1 ("single-user mode": database/connection_manager.py, cloud_storage/secret_manager.py, kafka.py, rest_api.py, catalog.py).
flowfile_frame/flowfile_frame/database/): create_database_connection(...)(connection_name, database_type=postgresql|mysql|sqlite|mssql|oracle, host/port/database/username/password, ssl_enabled, url) stores a FullDatabaseConnection; get_all_available_database_connections() returns password-free interfaces. read_database(connection_name, table_name=… | query=…, schema_name=…, flow_graph=…) → NodeDatabaseReader; query wins if both table_name and query are given. write_database(df, connection_name, table_name, schema_name=…, if_exists="append"|"replace"|"fail") → NodeDatabaseWriter.flowfile_frame/flowfile_frame/cloud_storage/): connection helpers in secret_manager.py. read_from_cloud_storage(source, file_format=csv|parquet|json|delta, connection_name, scan_mode, …) dispatches to scan_{csv,parquet,json}_from_cloud_storage/scan_delta; write_to_cloud_storage(df, path, file_format, …, partition_by delta-only) funnels through add_write_ff_to_cloud_storage → NodeCloudStorageWriter.catalog.py, catalog_reference.py): read_catalog_table, read_catalog_sql, write_catalog_table, register_flow_with_catalog, plus navigable CatalogReference/SchemaReference/list_catalogs()/default_schema() that talk to CatalogService directly (in-process, same DB).kafka.py, read_kafka) and REST (rest_api.py, read_api, with auth/pagination coercers _coerce_auth/_coerce_pagination) add NodeKafkaSource/NodeRestApiReader nodes.On "does this need a running flowfile_worker": no, for frame graphs — verified, corrects a natural assumption. Core's general architecture rule is "the worker does all external fetching" (GA4 is the canonical zero-exception template — see flowfile-node-development §1.5 for the full rule and rationale). But add_database_reader, add_kafka_source, and add_rest_api_reader each carry an explicit if self.execution_location == "local": branch in flow_graph.py that fetches in-process instead of dispatching to the worker — added specifically so CLI/--run-flow and other no-worker execution modes still work. Since create_flow_graph() always sets execution_location = "local" for any graph flowfile_frame builds, every DB/Kafka/REST read you do from a flowfile_frame script runs in-process, with no worker required for .collect(). Verified live in this session: ff.read_api(url="https://jsonplaceholder.typicode.com/todos/1").collect() succeeded with fetching happening inline (shared/rest_api/fetch.py::fetch_rest_api, the same function the worker calls, just invoked directly) — no flowfile_worker process was running anywhere on the host. This only applies to .collect()-time execution of a frame-built graph; a flow later opened in the Designer and run in a non-local execution mode goes back through the worker like any other flow (flowfile-architecture-contract).
Import side effect: these connector modules import flowfile_core, whose package __init__ runs validate_setup() + init_db() — importing flowfile_frame creates and Alembic-migrates the catalog DB if it doesn't already have the current schema. For any probing/scripting, isolate with FLOWFILE_DB_PATH=<scratch path> (per the standing project rule — do not point ad hoc scripts at the live catalog DB).
| Operation | Native-node condition | Fallback |
|---|---|---|
select | strings + (aliased/cast) Columns only | polars-code input_df.select([...]) |
filter(*predicates, **constraints) | every predicate has a formula form (_ff_repr) and the and-joined formula parses → NodeFilter(advanced); flowfile_formula= always native | polars-code input_df.filter(...) for lambdas, unmapped methods, or unparseable formulas |
drop | plain names / unaltered Columns (a missing name only with strict=False) → NodeSelect with those columns keep=False, keep_missing=True | polars-code for selectors or a missing name under strict=True |
sort | plain columns, no nulls_last/maintain_order, multithreaded=True | polars-code |
join | equality joins, default suffix, no validate/nulls_equal/coalesce/maintain_order; how="cross" always native | polars-code join |
group_by().agg | simple exprs, aggs in the fixed _NATIVE_AGG_FUNCS set, no maintain_order/Selector | polars-code |
pivot | single on + single values, agg in {first,last,min,max,sum,mean,median,count}; values is required (raises ValueError if None) | polars-code — multi-column fallback is currently broken, see §10 |
unpivot | plain columns, default variable/value names | polars-code |
unique | string/unaltered-Column subset, no maintain_order | polars-code |
concat | only how="diagonal_relaxed" + parallel + !rechunk + no duplicate sources → NodeUnion(mode="relaxed") | default how="vertical" goes to polars-code pl.concat([...]) |
with_columns | every expr _ff_repr-convertible and parseable by core → chain of native NodeFormula nodes (one per expr) — this includes when().then()…otherwise() chains (→ if/elseif/else/endif), is_in (→ in (...)) and ~ (→ not(...)); flowfile_formulas= kwarg can also auto-upgrade via polars_expr_transformer.to_flowframe_code (≥0.5.4) | polars-code input_df.with_columns([...]) |
with_row_index | name == "record_id", or (offset == 1 and name != "index") → NodeRecordId; also detects cum_count().over(...) patterns | polars-code |
head/limit | NodeSample, always | — |
rename | implemented via select(..., _keep_missing=True) | — |
everything else on pl.LazyFrame (tail, slice, shift, reverse, fill_null, quantile, …) | injected generic wrapper → polars-code | passthroughs (§5) add no node at all |
Flowfile-only extensions with no Polars equivalent: filter_split (→ (pass, fail) frames on output handles 0/1), random_split(splits, seed) (→ N frames), the ML verbs train_model/apply_model/evaluate_model/wait_for, fuzzy_join, text_to_rows, solve_graph (graph connected-components), explode_hierarchy (parent→child transitive closure via polars_grouper's hierarchy_* functions → NodeExplodeHierarchy; the result is a new table, not the input plus columns), dynamic_rename (prefix/suffix/formula/first-row renaming), the visual-grouping context manager with df.group("name"): + set_group (organizational only, no data effect), write_catalog_table, the cloud/DB writers, to_graph/save_graph.
Known parity deviations, not bugs:
GroupByFrame.sum/mean/median aggregate cs.numeric() columns only (Polars aggregates everything it can).filter predicates become one node (a native NodeFilter when every predicate has a formula form, else one polars-code node), not a chain.concat's default is graph-opaque polars-code, not the graph-friendly NodeUnion.Series is a 65-line stub; there is no eager DataFrame type — LazyFrame = DataFrame = FlowFrame aliases in __init__.py exist purely "for compatibility with generated code" and must not be removed (contract stated in flowfile_frame/CLAUDE.md).For a plan to close specific gaps in this table, see flowfile-codegen-parity-campaign — this table is a snapshot, not a roadmap.
make stubs / make check_stubsBecause FlowFrame and Expr get most of their methods injected at runtime (§4), static type checkers see almost nothing without stubs. The package ships committed .pyi files plus py.typed (PEP 561) for every module, including database/ and cloud_storage/ submodules.
# Makefile:259 — regenerate all stubs
stubs:
poetry run python flowfile_frame/expr_stub_generator.py
poetry run python flowfile_frame/flow_frame_stub_generator.py
poetry run python flowfile_frame/submodule_stub_generator.py
ruff check $(find flowfile_frame/flowfile_frame -name '*.pyi') --select F401 --fix --quiet
# Makefile:269 — CI drift gate: rerun stubs, then diff
check_stubs: stubs
git diff --exit-code -- 'flowfile_frame/flowfile_frame/*.pyi' 'flowfile_frame/flowfile_frame/**/*.pyi'expr_stub_generator.py → expr.pyi. Imports the live module and introspects Expr/Column/StringMethods/DateTimeMethods/When/ExprNameNameSpace/ExprListNameSpace plus top-level functions; adds every remaining pl.Expr method as def m(self, *args, **kwargs) -> 'Expr': .... Output is explicitly sorted for determinism — a source comment notes unsorted output caused spurious diffs before this was fixed.flow_frame_stub_generator.py → flow_frame.pyi. Introspects the live FlowFrame (so injected LazyFrame methods appear) plus module-level functions. Duplicates the PASSTHROUGH_METHODS set from lazy_methods.py by hand — if you change the passthrough set, update this copy too or the generated stub drifts silently from actual runtime behavior. Rewrites LazyFrame → FlowFrame in annotations.submodule_stub_generator.py → a .pyi for every other .py file in the package, including __init__.pyi (pure AST-based, no import needed; skips only expr.py/flow_frame.py and private modules; flattens if TYPE_CHECKING: imports; prunes unused imports; emits __all__ in __init__.pyi for PyCharm). Generated-file header: # Auto-generated stub for … — do not edit.flowfile_frame/readme.md claims __init__.pyi is "hand-maintained" — it is not; it is generated (verified by reading the header), and flowfile_frame/CLAUDE.md explicitly corrects this. Trust the CLAUDE.md, not the readme, on this point.flowfile_frame, running make stubs has the same DB-migrating import side effect as importing the library (§8). Isolate:FLOWFILE_DB_PATH=/tmp/stub_scratch.db make stubsmake check_stubs runs as the check-stubs job in .github/workflows/test.yaml, gated on flowfile_frame/** changes (via the backend_frame path-filter output) or a manual run_all_tests dispatch. Failure message: "stubs are out of sync with the source. Run 'make stubs' and commit the result." — any public-surface change to frame code without regenerating stubs fails CI, not just a lint warning._add_polars_code only for genuinely complex cases, mirroring the use_polars_code_path/can_use_native flag pattern already used by sort/select/join.getattr/dunder-attribute strings, assign output_df (or a single-line pl./col(-prefixed expression), use input_df/input_df_1..N, prepend function sources with the #─────SPLIT───── convention.generate_node_id() → settings (always include flow_id, node_id, depending_on_id(s), is_setup=True, description) → flow_graph.add_* → self._create_child_frame(new_node_id) for single input, or manual "main"/"right" connections + a direct FlowFrame(...) construction for multi-input.description: str | None = None on every graph-building method — the frontend uses it as the node label.Expr changes: keep _repr_str correct (this is the whole contract); propagate _function_sources, convertable_to_code, agg_func, is_complex; add an _ff_repr mapping when a flowfile-formula equivalent exists (unlocks native NodeFormula emission via with_columns). Explicitly-defined methods always win over injected ones.__init__.py exports intact: the LazyFrame = DataFrame = FlowFrame aliases and Polars dtype re-exports — generated code and docs examples depend on both existing.flowfile-change-control's no-agent-commit policy): FLOWFILE_DB_PATH=<scratch> make stubs; make check_stubs is what CI runs. If you touched a passthrough-methods set, update the hand-duplicated copy in flow_frame_stub_generator.py too..collect() against a plain-Polars reference implementation, and an assertion on node type / generated code string, mirroring test_ff_repr.py's _is_formula_node/_is_polars_code_node helper pattern. Round-trip anything UI-visible through save_graph + reopen.FLOWFILE_DB_PATH=/tmp/frame_test.db poetry run pytest flowfile_frame/tests --disable-warnings and poetry run ruff check flowfile_frame.flowfile_frame/tests/ is flat — no package-specific pytest marker (the root markers are listed in flowfile-testing-and-validation §1). tests/conftest.py sets TESTING=True at import and unconditionally deletes/recreates a cloud connection named minio-flowframe-test (s3, http://localhost:9000, minioadmin/minioadmin) in the catalog DB at collection time — this only registers the connection row; MinIO itself is needed only by the tests that actually read/write through it. Docker-gated tests use tests/utils.py::is_docker_available() with @pytest.mark.skipif.
| File | Lines | Covers |
|---|---|---|
test_lazy_frame.py | 1545 | Injected LazyFrame methods |
test_flow_frame.py | 934 | Core ops, writer round-trips, save_graph |
test_expressions.py | 901 | Expr behavior |
test_ff_repr.py | 848 | _ff_repr contract; "is this a NodeFormula or NodePolarsCode" assertions |
test_group_frame.py | 477 | GroupByFrame |
test_joins.py | 333 | join/fuzzy_join |
test_catalog_reference.py | 242 | Catalog navigation |
test_dynamic_rename.py | 107 | dynamic_rename |
test_flowfile_frame.py | 117 | Misc/top-level API |
test_evaluate_model.py, test_node_groups.py, test_catalog_write_partition.py, test_cloud_write_partition.py | 93 / 85 / 19 / 45 | ML eval, visual groups, catalog/cloud partitioned writes |
CI runs poetry run pytest flowfile_frame/tests --disable-warnings in both the Linux/macOS matrix and the Windows job. Isolate local runs with FLOWFILE_DB_PATH — the shared TESTING DB can be dropped mid-run by a concurrent pytest session (flowfile-testing-and-validation has the full isolation model).
df.pivot(on=["c", "c2"], index="k", values="v", aggregate_function="sum") raises TypeError: LazyFrame.pivot() got an unexpected keyword argument 'sort_columns' at graph-build time. The fallback template passes DataFrame-only kwargs to a LazyFrame call and assigns to a bare result name instead of output_df. Single on/values pivots (the native path) work fine.from_dict single-row-list quirk. ff.from_dict({"x": [1]}) produces a list[i64] column instead of i64 (verified). Multi-row dicts behave normally; likely a FlowDataEngine-side raw-data inference edge case for 1-row inputs. If you see an unexpected List dtype on a single-row manual-input frame, this is why.rename(strict=...) is accepted but silently ignored — no error, no effect.LazyFrame result silently degrades the node to output_df = input_df while injecting the real result out-of-band — the visual graph misrepresents the computation, and only a logger.error hints at it. If a saved .flowfile "does nothing" for a node that should transform data, check whether that node went through this path.lazy_methods' non-convertible-arg short-circuit passes a generator expression as a single call argument — looks structurally suspect; not exercised by the test suite as of this writing, treat as unverified rather than confirmed-safe.Facts above dated 2026-07-03 (v0.12.7), branch feature/claude-skills. Re-run these before trusting a line number or count in a newer checkout.
# line-number/shape spot checks for the big three files
grep -n "def __new__\|def __init__\|def sort\|def select\|def join\|def filter\b\|def group_by\b" flowfile_frame/flowfile_frame/flow_frame.py
grep -n "add_expr_methods(Expr)$" flowfile_frame/flowfile_frame/expr.py
grep -n "^PASSTHROUGH_METHODS" flowfile_frame/flowfile_frame/lazy_methods.py flowfile_frame/flowfile_frame/adding_expr.py
# confirm the sandbox contract hasn't drifted
grep -n "safe_globals\|_validate_code\|_wrap_in_function" flowfile_core/flowfile_core/flowfile/flow_data_engine/polars_code_parser.py
# confirm which connectors have an execution_location=="local" in-process branch
grep -n 'execution_location == "local"' flowfile_core/flowfile_core/flowfile/flow_graph.py
# current .pyi file count
find flowfile_frame/flowfile_frame -name '*.pyi' | wc -l
# stub pipeline still wired the same way
grep -n "^stubs:\|^check_stubs:" -A 8 Makefile
grep -n "check-stubs" -A 3 .github/workflows/test.yaml
# reproduce the node-chain / node-id-skip probe from §2 (isolate the DB!)
FLOWFILE_DB_PATH=/tmp/frame_probe.db poetry run python - <<'EOF'
import flowfile_frame as ff
df = ff.from_dict({"a": [1, 2, 3], "b": ["x", "y", "z"]})
out = df.filter(ff.col("a") > 1).with_columns((ff.col("a") * 2).alias("dbl")).select("a", "dbl").sort("a", descending=True)
for n in out.flow_graph.nodes:
print(n.node_id, n.node_type, type(n.setting_input).__name__)
print(out.collect())
EOF
# reproduce the "REST read needs no worker" claim (run with no flowfile_worker process alive)
FLOWFILE_DB_PATH=/tmp/frame_probe2.db poetry run python -c "
import flowfile_frame as ff
print(ff.read_api(url='https://jsonplaceholder.typicode.com/todos/1').collect())
"
# reproduce the multi-column pivot bug and the from_dict single-row quirk
FLOWFILE_DB_PATH=/tmp/frame_probe3.db poetry run python -c "
import flowfile_frame as ff
df = ff.from_dict({'k':[1,1,2,2],'c':['a','b','a','b'],'c2':['x','x','y','y'],'v':[1,2,3,4]})
df.pivot(on=['c','c2'], index='k', values='v', aggregate_function='sum').collect()
"
# run the frame test suite in isolation
FLOWFILE_DB_PATH=/tmp/frame_test.db poetry run pytest flowfile_frame/tests --disable-warnings© Edwardvaneechoud, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/flowfile-frame-and-codegen of Edwardvaneechoud/Flowfile.
Open the folder on GitHubat commit c054c90
Flowfile Frame And Codegen next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Flowfile Frame And Codegen this skillEdwardvaneechoud/Flowfile | 370 | — | ~12k | Automated safety check: Pass | MIT | |
| Spark Python Data Sourcedatabricks/databricks-agent-skills | 345 | — | ~2.1k | Automated safety check: Pass | Custom licence | |
| Hybrid-Engine Data Analysiscode-yeongyu/oh-my-openagent | 70k | — | ~1.4k | Automated safety check: Pass | Custom licence | |
| Optimuskgmims-harvard/OptimusKG | 146 | — | ~1.9k | Automated safety check: Pass | MIT | |
| Narwhalsanam-org/metaxy | 124 | — | ~3.3k | Automated safety check: Pass | Apache-2.0 | |
| PolarsK-Dense-AI/scientific-agent-skills | 48k | 1 repos | ~3.3k | Automated safety check: Pass | MIT |
databricks/databricks-agent-skills
Build custom Python data sources for Apache Spark using the PySpark DataSource API — batch and streaming readers/writers for external systems.
code-yeongyu/oh-my-openagent
Analyzes CSV, Parquet and JSON data with DuckDB, Polars, numpy and matplotlib, preferring a persistent kernel over repeated one-shot processes.
mims-harvard/OptimusKG
Guide for using OptimusKG, the biomedical knowledge graph, through the optimuskg Python client.
anam-org/metaxy
Effectively use Narwhals to write dataframe-agnostic code that works seamlessly across multiple Python dataframe libraries.
K-Dense-AI/scientific-agent-skills
High-performance DataFrame library for Python ETL, analytics, and pandas migration.
ancoleman/ai-design-components
Data ingestion patterns for loading data from cloud storage, APIs, files, and streaming sources into databases.
Edwardvaneechoud/Flowfile
Maps the /ai/ subsystem of flowfile_core, its three agent tiers, litellm seam, BYOK keys and rate limits, and sets rules for extending or debugging it safely.
Edwardvaneechoud/Flowfile
Maps Flowfile's core, worker, frontend, kernel, scheduler and shared services and the design contracts between them, for onboarding and cross-service debugging.
Edwardvaneechoud/Flowfile
Recreates every Flowfile development and build environment from scratch, with exact version pins and an explanation of what each Makefile target really does.
Edwardvaneechoud/Flowfile
Explains how changes to the Flowfile monorepo are gated, versioned and released, including version sync, stub and docs drift checks, Alembic migrations and pinned dependencies.
Edwardvaneechoud/Flowfile
Runbook for closing gaps between a Flowfile visual flow's results and its exported Polars or FlowFrame Python code, measured by tests rather than by eye.
Edwardvaneechoud/Flowfile
Catalog of Flowfile's environment variables and runtime flags: what each does, where the code reads it, its default, and where the docs disagree with the code.
Works with
Categories
Deep dive into flowfileframe — the Polars-LazyFrame-shaped Python API that builds an in-process flowfilecore FlowGraph as a side effect of every method call — covering the FlowFrame/Expr internals…. Flowfile Frame And Codegen is an agent skill from Edwardvaneechoud/Flowfile.pyi pipeline.
Flowfile Frame And Codegen fits situations like: debugging flowfileframe scripts; adding/changing a FlowFrame; investigating why a .collect() triggered real I/O; A write happened before .collect().
Run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a claude-code`. Or copy the skill folder (.claude/skills/flowfile-frame-and-codegen in Edwardvaneechoud/Flowfile) into .claude/skills/flowfile-frame-and-codegen in your project. Claude Code loads it when a task matches its description.
Run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a codex`. Or copy the skill folder (.claude/skills/flowfile-frame-and-codegen in Edwardvaneechoud/Flowfile) into .agents/skills/flowfile-frame-and-codegen in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frame-and-codegen -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/flowfile-frame-and-codegen, .gemini/skills/flowfile-frame-and-codegen, .github/skills/flowfile-frame-and-codegen and .opencode/skills/flowfile-frame-and-codegen in your project.
Going by SKILL.md and its folder, Flowfile Frame And Codegen needs the command-line tools its instructions call (poetry, make, ruff, git and mysql). Our summary lists: Python 3; Docker.
SKILL.md names 1 domain. In commands or code: jsonplaceholder.typicode.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Flowfile Frame And Codegen is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 12k tokens (SKILL.md is roughly 46k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Flowfile Frame And Codegen: Spark Python Data Source (databricks/databricks-agent-skills, 345 stars), Hybrid-Engine Data Analysis (code-yeongyu/oh-my-openagent, 70k stars), Optimuskg (mims-harvard/OptimusKG, 146 stars) and Narwhals (anam-org/metaxy, 124 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
Edwardvaneechoud (a GitHub user) maintains it in Edwardvaneechoud/Flowfile, which has 370 GitHub stars. The repository holds 19 skills in this directory. The repository was last updated on October 6, 2026.
Source: Edwardvaneechoud/Flowfile on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.