Authoring reference
Profile DSL reference
The Profile DSL is the Python API behind Pinman Profile Scripts. It declares a Profile's metadata, Interface, desired configuration, included Profiles, live controls, and ordered actions. Pinman evaluates the script to build the Profile shown in the UI and used by Preview, Apply, and Run.
This page specifies the DSL calls and their arguments. For a progressive introduction with complete examples, start with Profile Scripts.
Import and structure#
A Profile Script imports profile(), creates one Profile, and keeps the returned
builder in a variable—conventionally p:
from pinman.profiles import profile
p = profile("Quiet Audio", zone_tags=["Audio"])
p.target("winrt:/Sound/Master/Volume", value=0.30)
Declaration order is meaningful. Declare the complete Interface first. Included
content contributes where p.include() appears, later direct targets can
override earlier ones, and steps run in order.
Common types#
| Type | Accepted values |
|---|---|
ProfileValue | str, int, float, or bool |
| Item location | A tracked Pinman location, which may contain Project or device-role bindings. |
itemtype | "auto" by default, or an explicit Schema itemtype for an advanced override. |
| Input type | "text", "number", "select", or "boolean" |
Profile values cannot be bytes. Use the text representation expected by the
Item adapter—for example, Registry binary data uses hex: text.
profile()#
profile(
name: str,
zone_tags: list[str] | None = None,
description: str | None = None,
software_refs: list[str] | None = None,
status: str = "in_sync",
) -> ProfileBuilder
Creates the Profile and returns the builder used for the remaining declarations.
| Argument | Description |
|---|---|
name | Reader-facing Profile name. |
zone_tags | Zones associated with the Profile. |
description | Brief explanation of the configuration or action. |
software_refs | Software Catalog entries associated with the Profile. |
status | Initial builder metadata. Ordinary Profile Scripts leave the default; Pinman's comparison determines the operational status. |
Call profile() once in each script. Its return value is the only public object
needed to declare Profile content.
p = profile(
"Recording Session",
description="Prepare audio and display settings for recording",
zone_tags=["Audio", "Display"],
software_refs=["example:recorder"],
)
Interface#
p.input()#
p.input(
name: str,
label: str,
type: Literal["text", "number", "select", "boolean"] = "text",
default: ProfileValue = REQUIRED,
choices: dict[str, str] | None = None,
choice_sources: dict[str, str] | None = None,
help: str | None = None,
min_value: float | None = None,
max_value: float | None = None,
step: float | None = None,
group: str | None = None,
order: int | None = None,
widget: str | None = None,
) -> None
Declares one public Interface input. All inputs must be declared before the
script reads p.inputs or adds Profile content.
REQUIRED is descriptive notation in this signature, not a name to import:
omit default to make the input required.
| Argument | Description |
|---|---|
name | Public Python identifier used by p.inputs.<name>. It cannot begin with _. |
label | Reader-facing label. |
type | Input type; defaults to "text". |
default | Default value. Omit it to require the person using the Profile to supply a value. |
choices | For select, a mapping of stored values to reader-facing labels. |
choice_sources | Optional provenance labels for select choices. Authoring tools use this to retain where a choice came from; it does not change runtime behavior. |
help | Explanatory text displayed with the input. |
min_value, max_value | Inclusive bounds for a number input. |
step | Positive increment presented for a number input. |
group | Optional visual grouping name. |
order | Optional position within its group. |
widget | Optional visual-editor widget preference. |
choices and choice_sources apply only to select. Numeric bounds and
step apply only to number; bounds and defaults must be finite, and step
must be greater than zero.
p.input(
"mode",
label="Audio mode",
type="select",
default="game",
choices={"game": "Game", "quiet": "Quiet"},
help="Choose the master-audio configuration.",
)
p.inputs.<name>#
value: ProfileValue = p.inputs.<name>
Returns the validated runtime value of a declared input. A supplied value takes precedence over its default. Reading a required input without supplying a value stops evaluation and identifies the missing input.
| Input type | Returned type |
|---|---|
text | str |
select | str choice key |
number | int or float |
boolean | bool |
Reading p.inputs closes the Interface, so no later p.input() calls are
accepted.
mode = p.inputs.mode
volume = 0.80 if mode == "game" else 0.30
Desired configuration#
p.target()#
p.target(
location: str,
value: ProfileValue,
itemtype: str = "auto",
enabled: bool = True,
authored_from: str | None = None,
) -> None
Declares the desired value of one tracked property.
| Argument | Description |
|---|---|
location | Writable Item location. |
value | Desired value. |
itemtype | Itemtype override. Leave "auto" to use the active Schema. |
enabled | When False, retain the declaration but exclude it from comparison and Apply. |
authored_from | Optional authoring-provenance label, such as a Capture name. It does not affect Apply. |
Outside a step, the target participates in comparison and Apply. Inside
p.step(), the same call declares an ordered write that always runs and does
not represent lasting desired state.
p.target("winrt:/Sound/Master/Volume", value=0.30)
p.target("winrt:/Sound/Master/Mute", value=False)
p.remove()#
p.remove(
location: str,
itemtype: str = "auto",
enabled: bool = True,
authored_from: str | None = None,
) -> None
Declares that one tracked property should be absent.
| Argument | Description |
|---|---|
location | Writable Item location to remove. |
itemtype | Itemtype override; normally "auto". |
enabled | When False, retain the declaration without comparing or applying it. |
authored_from | Optional authoring-provenance label. |
Inside p.step(), removal becomes an ordered stage action rather than lasting
desired state.
p.remove("fs:/C/Example/recorder.ini#capture/obsolete_option")
p.file()#
p.file(
location: str,
*,
asset: str,
itemtype: str = "auto",
enabled: bool = True,
authored_from: str | None = None,
) -> None
Declares that a tracked file or directory should match an asset stored with the Profile.
| Argument | Description |
|---|---|
location | Destination file or directory location. Do not include a structured-file # data path. |
asset | Relative path below the Profile's asset directory. |
itemtype | "auto", "file", or "directory". With "auto", Pinman infers the kind from the asset. |
enabled | When False, exclude the file target from comparison and Apply. |
authored_from | Optional authoring-provenance label. |
For presentation.py, assets live under presentation/ beside the script:
profiles/
presentation.py
presentation/
layout.json
p.file("fs:/C/Example/layout.json", asset="layout.json")
p.file() cannot appear inside p.step().
p.guided()#
p.guided(
location: str,
value: ProfileValue,
*,
software: str,
itemtype: str = "auto",
instructions: list[str] | None = None,
verify: bool = True,
enabled: bool = True,
) -> None
Declares desired state that a person completes through another program.
| Argument | Description |
|---|---|
location | Tracked Item location. |
value | Desired value. |
software | Software Catalog ID for the program used to make the change. |
itemtype | Itemtype override; normally "auto". |
instructions | Ordered steps shown to the person. When omitted, Pinman can resolve guidance from reference data. |
verify | Re-read the Item after the person completes the steps; defaults to True. |
enabled | When False, skip this guided target. |
p.guided(
"winrt:/GPU/Global/PowerManagementMode",
"PreferMax",
software="nvidia:app",
instructions=[
"Open the NVIDIA app.",
"Open the global graphics settings.",
"Set Power management mode to Prefer maximum performance.",
],
)
p.guided() cannot appear inside p.step().
Read current state#
p.read()#
p.read(
location: str,
itemtype: str = "auto",
) -> ProfileValue | None
Makes a current captured value available to Profile logic. The read is not a target: it is not compared, applied, or included in Profile status.
| Argument | Description |
|---|---|
location | Item location to read. |
itemtype | Itemtype override; normally "auto". |
Pinman evaluates a script using p.read() in two passes. The first pass records
the requested locations; the second returns their captured values. The call
returns None when no value is available or when an authoring command evaluates
the script without a Project database session. Handle that case explicitly.
current = p.read("winrt:/Sound/Master/Volume")
if current is not None:
p.target(
"winrt:/Sound/Master/Volume",
value=max(0.0, float(current) - 0.10),
)
Composition#
p.include()#
p.include(
profile_path: str,
inputs: dict[str, object] | None = None,
input_map: dict[str, str] | None = None,
) -> None
Evaluates another Profile Script and contributes its content at the point of the call. The path is relative to the containing Profile.
| Argument | Description |
|---|---|
profile_path | Relative path to the included Profile Script. |
inputs | Fixed, private values for child inputs. |
input_map | Mapping from a child input name to an explicitly declared input in the containing Profile. |
A child input named in neither mapping is promoted into the containing Profile's Interface under the same name. Compatible inputs promoted by multiple children share one Interface field.
p.input(
"audio_mode",
label="Audio mode",
type="select",
default="game",
choices={"game": "Game", "quiet": "Quiet"},
)
p.include(
"audio.py",
input_map={"mode": "audio_mode"},
)
p.include("display.py", inputs={"resolution": "high"})
The same child input cannot be both fixed and mapped. Mapped input types and
ranges must be compatible. Circular includes are rejected, and p.include()
cannot appear inside p.step().
Included direct, read, guided, and file targets retain their source identity. Included controls and ordered steps also contribute to the containing Profile.
p.drop()#
p.drop(predicate: Callable[[Target], bool]) -> None
Removes direct targets collected before the call when predicate(target)
returns True. It is useful after p.include() when the containing Profile
replaces selected child targets.
The predicate can inspect these public target fields:
| Field | Meaning |
|---|---|
location | Authored Item location. |
itemtype | Authored itemtype or "auto". |
target_value | Desired value. |
action | "set" or "remove". |
enabled | Whether the target participates in comparison and Apply. |
authored_from | Optional provenance label. |
p.include("base.py")
p.drop(lambda target: target.location == "winrt:/Sound/Master/Volume")
p.target("winrt:/Sound/Master/Volume", value=0.60)
p.drop() filters ordinary p.target() and p.remove() declarations. File,
guided, read, control, and step declarations are separate collections.
Live controls#
p.control()#
p.control(
location: str | None = None,
*,
input: str | None = None,
group: str = "",
order: int = 0,
widget: WidgetKind | None = None,
step: float | None = None,
label: str | None = None,
value: ProfileValue | None = None,
) -> None
Places a tracked property or declared Interface input on a live control surface. A control reads and writes immediately; it does not represent desired state and is not part of comparison or Apply.
Supply exactly one of location or input.
| Argument | Description |
|---|---|
location | Property location for a property control. |
input | Name of a previously declared Interface input. |
group | Visual group label. |
order | Position within the group. Equal values retain declaration order. |
widget | Optional widget override. |
step | Numeric increment override for a property control. |
label | Reader-facing label override. |
value | Fixed value written by a property button. |
WidgetKind is one of "slider", "toggle", "stepper", "select",
"text", "color", "duration", or "button". Omitted property-control
presentation fields come from the Schema Item.
An input control uses the input's range and step metadata, so it does not accept
step or value:
p.control(
"rest:/audio/volume",
group="Sound",
order=10,
widget="slider",
)
p.control(
input="mode",
group="Configuration",
order=20,
)
The same property may be declared more than once with different widgets.
Ordered actions#
p.step()#
p.step(hold: float = 0.0) -> StepSpec
Creates one stage in an ordered action. Use the returned object as a context manager:
with p.step(hold=10):
p.target("rest:/display/message", value="Shutting down in 10 seconds")
with p.step():
p.invoke("rest:/system/shutdown")
| Argument | Description |
|---|---|
hold | Seconds to wait after this stage and before the next. It must be zero or greater. |
Steps run in declaration order after any ordinary direct targets. Targets and removals inside a step are stage directions: Pinman writes them every time the Profile runs, and they do not contribute to Profile status.
Steps form a flat sequence and cannot be nested. Use p.step() only with
with; p.guided(), p.file(), and p.include() are not valid inside a
step.
p.invoke()#
p.invoke(location: str) -> None
Invokes the tracked REST command at location when the Profile runs. A command
has no value and does not participate in comparison.
Inside p.step(), the command joins that stage. A bare p.invoke() creates a
one-command step.
with p.step():
p.invoke("rest:/system/reload")
Run a Profile containing steps or commands with pinman profile run.
Apply hooks#
p.on_before_apply#
@p.on_before_apply
def before(targets: list[Target]) -> None:
...
Registers one function to run immediately before Pinman begins the Profile's writes. The function receives the declared direct targets. Raising an exception stops Apply before those writes begin.
The decorator returns the original function. Registering another before hook replaces the earlier one.
@p.on_before_apply
def before(targets):
print(f"Applying {len(targets)} direct targets")
p.on_after_apply#
@p.on_after_apply
def after(result: ApplyResult) -> None:
...
Registers one function to run when Apply finishes, including an Apply that
stops before writing. Completed writes and their history are recorded before
the hook runs. The function receives an ApplyResult with these commonly
useful fields:
| Field | Type | Meaning |
|---|---|---|
success | bool | Whether Apply completed successfully. |
changed_count | int | Existing values changed. |
added_count | int | Values added. |
removed_count | int | Values removed. |
unchanged_count | int | Targets already at the requested value. |
failed_count | int | Targets that failed. |
details | list | Structured failure and warning details. |
history_record_id | int | None | Apply-history record used by Undo. |
The decorator returns the original function. Registering another after hook replaces the earlier one. If an after hook raises, completed writes remain applied and available to Undo.
@p.on_after_apply
def after(result):
print(
f"Changed {result.changed_count}; "
f"failed {result.failed_count}"
)
Validation errors#
The DSL raises an error while evaluating the script when a declaration is not coherent. Common examples include:
- reading an undeclared or missing required input;
- declaring an input after Profile content;
- supplying an unknown select choice or an out-of-range number;
- fixing and mapping the same child input;
- including Profiles in a cycle;
- giving
p.control()bothlocationandinput; and - nesting steps or using a negative hold.
Use the authoring commands to catch these errors without changing the Machine:
pinman profile authoring check path\to\profile.py
pinman profile authoring lint path\to\profile.py
Supply required or non-default Interface values with repeated --input name=value options. check reports the resulting declarations; lint also
checks writable target structure. Preview is the Project-aware validation step
that resolves bindings, Schema Items, and write support.

