Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

PyPI Python 3.10+ License Code style: ruff CI CodeFactor Download Stats Documentation

funcsnap

funcsnap takes a live Python function or method, walks the objects it closes over, and collects enough information to reconstruct a self-contained Python source string you can exec() or save as a single file.

You get two complementary outputs:

  1. get_source_functions(func, ...) — a flat dictionary keyed by stable IDs, with source text, imports, and dependency edges (external_vars). Use this when you want to inspect the graph, filter it, or emit your own format.
  2. reconstruct(results) — one concatenated module string (imports, globals, definitions, optional shims) in dependency order, ready for exec().

By default, anything installed in the interpreter environment (stdlib, site-packages, venv layout) is not expanded into separate definitions; those dependencies remain references so you do not accidentally inline large third-party trees.

Install

pip install funcsnap

Quick start

from funcsnap import get_source_functions, reconstruct

results = get_source_functions(your_func)
code = reconstruct(results)
namespace = {}
exec(code, namespace)
# Call the emitted names from `namespace` (usually the function’s short name).

Public API: get_source_functions, reconstruct (funcsnap/__init__.py).

get_source_functions — result shape

The return value is a dict[str, dict]: a flat map, not a nested tree. The “tree” is the graph you get by following each entry’s external_vars list.

Keys (IDs)

  • Functions: {module}.{function_name} (e.g. mypkg.util.foo)
  • Classes: {module}.{ClassName}
  • Captured globals (non-function, non-class): {module}.{name} with type: "variable"

Entry fields

Field Used for Meaning
type all "function", "class", or "variable"
value all Source text for functions/classes; repr(value) text for variables
file function, class Path to the defining .py file (when known)
imports function, class Top-level import lines from that file that bind names used in the snippet
external_vars function, class List of other result keys or module names this piece depends on

Packages under the environment roots (see Path filters) are usually not given their own keys; they still appear in external_vars (e.g. a numpy reference) so you know what is missing from the bundle.

Worked example

Save as demo.py and run python demo.py (a real file is needed so source can be read reliably):

from funcsnap import get_source_functions

SCALE = 10


def helper(x):
    return x * SCALE


def root(y):
    return helper(y) + 1


if __name__ == "__main__":
    results = get_source_functions(root)
    print(sorted(results.keys()))

You should see something like:

['__main__.SCALE', '__main__.helper', '__main__.root']

One entry (the root function) looks like this (ellipsis added for readability):

results["__main__.root"] == {
    "type": "function",
    "value": "def root(y):\n    return helper(y) + 1\n",
    "file": "/path/to/demo.py",
    "imports": [],
    "external_vars": ["__main__.helper"],
}

The captured constant:

results["__main__.SCALE"] == {
    "type": "variable",
    "value": "10",  # repr(SCALE)
}

helper links to SCALE via its own external_vars; root links to helper. That list is what reconstruct() uses to order definitions.

Optional: narrowing with include_dirs

If you only want to recurse into code under certain roots, pass absolute directories. The entrypoint function you pass in is always recorded; dependencies are only expanded when their defining file lies under one of the include paths (after the default environment skip).

import tempfile
from funcsnap import get_source_functions

def root(y):
    return y + 1

# Empty directory: nothing under it matches dependency files → only the root remains.
with tempfile.TemporaryDirectory() as tmp:
    results = get_source_functions(root, include_dirs=[tmp])

assert sorted(results.keys()) == ["__main__.root"]

See Path filtering for the full rules.

Path filtering: include_dirs, exclude_dirs, and the default environment skip

Resolution uses FunctionAnalyzer._should_analyze in this order:

  1. Environment directories — paths from sysconfig.get_paths(), site.getsitepackages(), and site.getusersitepackages(). If a dependency’s file path lies under one of these roots, funcsnap does not recurse into it (you do not pull in the stdlib or installed packages as inlined definitions). References can still appear in external_vars.
  2. exclude_dirs — additional absolute subtrees to skip. Recursion into those files stops; IDs from excluded code may still appear in external_vars, so dependencies are not silently forgotten.
  3. include_dirs — if not None, a file is analyzed only when it falls under one of these directories (after step 1). The root function you pass in is still stored.

Use absolute paths; funcsnap normalizes with os.path.abspath.

reconstruct — single module string

reconstruct(results) turns the flat map into one string with labeled sections:

  1. Imports — needed import lines gathered from all entries. Imports are omitted when every name they bind will already be defined in the bundle. Relative imports are not emitted as-is (they break under a bare exec() without package context); when needed, funcsnap may build types.SimpleNamespace shims so attribute access like prim.clamp still resolves after definitions exist.
  2. Variables — variable entries become name = <repr> assignments.
  3. Definitions — functions and classes, ordered by a topological sort on external_vars (Kahn’s algorithm), with a cycle fallback if the graph is not a strict DAG.
  4. Name collisions — if the same short name (last segment of the key) appears for more than one full ID, definitions are emitted under prefixed names such as _mylib_core_primitives__scale, derived from the module path. A trailing collision alias block sets the bare scale to a canonical choice and exposes the others as scale__<module_slug>.
  5. Module shims — emitted after definitions when relative-import shims need already-defined names.

Safety / correctness: these steps are about making the bundle importable and consistent (renames, import elision, avoiding broken relative imports). They are not a sandbox: never exec() untrusted code you did not audit.

Example continuation from the worked example: reconstruct(results) can produce:

# --- Variables ---
SCALE = 10

# --- Definitions ---
def helper(x):
    return x * SCALE

def root(y):
    return helper(y) + 1

You can exec that string, then call namespace["root"](...) (or write it to a .py file and run it).

Limitations

  • Dynamic or opaque objects (C extensions, unusual inspect cases) may not yield usable source.
  • Relative imports inside the original package need special handling; reconstruct() avoids emitting them verbatim and may use shims, but complex package layouts can still require manual fixes.
  • The analyzer follows static name use in the source text plus runtime globals; highly dynamic patterns may be incomplete.

Development

Clone the repo, create a virtual environment, then:

pip install -e ".[dev]"
ruff check .
pytest

Version strings are derived from Git tags (hatch-vcs). Without a repository or tags, builds use the fallback version from pyproject.toml.

PyPI releases (maintainers)

  1. Configure trusted publishing on PyPI for this repository and workflow .github/workflows/publish.yml (optional GitHub Environment pypi).
  2. Merge your changes to main, then tag and push: git tag v0.1.0 && git push origin v0.1.0 (use the next semantic version). The publish workflow runs on tags matching v*.

Ensure [project.urls] Repository in pyproject.toml points at https://github.com/kyrylo-gr/funcsnap so PyPI metadata matches the project home.

License

MIT — see LICENSE.

Metadata

Release files for funcsnap 0.0.1.dev3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for funcsnap 0.0.1.dev3
File Size Uploaded Provenance
funcsnap-0.0.1.dev3.tar.gz 29.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for funcsnap 0.0.1.dev3
File Interpreter ABI Platform
funcsnap-0.0.1.dev3-py3-none-any.whl Python 3 none any Details

Total release size: 41.2 kB

Release files / funcsnap-0.0.1.dev3.tar.gz

Download URL funcsnap-0.0.1.dev3.tar.gz
Size 29.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ba8df3c1072c8522090c32ebf64de82470ee4c8809ba9c0dc8d0f005dec10a5c
BLAKE2b-256 checksum
How to use checksums
7eceec97d5910356d7c39a991cfe41bfe946c349a3ae9f8c1471310315b98057
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / funcsnap-0.0.1.dev3-py3-none-any.whl

Download URL funcsnap-0.0.1.dev3-py3-none-any.whl
Size 11.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9b4e49d8933e5096a5055a06446d89d460a39b841ddd9ff7278296f6377239a7
BLAKE2b-256 checksum
How to use checksums
4f424a16b36619740c2369b0b49c085b45ae19bfe358813ffd0159915e03a8dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Security for funcsnap 0.0.1.dev3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page