Folders
Folders organize your Jobs, WorkData, ProcessingSteps, action templates and cron triggers
in a tree, like directories on a file system.
Every owner — your user, or your organization — has its own tree, starting at the root /.
An item is filed in at most one folder; an item that is in no folder sits at the root.
Filing an item only changes where it is listed: its content and tags stay the same.
The examples below assume that client is an already configured PineXQ client.
See Set up the Client for authentication and client setup.
from pinexq.client.job_management import ( ActionTemplate, ActionTemplateQuery, CronTrigger, CronTriggerQuery, Folder, Job, ProcessingStep, WorkData,)Paths and names
Section titled “Paths and names”A folder is identified by its path, for example /projects/alpha/.
Paths start and end with /; the root is /.
A folder name is one path segment:
- Allowed are letters and digits of any script, spaces,
-,_,., and symbols such as+,=,$or€. - Other punctuation is not allowed, for example
,,!,#,&,(,),@or:. Neither are/, the characters* ? [ ] { } \, control characters, and the names.and... - A name has no leading or trailing whitespace and is at most 255 bytes long; a whole path is at most 2048 bytes.
- Two folders with the same parent cannot have the same name.
Creating folders
Section titled “Creating folders”Folder.create() creates a folder at the root, or under a parent:
projects = Folder(client).create(name="projects")alpha = Folder(client).create(name="alpha", parent=projects)
print(alpha.path) # /projects/alpha/print(alpha.parent().path) # /projects/To work with an existing folder, load it from its URL with Folder.from_url(client, url), or find it with a
query.
Filing items
Section titled “Filing items”Create a Job or upload WorkData directly into a folder:
job = Job(client).create(name="alpha-run", folder=alpha)
with open("measurements.csv", "rb") as file: workdata = WorkData(client).create( filename="measurements.csv", file=file, mediatype="text/csv", folder=alpha, )Action templates and cron triggers can be created in a folder as well:
automation = Folder(client).create(name="automation")
template = ActionTemplate(client).create( name="daily-report-template", description="Create the daily report.", processing_step_url=processing_step.self_link(), folder=automation,)trigger = CronTrigger(client).create( title="daily-report-trigger", description="Runs the daily report every morning.", cron_expression="0 9 * * *", enabled=False, folder=automation,)Move existing items with move_to_folder().
Without a folder, the item moves back to the root:
workdata.move_to_folder(alpha)processing_step.move_to_folder(projects)template.move_to_folder(projects)job.move_to_folder() # back to the rootFiling a template or trigger only organizes it. A template’s folder does not decide where the Jobs created from it go (see Folder for Jobs created from a template), and moving a trigger does not change its schedule or the templates linked to it. A trigger and the templates it runs are filed independently of each other.
Each item’s HCO shows where it is filed: folder_path holds the path, and parent_folder_link leads to the folder.
print(workdata.work_data_hco.folder_path) # /projects/alpha/print(template.action_template_hco.folder_path) # /projects/print(trigger.cron_trigger_hco.folder_path) # /automation/A sub-job starts at the root. Pass inherit_parent_folder=True to file it in its parent’s folder instead:
sub_job = parent_job.create_sub_job("shard-1", inherit_parent_folder=True)Copying WorkData between your user and your organization (see Sharing with your organization) keeps its folder: the copy is filed under the same path at the destination, and missing folders are created.
Browsing and finding folders
Section titled “Browsing and finding folders”A folder lists its child folders and its items, page by page.
child_folders() collects all child folders; contents() returns a page of the items filed directly in the folder,
ordered by creation date, newest first.
Use iter_flat() on a result to walk through all its pages.
for child in projects.child_folders(): print(child.path)
page = alpha.contents()print(page.total_entities)for item in page.iter_flat(): # JobHco, WorkDataHco, ProcessingStepHco, ActionTemplateHco or CronJobTriggerHco print(item)
# or by type, for the current pagejobs, workdatas, steps = page.jobs, page.workdatas, page.processing_stepstemplates, triggers = page.action_templates, page.action_triggersWith contains, only child folders that hold an item of the given type, directly or in a folder below them,
are listed. This is useful when you are looking for a folder with WorkData in it:
from pinexq.client.job_management.hcos import FoldableItemType
with_data = projects.child_folders(contains=FoldableItemType.WORKDATA)The item types are JOB, WORKDATA, PROCESSING_STEP, ACTION_TEMPLATE and ACTION_TRIGGER.
children() takes the same contains argument and returns one page of child folders instead of all of them.
The root has the same two listings: Folder.root_children(client) for your top-level folders,
and Folder.root_contents(client) for the items that are in no folder.
To find folders anywhere in your tree, query them by path prefix, name, or parent.
Folder.suggest() returns matching paths, for example to complete user input:
result = Folder.query(client, path_prefix="/projects/")for folder in result.iter_flat(): print(folder.path)
print(Folder.suggest(client, prefix="/proj")) # ['/projects/', '/projects/alpha/']Filtering queries by folder
Section titled “Filtering queries by folder”The Job, WorkData, ProcessingStep, action template and cron trigger queries take a folder filter: folder is the
folder’s path, and folder_recursive=True includes items in its descendant folders as well (the default is False,
the folder only).
The template and trigger query helpers take the filter directly:
templates = ActionTemplateQuery.create(client, folder="/projects/", folder_recursive=True).to_list()triggers = CronTriggerQuery.create(client, folder="/automation/").to_list()The Job, WorkData and ProcessingStep query helpers described in Searching for resources do not offer this filter yet, so set it on the query parameters:
from pinexq.client.job_management import enter_jmafrom pinexq.client.job_management.model import WorkDataFilterParameter
workdata_root = enter_jma(client).work_data_root_link.navigate()parameters = workdata_root.query_action.default_parameters()parameters.filter = WorkDataFilterParameter(folder="/projects/", folder_recursive=True)
result = workdata_root.query_action.execute(parameters)for workdata_hco in result.workdatas: print(workdata_hco.name, workdata_hco.folder_path)Jobs and ProcessingSteps work the same way, with JobFilterParameter on
enter_jma(client).job_root_link.navigate().job_query_action, and ProcessingStepFilterParameter on
enter_jma(client).processing_step_root_link.navigate().query_action.
Renaming and moving folders
Section titled “Renaming and moving folders”archive = Folder(client).create(name="archive")
alpha.rename("alpha-2026") # now /projects/alpha-2026/alpha.move(new_parent=archive) # now /archive/alpha-2026/alpha.move() # back to the root: /alpha-2026/Renaming or moving a folder updates the paths of all folders below it, and the items inside move along with it. A folder cannot be moved into its own subtree.
Deleting folders
Section titled “Deleting folders”delete() deletes an empty folder. A folder that still holds items or child folders is refused.
archive.delete() # empty again after alpha moved back to the rootclear() deletes the items in a folder and in all its descendant folders, but keeps the folders themselves.
It is best effort: an item that cannot be deleted — for example WorkData that is not marked as deletable, or
that a Job or an action template uses as input — is skipped, not forced.
Cron triggers are deleted first, so a trigger cannot start a Job from a template that is about to be deleted. Then come action templates, Jobs, ProcessingSteps and WorkData. Because the templates are gone by then, WorkData that was only used as input by a template in the cleared folders can be deleted too. Deleting a template unlinks it from every trigger, including triggers in other folders.
The returned report lists every item:
report = projects.clear()print(report.deleted_count, report.skipped_count)for outcome in report.outcomes: print(outcome.item_type, outcome.item_id, outcome.status, outcome.reason)Folders as job input
Section titled “Folders as job input”An input DataSlot that takes a collection of WorkData can read it from a folder instead of a list of selected WorkData.
The convenience class has no method for this yet, so use the action on the DataSlot’s HCO.
recursive=True, the default, includes WorkData in the folder’s descendant folders too:
from pinexq.client.job_management.model import SetDataSlotFolderSourceParameters
slot = job.get_input_data_slots()[0]slot.set_folder_source_action.execute( SetDataSlotFolderSourceParameters( folder_url=str(alpha.self_link().get_url()), # None for your root recursive=True, ))job.refresh()The folder must exist when you set it. The DataSlot stores the folder’s path, and reads the WorkData in it when the Job is started. If the folder no longer exists then, or its WorkData does not fit the DataSlot (too few or too many files, or a media type the DataSlot does not accept), starting the Job returns an error and the Job goes to the Error state.
To freeze the WorkData before the Job starts, resolve the DataSlot: it then keeps the folder’s current WorkData, and the Job uses that set even if the folder changes. Resolving checks the set against the DataSlot and raises an error if it does not fit. Resolve again to refresh the set, or clear it to read the folder at start again:
job.resolve_input_dataslot(0)job.clear_resolved_input_dataslot(0)Action templates have the same folder sources, with convenience methods:
template.set_dataslot_folder_source(slot_index=0, folder=alpha, recursive=True)template.resolve_dataslot(slot_index=0) # every Job created from the template uses this settemplate.clear_resolved_dataslot(slot_index=0)Where output WorkData goes
Section titled “Where output WorkData goes”The output WorkData of a Job is created when the Job is started. Each output DataSlot decides where it goes:
| Output DataSlot target | Output WorkData is filed in |
|---|---|
| none (the default) | the Job’s folder, or the root for a Job at the root |
a path, e.g. /projects/alpha/results/ |
that folder |
/ |
the root, even for a Job in a folder |
Set the target with set_output_folder(). A missing folder, and any missing folders above it, are created when the
Job starts. An empty path or None clears the target:
job.set_output_folder(0, "/projects/alpha/results/")job.set_output_folder(1, "/") # always the rootjob.set_output_folder(0, None) # back to the Job's folder
print(job.get_output_data_slots()[0].target_folder)The Job’s folder is read when the Job is started. Moving the Job afterwards does not move its output WorkData.
A sub-job created with inherit_parent_folder=True sits in its parent’s folder, so its output goes there as well.
Folder for Jobs created from a template
Section titled “Folder for Jobs created from a template”An action template can set the folder that its Jobs are created in, whether a cron trigger or execute_now()
creates them. Set it with set_job_folder(); None, an empty path or / clears it:
template.set_job_folder("/reports/daily/")print(template.action_template_hco.job_folder) # /reports/daily/
template.set_job_folder(None) # Jobs are created at the root again| Template job folder | Job is created in |
|---|---|
none (the default), empty or / |
the root |
a path, e.g. /reports/daily/ |
that folder |
A missing folder, and any missing folders above it, are created when a Job is created, in the tree of the Job’s owner. A template sets no output target folders, so the output WorkData of its Jobs is filed in the Job’s folder (see Where output WorkData goes): the job folder, or the root without one.
The template’s own folder does not matter here: a template filed in /automation/ without a job folder creates its
Jobs at the root. That is also why / means the same as no job folder, unlike the target of an output DataSlot.
Dated job folders
Section titled “Dated job folders”A job folder can contain date placeholders, filled in each time a Job is created from the template, so the template spreads its Jobs over one folder per day:
template.set_job_folder("/reports/{{date+1@Europe/Berlin}}/")print(template.action_template_hco.job_folder) # /reports/{{date+1@Europe/Berlin}}/, as written| Placeholder | Filled in with |
|---|---|
{{date}} |
the date in UTC |
{{date+N}}, {{date-N}} |
that date moved by N days, at most 365 either way |
{{date@Europe/Berlin}}, {{date+1@Europe/Berlin}} |
the same in that time zone |
Dates are always written yyyy-MM-dd, and the date is the one at which the Job is created: with the job folder above,
a Job created at 00:30 on 2026-10-14 in Berlin goes to /reports/2026-10-15/. A placeholder can be a whole folder
name or part of one (/reports/run-{{date}}/), and a path can contain several. The time zones are the IDs cron
triggers accept, such as Europe/Berlin or UTC.
set_job_folder() raises an ApiException naming the reason for an unknown placeholder, an offset beyond 365 days,
an unknown time zone, or a path that is not a valid folder path once filled in.