Pathbase API
This guide focuses on the Python API surface that is most useful when
integrating pathbase into tools, launchers, and pipeline code.
Core Imports
from pathbase import Template, find_matching_templates, match_template
Parse a Path Without Knowing the Template
This is the most important integration pattern for many tools: start with a
real filepath, let pathbase discover the matching template automatically, and
get the parsed fields back.
If you want the shortest Python API version of that flow, use
Template.from_path(...) and then parse through the returned template object.
from pathbase import Template
path = "/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr"
template = Template.from_path(path)
fields = template.parse(path)
print(template.template)
print(fields)
Expected output:
${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}
{
"show": "bigbuckbunny",
"sequence": "seq001",
"shot": "shot010",
"step": "lighting",
"task": "render",
"descriptor": "beauty",
"version": 1,
"frame": 1001,
"ext": "exr",
}
Once you have the discovered template object, you can round-trip the parsed fields back through it. For example, bump the version and format the next path:
fields["version"] = fields["version"] + 1
next_path = template.format(**fields)
print(next_path)
Expected output:
/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v002.1001.exr
If you need the equivalent path for another platform and envstack is
installed, you can parse the source path once and ask pathbase to resolve the
target-platform template from the named envstack stack:
from pathbase import Template
path = "/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr"
template = Template.from_path(path)
windows_path = template.to_platform(path, "windows")
print(windows_path)
Expected output, assuming the windows platform resolves ROOT=D:/projects:
D:/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr
If envstack is not installed, you can still do the same conversion by passing
an explicit target environment mapping:
from pathbase import Template
path = "/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr"
template = Template.from_path(path)
windows_path = template.to_platform(
path,
"windows",
target_env={
"ROOT": "D:/projects",
"FILEPATH": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}",
},
)
print(windows_path)
If you also want the matched environment variable name, use
match_template(...):
from pathbase import match_template
env = {
"ROOT": "/mnt/projects",
"FILEPATH": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}",
"FILEPATH_V1": "${ROOT}/{show}/{sequence}/{shot}/{task}/{task}_{descriptor}.{frame:04d}.{ext}",
"FILEPATH_V2": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_f{frame:04d}.{ext}",
}
path = "/mnt/projects/bigbuckbunny/seq001/shot010/render/render_beauty.1001.exr"
template_name, template = match_template(path, env=env)
fields = template.parse(path)
print(template_name)
print(fields)
Expected output:
FILEPATH_V1
{
"show": "bigbuckbunny",
"sequence": "seq001",
"shot": "shot010",
"task": "render",
"descriptor": "beauty",
"frame": 1001,
"ext": "exr",
}
This does not require the caller to preselect FILEPATH_V1. The path is
matched automatically against all available templates in the environment.
Discovery Scope
pathbase matches against the resolved runtime environment, not only against a
pathbase.env file. That means templates can live in tool-local env files such
as mytool.env, render.env, or any other envstack-managed source, as long as
those variables are present in the environment seen by the Python process.
Auto-discovery is intentionally conservative:
- only string values that look like path templates are considered
- malformed candidate templates are skipped during discovery
- path separators are normalized during matching, so
/and\do not break cross-platform parsing
If your process loads many template-like env vars and discovery becomes
ambiguous, pass a narrower env= mapping, use Template.from_env(...), or
specify the template name explicitly.
Frame Tokens: Numeric vs Flexible
When you use {frame:04d}, pathbase treats the field as numeric and returns
an integer when parsing a concrete file path.
from pathbase import Template
template = Template(
"${ROOT}/{show}/{sequence}/{shot}/{step}/"
"{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}",
env={"ROOT": "/mnt/projects"},
)
fields = template.parse(
"/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr"
)
print(fields["frame"])
print(type(fields["frame"]).__name__)
Expected output:
1001
int
When you use {frame}, pathbase leaves the value flexible, which is useful
for frame-pad expressions such as %08d.
from pathbase import Template
template = Template(
"${ROOT}/{show}/{sequence}/{shot}/{step}/"
"{task}_{descriptor}_v{version:03d}.{frame}.{ext}",
env={"ROOT": "/mnt/projects"},
)
fields = template.parse(
"/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.%08d.exr"
)
print(fields["frame"])
print(type(fields["frame"]).__name__)
Expected output:
%08d
str
Choose based on the paths your tools need to parse:
{frame:04d}for concrete frame files with numeric typing{frame}for flexible frame tokens, including frame pads like%04dand%08d
Token Rules
By default a string token matches any text without a separator, and
{frame:04d} matches any number of digits. Two kinds of rule narrow that.
Both apply when parsing and matching. format() also checks values against
them and raises FieldFormatError, so a formatted path always parses back.
Inline choices
List the allowed values after the field name, separated by |:
${ROOT}/{show}/{shot}/mov/{shot}_{version}.{ext|mov|mp4}
ext now matches only mov or mp4, so a .png file no longer matches this
template. Format specs still follow a colon: {take|1|2:02d} matches 01 and
02. A field used more than once lists its choices once, on any occurrence,
and they constrain every occurrence. Listing different choices for the same
field is an error. Template.choices returns the allowed values for each
field.
Regex rules
Rules too long for inline choices are kept out of the template, as a map from token name to regex. They can be passed directly:
template = Template.from_env(
"FILEPATH",
rules={"version": "^v[0-9]{3}$", "frame": "^[0-9]{4}$"},
)
Rules can also be named by the PATHBASE_RULES environment variable, as a
JSON file path or inline JSON. Global rules apply to every template.
Per-template rules are layered over them, in the same shape as ioscan specs:
{
"rules": {"version": "^v[0-9]{3}$", "shot": "^[0-9]{2}[a-z]{2}_[0-9]{4}$"},
"templates": {"PLATE_FILE": {"rules": {"frame": "^[0-9]{8}$"}}}
}
PATHBASE_RULES can list several files, separated by os.pathsep (:, or
; on Windows). They are layered in order and later files win. Global rules
merge token by token, and so does each template’s rules. A tool can add its
own rules after a shared file instead of copying it:
PATHBASE_RULES: ${PATHBASE_RULES}:${DEPLOY_ROOT}/conf/mytool/rules.json
A listed file that does not exist is skipped with a warning. The vfx
example ships a rules.json (see Distribution).
A rule replaces the token’s default pattern, so the match finds the split
that satisfies it. Anchors (^ or \A, $ or \Z) are optional, but only at
the very start and end: write alternatives as ^(run-[0-9]+|[0-9a-f]{8})$, not
^run-[0-9]+$|^[0-9a-f]{8}$. A rule should not match a path separator. A
rule that keeps a delimiter out of a token, such as ^[a-z][a-z0-9-]*$ for
{task} in {task}_{descriptor}, also makes the split between neighbouring
tokens unambiguous. When a field has both inline choices and a rule, the
choices win, and Template.rules returns only the rules in effect.
An unreadable rules file, or a rule that is not a valid regex, raises
InvalidRulesError. These errors are raised even from
find_matching_templates, so a mistake cannot skip every template silently.
Format a Path
Use Template.format(...) when your tool already knows which template it wants
to write.
from pathbase import Template
template = Template(
"${ROOT}/{show}/{sequence}/{shot}/{step}/"
"{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}"
)
path = template.format(
show="bigbuckbunny",
sequence="seq001",
shot="shot010",
step="lighting",
task="render",
descriptor="beauty",
version=1,
frame=1001,
ext="exr",
ROOT="/mnt/projects",
)
print(path)
Expected output:
/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr
Parse a Path
Use Template.parse(path) when the template is already known and you want the
fields back.
from pathbase import Template
template = Template(
"${ROOT}/{show}/{sequence}/{shot}/{step}/"
"{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}"
)
fields = template.parse(
"/mnt/projects/bigbuckbunny/seq001/shot010/lighting/render_beauty_v001.1001.exr"
)
print(fields)
Expected output:
{
"show": "bigbuckbunny",
"sequence": "seq001",
"shot": "shot010",
"step": "lighting",
"task": "render",
"descriptor": "beauty",
"version": 1,
"frame": 1001,
"ext": "exr",
}
Read a Template From the Environment
Use Template.from_env(...) when your integration reads template strings from
environment variables.
import os
from pathbase import Template
os.environ["FILEPATH"] = (
"${ROOT}/{show}/{sequence}/{shot}/{step}/"
"{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}"
)
os.environ["ROOT"] = "/mnt/projects"
template = Template.from_env("FILEPATH")
print(template.format(
show="bigbuckbunny",
sequence="seq001",
shot="shot010",
step="lighting",
task="render",
descriptor="beauty",
version=1,
frame=1001,
ext="exr",
))
Discover the Matching Template for a Path
Use match_template(path, env=...) when your tool has a real filepath and
needs to know which environment template it belongs to.
from pathbase import match_template
env = {
"ROOT": "/mnt/projects",
"FILEPATH": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}",
"FILEPATH_V1": "${ROOT}/{show}/{sequence}/{shot}/{task}/{task}_{descriptor}.{frame:04d}.{ext}",
"FILEPATH_V2": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_f{frame:04d}.{ext}",
}
name, template = match_template(
"/mnt/projects/bigbuckbunny/seq001/shot010/render/render_beauty.1001.exr",
env=env,
)
print(name)
print(template.parse("/mnt/projects/bigbuckbunny/seq001/shot010/render/render_beauty.1001.exr"))
Expected output:
FILEPATH_V1
{
"show": "bigbuckbunny",
"sequence": "seq001",
"shot": "shot010",
"task": "render",
"descriptor": "beauty",
"frame": 1001,
"ext": "exr",
}
Build a Template Directly From a Path
Use Template.from_path(...) when you want the matching Template object
first and then want to parse or inspect it.
from pathbase import Template
env = {
"ROOT": "/mnt/projects",
"FILEPATH": "${ROOT}/{project}/{name}_v{version:03d}.txt",
"FILEPATH_V1": "${ROOT}/{project}/{name}.{ext}",
}
template = Template.from_path("/mnt/projects/demo/report_v001.txt", env=env)
print(template.template)
print(template.parse("/mnt/projects/demo/report_v001.txt"))
Expected output:
${ROOT}/{project}/{name}_v{version:03d}.txt
{
"project": "demo",
"name": "report",
"version": 1,
}
Inspect Multiple Possible Matches
Use find_matching_templates(...) when you want to detect and handle ambiguity
yourself.
from pathbase import find_matching_templates
env = {
"ROOT": "/mnt/projects",
"FILEPATH": "${ROOT}/{project}/{name}.txt",
"ALT_FILEPATH": "${ROOT}/{project}/{artifact}.txt",
}
matches = find_matching_templates("/mnt/projects/demo/report.txt", env=env)
print([name for name, _template in matches])
Expected output:
["FILEPATH", "ALT_FILEPATH"]
Advanced: Current and Legacy Template Families
If your production has evolved over time, you can keep one current template and one or more legacy variants side by side.
from pathbase import match_template
env = {
"ROOT": "/mnt/projects",
"FILEPATH": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_v{version:03d}.{frame:04d}.{ext}",
"FILEPATH_V1": "${ROOT}/{show}/{sequence}/{shot}/{task}/{task}_{descriptor}.{frame:04d}.{ext}",
"FILEPATH_V2": "${ROOT}/{show}/{sequence}/{shot}/{step}/{task}_{descriptor}_f{frame:04d}.{ext}",
}
path = "/mnt/projects/bigbuckbunny/seq001/shot010/render/render_beauty.1001.exr"
template_name, template = match_template(path, env=env)
print(template_name)
print(template.parse(path))
Expected output:
FILEPATH_V1
{
"show": "bigbuckbunny",
"sequence": "seq001",
"shot": "shot010",
"task": "render",
"descriptor": "beauty",
"frame": 1001,
"ext": "exr",
}
Path-Like Inputs
Template, Template.parse(...), match_template(...), and
Template.from_path(...) accept path-like values, not just plain strings.
from pathlib import Path
from pathbase import Template
template = Template("${ROOT}/{project}/{name}.txt", env={"ROOT": "/mnt/projects"})
fields = template.parse(Path("/mnt/projects/demo/report.txt"))
print(fields)
Expected output:
{
"project": "demo",
"name": "report",
}