User blocks guide¶
pSeven Enterprise provides a set of components, which are building blocks for user workflows - the default block library. All customers have the option to extend the block library with custom blocks, developed in-house or by third parties. Growing the block library is the primary way to enhance the capabilities of pSeven Enterprise: custom blocks can integrate new software tools and methods, implement new data processing functions, connect user workflows to external services or databases.
For block developers, pSeven Enterprise provides open protocols and APIs, development templates and demo blocks - working examples based on those templates. This section explains the use of these templates and examples, provides instructions, general development guidelines, protocol and API references.
Get the developer roles
To get the development templates and examples,
you need the Block developer
role,
as they are hidden by default.
You also need the Studio & AppsHub user
role
to be able to sign in to pSeven Enterprise Studio,
which is required to develop a block.
Ask admins to
configure your user account
with these roles.
Skills you need¶
This guide is designed to be as simple and straightforward as possible, however it still requires at least a basic understanding of the following:
- pSeven Enterprise workflow basics - creating workflows, adding blocks and editing their settings, running workflows.
- Programming in general; Python, to follow the guides and examples; optionally HTML and basic JavaScript, if you are going to develop blocks with UI.
- The basics of HTTP: request types (GET, POST), API endpoints.
- JSON and YAML syntax.
- Optionally - version control systems, such as Git. Using one to store your work is strongly recommended, although not required.
Templates and demo blocks¶
The templates and demo blocks are found in the block library in pSeven Enterprise Studio. Sign in and create a new empty workflow to access blocks - see the Block library pane on the left.
If you have the Block developer
role, the Common library section in
Block library displays two additional block groups: Templates and Demo blocks.
You can extract blocks from there to explore their code:
- Drag a block from Common library to the Explorer sidebar, or select a block in the pane and use the Export selected block command from the pane's menu. This creates a new folder with the icon in Explorer.
- The exported folder is a block prototype. Select it and download with the button in the Explorer toolbar.
As a result, you will obtain a ZIP archive containing the block prototype -
a folder with the .p7protoblock
extension.
This folder contains the entire block implementation - its code, assets, and so on.
Code comments in templates and demo blocks supplement
the step-by-step development guides here.
Review these comments for additional details as you progress.
Blocks in Templates aren't actually functional blocks. They contain only the template code to help you develop your own block.
- Scratch is a template suitable if you want to develop a block without UI, or you want to have more control over the block implementation and develop a fully custom UI. This template contains only the minimal boilerplate code.
- Settings is a template for simple configurable blocks with a basic UI. In addition to the boilerplate code, this template contains a sample UI implemented in HTMX with minimal JavaScript.
Demo blocks are working examples, developed from the above templates. These are functional blocks, which can actually run in workflows - however, they are not production blocks. Their purpose is to supplement the development guides in this section.
- Log provides an example of working with input ports and run logs. This block accepts any value to its input and prints this value to the workflow run log. It has no settings and no UI.
- Random provides an example of working with output ports. This block generates and outputs a random number. Also has no settings and no UI.
- Add is a minimum example of data processing, and also an example of handling port data types. It accepts two values to its inputs, sums them, and outputs the result; requires the inputs to be scalar numbers. Also has no settings and no UI.
- Calc provides an example of working with block configuration. It has a UI where the user can add named operands and select one of the two operations: addition or multiplication. For each operand, Calc adds an input port with the same name, used to set the operand value. During a workflow run, this block gets the operand values from its input ports, performs the operation selected by the user, and outputs the result.
-
Windows cmd is an example focused on handling input and output files in blocks that run on Windows extension nodes; it requires a Windows node to run. This block has two input ports for the names of the input and output files, and another input port for the command to run, which may use the
${IN}
and${OUT}
substitutions for filenames. During a workflow run, the input file is copied to the Windows node where the block runs the command, producing the output file; the output is then downloaded from the Windows node.Note the command is handled in a very naive way, which you must not copy to any production block. The sole purpose of this demo is to highlight the key points of file handling on Windows nodes, while the command is only used as a way to simulate the file processing on the node.
Never use demo blocks as templates
Demo blocks are simplified examples. Each of them only serves to explain a certain aspect of the block development, intentionally neglecting the rest. This is unacceptable in production blocks, so never base your development on any of the demo blocks.
Block implementation overview¶
A block is a HTTP server (backend), which uses HTTP requests to exchange data and files with other blocks through the pSeven Enterprise APIs, and can have a web UI (frontend) to display data or settings to the user.
pSeven Enterprise provides a runtime environment to blocks, controls their startup and shutdown, and handles all data exchange. From the developer's point of view, pSeven Enterprise is a programming platform, where you can run virtually any code as a platform plug-in (block), provided that your plug-in implements the block/platform protocol. That one is a HTTP/JSON protocol specifying the control requests from the platform, which your block must handle, and the requests your block can send to the platform to get data, save state, and so on. In essence, all guidelines and examples in this section describe and explain the block/platform protocol and related APIs.
The backend HTTP server is required for every block, as it is the part that
implements the block/platform protocol. The UI frontend is optional.
The UI can communicate with the backend using HTTP; the platform does not pose
specific requirements for this communication, as it is not a part of the
block/platform protocol. pSeven Enterprise provides the basic services for
block UI: value editor, file dialog. These services are available through
a small JavaScript API (@api.js
).
There are two modes of block execution:
- edit mode is when the user is setting up a workflow and opens the block to edit its settings.
- run mode is when pSeven Enterprise launches the block during a workflow run, set up and started by the user.
In edit mode, a block displays its settings UI to the user, handles user's commands from the UI, updates its configuration with user's edits. This mode is optional, as you can develop blocks without UI. Such blocks are entirely configured through their input ports, using the Block properties pane, so they don't need to run in edit mode.
In run mode, a block gets its inputs and processes them to produce results. Blocks in run mode do not connect to each other directly: pSeven Enterprise manages all file and data exchange between them. Each block communicates with pSeven Enterprise via the block/platform protocol to get its input data and to make its results available to other blocks, when they request those results from the platform.
The block registers itself to the block library via the block manifest file (manifest.yaml
).
This is a required file with a specific YAML structure,
which identifies and fully describes the block:
what are its name and version,
which of the run modes it supports,
how the platform should launch it,
what are its input and output ports,
runtime requirements, API endpoints, and so on.
This sums up the requirements imposed by pSeven Enterprise:
- Your block must implement the block/platform protocol (HTTP/JSON) and handle all requests from the platform.
- For data input and output, file transfers and other functions handled by the platform, your block must use the platform APIs - HTTP at the backend, JavaScript for the UI.
- The implementation must contain the
manifest.yaml
file that describes the block in the format required by the platform.
Development templates already adhere to these requirements. If you develop a block in Python, you only need to implement the block's business logic (data processing) and describe its inputs and outputs in the block manifest - as you will see in the guides. Templates use a common code and file structure as they are all parts of the same framework. You are free to adjust this structure to your needs - as long as you correctly describe it in the manifest.
Python is not a requirement; you are free to implement blocks in any programming language. However you'll have to reproduce the Python templates in your language and, if it requires a certain runtime environment, add your custom runtime environment to pSeven Enterprise.
Development walkthrough¶
This section is a block development walkthrough - a series of step-by-step guides to developing a new block from a template, starting from the very basics and gradually adding features.
Before you begin with the walkthrough, see the overview of the block development cycle below. This is a brief general description, details are found in the guides that follow.
- Get a template: sign in to Studio, export a block prototype from Common library - Templates, then download it to your development workstation.
-
Make the initial required edits to the template: in
manifest.yaml
, set the block's ID, name, version, and group; rename the template folder to{name}-{ID}-{version}.p7protoblock
; add a block icon. This is now your block prototype.Never change the block ID afterwards. If you do, pSeven Enterprise will recognize it as another new block, and the block update feature in user workflows won't work as intended.
-
If you use a version control system, the block prototype with the above edits is a reasonable initial commit.
- Upload your block prototype back to Studio.
- In Studio, create a new test workflow. Import your block prototype: drag it from Explorer to the Workflow library pane. Then, create a block instance: drag the block from Workflow library to the workflow. This is the instance you'll use for testing.
-
Using WebDAV access, set up one-way file synchronization from the block prototype folder on your workstation to the block prototype folder in your test workflow.
WebDAV setup
In Studio, in the Explorer menu, select Copy URL HTTP/WebDAV. Connect to this URL with your Studio username and password, using any WebDAV client such as WinSCP (just paste the session URL) or rclone (set up a WebDAV remote).
-
Make changes in the block code on your workstation, test them live using the block instance in your workflow, commit to the block code repository. Do not change the version number until you publish (release) your block.
Don't store the working copy of your block code in pSeven Enterprise - it is your testing environment only. Use a version control system, such as Git, to manage your code.
-
Once your block is ready for use, remove the
DEV
tag inmanifest.yaml
and ask an admin to publish your block - add it to the common library, making it available to all users. This is effectively a version release of your block.Don't remove the
DEV
tag in the development version of the block. For example, if you use main and release branches, remove the tag in a release branch but don't merge this change to master. -
Increment the version number in
manifest.yaml
and also update this number in the name of the block prototype folder ({name}-{ID}-{version+1}.p7protoblock
). Commit these changes immediately after publishing the block.You shouldn't push any updates or fixes to an already released version of the block - such as attempting to overwrite it in the library while keeping the same version number. If you do, pSeven Enterprise won't recognize that the block prototype has changed. This will further create confusion because users won't be prompted to update the instances of your block in their existing workflows, and users will have no means of tracking the versions of your block.
Further sections describe the development process in more detail. Follow them in order - the initial steps are essential for understanding the advanced examples presented later. More in-depth details are available in the Development guidelines section, which is your next stop after completing the walkthrough.
Basics, logging, and testing¶
The example in this section is going to be a simple block similar to the Log demo block. It provides a single input port, which accepts any value and prints it to the workflow run log.
- If you use a version control system, initialize a new repository for the block code.
- Get the Scratch template:
export and download it to your workstation,
extract the
Scratch.p7protoblock
folder from the downloaded archive. Move the extracted folder to your code repository. -
Review the template file structure.
Scratch.p7protoblock/ ├── backend/ │ ├── __init__.py │ ├── __main__.py │ ├── env.py │ └── run.py ├── icon.svg ├── manifest.yaml ├── start.bat └── start.sh
manifest.yaml
- the block manifest file. This name is required, and this file must be located at the root of the block prototype folder (.p7protoblock
).icon.svg
- the block icon.start.bat
,start.sh
- the block startup scripts for Windows and Linux.backend/
- a Python package containing the boilerplate backend code; this template does not provide any frontend (UI) code.env.py
- reads environment variables to get the parameters required to run the block.run.py
- the main block file for run mode; this template omits edit mode.
There is no required file structure for your code, except the location and name of the block manifest. pSeven Enterprise reads the manifest to locate other block files. You are free change the default structure, as long as you update the manifest to reflect your changes.
-
Generate and copy a new block ID - for example, using Python:
import uuid print(uuid.uuid4().hex)
Each block in the library must have its own ID, otherwise it will conflict with other blocks. Block ID is a random UUID given without dashes - that is, a string of 32 hexadecimal characters.
-
Edit
manifest.yaml
to declare a new block.- Change
id
to the ID you've generated. - Set
name
- for example,name: "Test log"
. -
Set
version
to1
.In templates,
version
is used to track the template version number, so you should reset it for a new block. -
Change the group name in
groups
- for example,groups: ["Test blocks"]
.
Initial example manifest
Do not copy this example to your manifest as is. See the comment to
run.require
.manifest.yaml# Sample block from the development tutorial. manifest_type: "protoblock" protocol_version: 2 id: "777a7e57b10cc0000000000000000001" name: "Test log" version: 1 icon: "icon.svg" groups: ["Test blocks"] # Important. Blocks under development must be handled differently # from regular blocks. This tag enables pSeven Enterprise to identify # a block as being under development. tags: ["DEV"] # This block has no edit mode. # Run mode settings. run: # Do not copy! # The value here is a placeholder. The real value is the default # runtime environment ID (runenv ID), which depends on your version # of pSeven Enterprise. The correct runenv ID for your version is # specified in the template block manifests. require: "@python3_10:YYYY.MM.DD-tttttttttt.xxxxxxxx" code: ["backend", "start.sh", "start.bat"] platforms: linux/amd64: exec: ["./start.sh"] windows/amd64: exec: ["start.bat"]
- Change
-
Add a block icon: replace
icon.svg
in theScratch.p7protoblock
folder with your custom icon. If you want a different filename for the icon, also editicon
inmanifest.yaml
- for example,icon: "log-test.png"
. (1)- Vector (SVG) icons are recommended over raster ones. If you use a raster icon, make it 64×64 pixels at least.
-
Rename the
Scratch.p7protoblock
folder. Your custom name must keep the.p7protoblock
extension - otherwise you won't be able to import it into the block library because pSeven Enterprise won't recognize this folder as a block prototype.It is recommended that you follow this naming convention:
{name}-{ID}-{version}.p7protoblock
, where{name}
and{ID}
are thename
andid
parameters from the block manifest (for example:Test_log-777a7e57b10cc0000000000000000001-1.p7protoblock
).Naming convention for block prototype folders
In the pSeven Enterprise file storage, there is a single folder that stores all block prototypes and their version history - so each version of each block has to be a separate folder. The common way is to include the block and version identifiers in the folder name. You can also use a custom naming scheme, if it does not cause conflicts in the library.
-
If you use a version control system, make the initial commit to your repository.
-
Import your block to a test workflow - use the one you've created in the previous steps, or create new.
- Drag the block prototype folder from your file manager to the Explorer pane in Studio. A block prototype folder with the icon will appear in Explorer.
- Open your test workflow. Drag the prototype folder from Explorer to the Workflow library pane.
You can now sync the code from your workstation to the block prototype in the workflow library, and add the block to the workflow for testing. Set up the sync first.
-
Any block you see in the Workflow library pane is actually a block prototype stored inside the workflow, in its hidden
@Blockcode
subfolder. Using WebDAV, you can set up file synchronization from the block prototype folder on your workstation (your working copy) directly to the prototype folder in the workflow - so your changes in the local copy automatically apply to the test block.- Open your test workflow in Explorer: double-click its tab title in Studio to navigate to the workflow folder, or open the tab bar menu on the right and use the Reveal in Explorer command.
-
To show hidden folders, click anywhere in Explorer to get focus, then press Ctrl+Alt+H. The Files tab in the workflow will now display hidden files and folders, and the file tree will resemble the following:
Test.p7wf/ ├── @Blockcode/ │ └── 777a7e57b10cc0000000000000000001-1/ │ ├── backend/ │ ├── ... │ └── start.sh ├── @Blockdata/ ├── @Runs/ ├── ... ...
-
Note the
@Blockcode/{id}-1/
folder - this is the copy of your block prototype in the workflow. Connect to pSeven Enterprise with a WebDAV client and sync your working copy to that folder.WinSCP is commonly used as an (S)FTP client for Windows, and it also supports WebDAV.
- Copy the WebDAV URL from pSeven Enterprise: open the menu in Explorer and select Copy URL HTTP/WebDAV.
- Run WinSCP and open the Login dialog - in its Tabs menu, select New tab Remote tab. If this is your first run of WinSCP, it opens the Login dialog automatically.
- In the Login dialog, select New site in the list to the left, then paste the WebDAV URL you've copied into the Host name field. Click any other field to refresh the dialog; WinSCP will parse the connection settings from the URL and display them.
- In the Login dialog, click Save, save the current settings as a new site, then click the login button at the bottom of the Login dialog. Enter your username and password once prompted - the ones you use to sign in to pSeven Enterprise normally.
- In the left pane, open your local folder with the block code
(
.p7protoblock
). In the right pane, open the remote folder noted above. The full path in the right pane should look like:/pseven/@files/Users/7/Test.p7wf/@Blockcode/777a7e57b10cc0000000000000000001-1/
. - From the Commands menu, select Keep Remote Directory up to Date... In the dialog that opens, leave all settings at their defaults and click Start at the bottom. Wait until the initial sync completes.
With this setup, WinSCP will automatically sync your changes to the remote copy when you save changes to files in the local copy. There might be a few seconds delay, depending on your network connection quality.
rclone is a cross-platform command-line program to manage files on cloud storage.
- Copy the WebDAV URL from pSeven Enterprise: open the menu in Explorer and select Copy URL HTTP/WebDAV.
-
Set up a WebDAV remote: run
rclone config
and answer its prompts. Use the following answers:- URL of http host to connect to: the WebDAV URL you've copied.
- Name of the WebDAV site you are using: "other".
-
Check the resulting config in
rclone.conf
:[pSevenWebDAV] type = webdav url = https://{pSeven Enterprise address:port} vendor = other user = {your pSeven Enterprise username} pass = {hashed password}
-
To sync files, run:
rclone sync {your .p7protoblock folder path} pSevenWebDAV:/pseven/@files/Users/{id}/{path}
where
{id}
is your user ID, and{path}
points to the copy of your protoblock in the test workflow.
There is no auto-sync feature in rclone. If you wish, you can set up OS triggers to run the
rclone sync
command, however it is usually more convenient to create an alias for it and just run it manually as needed.
-
After the initial sync completes, drag the block from the from Workflow library to the workflow. This creates a block instance in the workflow; you'll use such instances for testing.
-
Add an input port to your block. Edit
manifest.yaml
, adding the following lines:manifest.yaml - input portsinputs: "message": description: "The message to print to the workflow run log."
This declares an input port named
message
and adds a description, which will be displayed in the port's tooltip. Note the quotes around the port name.Example manifest with an input port
Do not copy this example to your manifest as is. See the comment to
run.require
.manifest.yaml# Sample block from the development tutorial. manifest_type: "protoblock" protocol_version: 2 id: "777a7e57b10cc0000000000000000001" name: "Test log" version: 1 icon: "icon.svg" groups: ["Test blocks"] tags: ["DEV"] # Block's single port - input, the value to log. inputs: "message": description: "The message to print to the workflow run log." run: # Do not copy! # The value here is a placeholder. The real value is the default # runtime environment ID (runenv ID), which depends on your version # of pSeven Enterprise. The correct runenv ID for your version is # specified in the template block manifests. require: "@python3_10:YYYY.MM.DD-tttttttttt.xxxxxxxx" code: ["backend", "start.sh", "start.bat"] platforms: linux/amd64: exec: ["./start.sh"] windows/amd64: exec: ["start.bat"]
-
Sync your changes, go back to the test workflow, and select the block instance created earlier. You can see that the instance still has no input ports. This is because changes in the block manifest do not propagate to block instances automatically: you'll have to re-add the block from Workflow library.
You only need to re-instantiate (re-add) the block when you make changes in its manifest. Changes in other parts of the code can be tested right away in an existing block instance.
-
Delete the existing block instance from the workflow, then again drag the block from Workflow library to the workflow. Check that the new instance has the
message
input port - select it and take a look at the Block properties pane on the right, section Inputs. -
Your block can now run and accept an input, but it still does nothing with that input. You need to implement a request handler - a function, which the block would call when it receives a run command from the platform. That command is a HTTP request sent by the platform to the block's backend HTTP server. The path (API endpoint) of this request is
/@next
, and the input data (input port values) arrives in the body of this request.The template uses FastAPI to add API endpoints to the block's backend server - this part of the template code is very similar to the basic FastAPI example. Review it: in your working copy, open
backend/run.py
and find the section titled "HTTP-HANDLERS".run.py# -----------------------------------------------------------------HTTP-HANDLERS # FastAPI instance representing the block backend. blk = fastapi.FastAPI() # ... @blk.post("/@next") async def next(body: dict): """pSeven signals to start the calculation.""" # HTTP server must respond immediately, so the calculation starts # in a worker thread. blk.state.mem.worker = threading.Thread( target=_calc, args=( body["inputs"], body["outputs"], ), ) blk.state.mem.worker.start()
You don't need to edit that part now - just note that it creates a
FastAPI
instance (blk
) and adds a POST request handler at/@next
(@blk.post("/@next")
), which calls the_calc()
function (target=_calc
) with the request body data as arguments.Worker thread
The block has to respond immediately, to inform the platform that it has received the request and is now processing it. For this reason, the actual processing must be performed in a separate worker thread - otherwise it'll delay the response.
Next, find the
_calc()
function inrun.py
and note the placeholder comment in thetry:
block.run.pydef _calc( inputs: Dict[str, Dict[str, str]], outputs: Dict[str, Dict[str, str]], ): """Runtime business logic of the block. NOTE: Function runs in a worker thread. """ try: # Implement your block logic here. response = {} except Exception as ex: # ...
Replace the placeholder with the following lines:
print("INF Message: %s" % str(inputs["message"]["value"]["data"])) print("DBG Raw inputs: %s" % str(inputs))
Here,
INF
andDBG
are log level prefixes: they inform the platform that the output should be sent to the info and debug log levels. The prefix itself will not be displayed in the output, it is removed by the platform.Log level prefixes
The log level prefixes are:
ERR
for error,WRN
for warning,INF
for info, andDBG
for debug. Any string from the block'sstdout
with one of these prefixes (likeprint("INF string")
) is sent to the appropriate log level by the platform. Also, strings without a prefix are sent to debugб so you can actually omit theDBG
prefix. This logging mini-protocol works with any language, not just Python.In Python, you can also use the
logging
module (LOG
in the template is alogging.Logger
instance):LOG.info("Message: %s", str(inputs["message"]["value"]["data"])) LOG.debug("Raw inputs: %s", str(inputs))
With the above edit, when your block receives a HTTP request to
/@next
, it will call_calc()
with the request body data as arguments. That data contains the value received to themessage
input port - which_calc()
outputs to the workflow run log.At this step, you don't need to re-add the block to the test workflow: this is only required after making changes in the block manifest.
-
To test the block, set some value to its
message
input port: select the block instance in the test workflow, then enter the port value in the Block properties pane on the right.Note that the inline value editor (the input field for the port value) currently expects a number and doesn't accept strings. To enter a string value, you'll have to open the value editor dialog (click in the input field) and switch the value type from Real to String there (at the top left in the dialog). The fix to this inconvenience is explained in the next section.
-
Run the workflow and check the messages in the Run log pane: your block should print the value you had entered earlier. If you select Show debug from the pane's menu, you can also find the debug level message with the full
inputs
data structure.
Data types, output ports¶
This section provides an introduction to data types, using the Test log block from earlier as an example. It also explains how to develop a block with output ports, using a simple example similar to the Random demo block (outputs a random number). Finally these two blocks are tested together in a workflow, where the generated random number is sent to the Test log block to print it to the workflow run log.
-
Earlier to set a string value to the
message
input of the Test log block, you had to open the editor dialog and change the value data type. Test log currently doesn't specify the expected data type for that port, so pSeven Enterprise uses default value editing behavior: allows you to select any data type and input any value, while assuming that Real type is the default.To change that behavior for the
message
port, you need to specify the its expected data type in the Test log block manifest. Edit the port declaration inmanifest.yaml
as follows:manifest.yaml - input portsinputs: "message": description: "A message which will be printed to log" schemas: [{ $ref: "s" }]
The line
schemas: [{ $ref: "s" }]
specifies that the expected data type is string -{ $ref: "s" }
is the JSON Schema notation that pSeven Enterprise uses for strings. Addingschemas
to a port changes the default behavior of data editors for that port in the Studio UI: now when you set a value formessage
, string type will be the default and the only type suggested by editors.Ports are typeless
Adding
schemas
to a port declaration doesn't mean that you set a fixed data type for that port. During a workflow run, the port will accept any value, regardless of its type.Always check and validate input values before you process them (in
_calc()
, for example). This is required in your block implementation; a few relevant examples are provided further in the guides.The
schemas
property may be omitted, if you don't want to use it. If you do, many usecases are covered by just the base types or combinations of those - such asschemas: [{ $ref: "s" }, { $ref: "r" }]
to use the string editor by default but also allow switching to Real type. If you ever need to fine-tune data types, see the Typesystem section for a guide. -
Sync your changes to pSeven Enterprise, then delete the Test log block instance from your test workflow and re-add this block from the Workflow library pane. This is required because you've made changes to the block manifest.
- Try and set a value to the
message
port: select the block instance in your test workflow, edit the port value in the Block properties pane on the right. Now everything you enter is interpreted as a string; if you open the value editor dialog, it also allows String type only - see the type selector at the top left.
For further steps, you'll need another new block with a new block ID.
Get another copy of the Scratch template and edit manifest.yaml
:
- Change
id
to a newly generated ID. This ID must be different from the one you've used for the Test log block - if you use the same id, your new block will overwrite the Test log block. - Set
name
- for example,name: "Test random"
. - Set
version
to1
. - Set
groups
- for example,groups: ["Test blocks"]
.
Continue editing this new block, Test random.
-
Add an output port in
manifest.yaml
.manifest.yaml - output portsoutputs: "N": description: "Random number in [0..1) number"
Example manifest with an output port
Do not copy this example to your manifest as is. See the comment to
run.require
.manifest.yaml# Sample block from the development tutorial. manifest_type: "protoblock" protocol_version: 2 id: "777a7e57b10cc0000000000000000002" name: "Test random" version: 1 icon: "icon.svg" groups: ["Test blocks"] tags: ["DEV"] # Block's single port - output, a random number. outputs: "N": description: "Random number in [0..1) number" run: # Do not copy! # The value here is a placeholder. The real value is the default # runtime environment ID (runenv ID), which depends on your version # of pSeven Enterprise. The correct runenv ID for your version is # specified in the template block manifests. require: "@python3_10:YYYY.MM.DD-tttttttttt.xxxxxxxx" code: ["backend", "start.sh", "start.bat"] platforms: linux/amd64: exec: ["./start.sh"] windows/amd64: exec: ["start.bat"]
-
Add the code to generate output values. Edit
run.py
- find_calc()
and replace the placeholder comment in thetry:
block.run.pydef _calc( inputs: Dict[str, Dict[str, str]], outputs: Dict[str, Dict[str, str]], ): """Runtime business logic of the block. NOTE: Function runs in a worker thread. """ try: import random n = random.random() outputs["N"] = {"value": {"data": n}} response = {"outputs": outputs} except Exception as ex: LOG.exception( "Error: inputs=%s, outputs=%s, mem=%s", inputs, outputs, blk.state.mem ) tb = "".join(traceback.TracebackException.from_exception(ex).format()) response = {"error": {"message": str(ex), "traceback": tb}} finally: _pseven_post("done", response) blk.state.mem.worker = None
The block will now prepare response data containing a single random number, then send it via a POST request to the
done
endpoint of the platform. This is the endpoint that the block, once it has done calculation, must use to report its results.Use
_pseven_post()
for all requests you send from the block's backend to the platform. This is a helper function provided by every block template. -
There should be two blocks in your test workflow at the moment: the Test random instance you've just edited, and the Test log instance created earlier. Create a link between them: connect the
N
output of the Test random block to themessage
input of the Test log block. - Run your test workflow and check its run log: Test log prints the random number received from Test random through the link.
Development guidelines¶
This section includes various details and guidelines, which were omitted from the development walkthrough.
Block version numbering¶
The pSeven Enterprise block library allows you to load different versions of a
custom block with the same ID. To distinguish between block versions, the
version
parameter in the manifest.yaml
file is used.
- When starting to develop a new block (with a new block ID), set
version
to1
. - When starting to develop a new version of an existing block (keeping the
block ID), increment the value of the
version
parameter. - When testing and debugging a block during development, do not change its version number.
Tagging the block under development¶
When developing a new version of a custom block, always add the DEV tag,
indicating that the block is under development.
This tag is added through the tags
parameter in the manifest.yaml
file:
tags: [{"": "DEV"}]
The block development templates and demo blocks have the DEV tag added by default.
The main purpose of the DEV tag:
- Hide the block from non-developers.
Only the users who have the
Block developer
role will have access to this block. For others, the Block library will not display this block at all. - Mark the block in the Block library pane. The DEV tag shows up next to the name of the block, indicating that this block is under development and is not ready for use.
- Disable the code cache for blocks intended to run on Windows extension nodes. When pSeven Enterprise first runs a block on an extension node, it saves a copy of the block code in the node's local cache to avoid copying it to the node every time that block runs. Your modified block of the same version without the DEV tag would conflict with its earlier state stored in the cache. Disabling the cache allows the modified block to run with the same version number.
Disabling the cache may cause significant time delays when configuring the block or running it within a workflow. When you are done testing and debugging the new version of the block, remove the DEV tag before you proceed to publish the updated block. For example, if you publish the block without any custom tags, the list of tags in the block's manifest should be empty:
tags: []
If you publish the block with custom tags, the list should include only your tags, without the DEV tag.
Adding Python modules¶
If your block uses any Python modules not provided by pSeven Enterprise, these modules need to be added to the block prototype. The module distribution and all its dependencies must contain only Python code. Such distributions are commonly referred to as pure Python module distributions.
To add modules from a distribution to your block prototype, use the
following command: pip install --target <directory> <distribution>
where <directory>
is the path to your block prototype folder
(.p7protoblock
), and <distribution>
is the name of the distribution.
Example:
pip install --target CustomBlock.p7protoblock extra_lib
This example adds the modules from the extra_lib
distribution to the
block prototype held in the CustomBlock.p7protoblock
folder that is
located in the current working directory.
Installing a module also installs all its dependencies to the block prototype. Once you have installed a module, you can import it in your block code as usual:
import extra_lib
File dialog¶
If configuring your block requires the user to select a file - for example, a template or model, which the block loads in edit mode - use the pSeven Enterprise stock file dialog.

The dialog supports the usage scenario where the user selects a file and additionally specifies a folder with its dependencies - for example, the main file of some assembly and the folder containing the parts. Further it is called context folder (see {CONTEXT DIR} in the figure).
-
In your block UI implementation, add
@api.js
- seeExample user block:
index.html<!DOCTYPE html> <html lang="en"> ... <body> ... <!-- Block's interactive UI. --> <script src="index.js"></script> <!-- pSeven Enterprise Javascript API. --> <script src="@api.js"></script> </body> </html>
@api.js
adds the following functions to the global scope:xSeven.filedialog.selectFile(config)
- select a single file.xSeven.filedialog.selectFiles(config)
- select multiple files.xSeven.filedialog.selectFolder(config)
- select a folder.
Use the
config
parameter to set up the dialog.config{ // Your dialog title - see {TITLE} in the figure at the beginning of this section. "title": "Select a model or assembly", // The commit button label - see {COMMIT}. "okLabel": "Select", // The cancel button label - see {CANCEL}. "cancelLabel": "Cancel", // Specific name of the context folder for the current dialog. // If specified, the context folder selector will appear in the dialog - see {CONTEXT DIR}. // Only selectFile() supports this. "context": "Assembly folder", // If you do not need a context folder, omit this property or set null. // Also omit it for selectFiles() and selectFolder() - those methods // do not support context folder. // "context": null, // Files of what type the user can select in the current dialog. // A list of [{"typename": ["name mask 1", "name mask 2"]}, ...]. // See {FILE TYPE} in the figure above. "filters": [ {"Model, assembly": ["*.prt", "*.as"]}, {"All files": ["*"]} ] }
-
Call one of the file selection functions - the block will display a file dialog to its user. The function returns a Promise, and when the user confirms the file selection in the dialog, that Promise resolves with an object or array of objects that you use to access the files.
// selectFile() without a context folder ("context": null), as well as // selectFolder(), return one object containing the name of the selected file (folder). // selectFiles() also returns one object, if the user has selected a single file. { "filename": "main_file.as", ".payload": "eyJuYW1lIjoiZGQtbmluYWx1YS1jdXN0b21lbWJsZW1zLmdpZiIsInBhdGgiOiIv" } // If the user has selected multiple files, selectFiles() returns an array. [ { "filename": "bell.prt", ".payload": "eyJuYW1lIjoic2VwdWxpcmYW5poIjoiL1VzZXJzLzcvZm9vIiwidYwNjkyMzgzNzN9" }, { "filename": "whistle.prt", ".payload": "VXNlcnMvNy9mb28iLCJ0eXBlIjoiRklMRSIsImlhdCI6MTcyNjA2OTIzODM3Mn0=" } ] // selectFile() with a context folder returns an object containing the name // of the context folder, and the path to file relative to that folder. // selectFile() with a context folder returns an object with the folder name // ("context"), and "filename" is the path to the file relative to that // folder (so if the file is in the folder root - simply the filename). { "filename": "main_file.as", "context": "Bells and whistles", ".payload": "sInBhdGgiOiIvVXNlcnMvNyIsInR5cGUiOiJGSUxFIiWF0IjoxNzI2MDY5MjY1OTQ1fQ==" }
Do not save or modify the above objects
As shown in the examples above, the object you get has the
".payload"
property, which can be understood as an access token or a transient identifier. The block must pass that token to pSeven Enterprise to gain access to the file. The token is only valid in the current editing session, that is, only until the user closes the block window. Even if you somehow save the token, in another edit session you will not be able to use the saved token to access the file.You can use the strings from
"filename"
and"context"
in the block UI - to display the names of selected files and folders to the user. The UI must not otherwise parse or modify the object (or array) you have got: pass them from the UI to the block's backend, unmodified. -
Send the object or array returned by file selection functions in a POST request to an endpoint at the backend that you implement to handle file selection requests. In
Example user block such an endpoint is called
select-file
, and the request is (simplified example):const selectedFile = await xSeven.filedialog.selectFile(config); fetch(`{block API URL}/select-file`, { method: "POST", headers: {'Content-Type': 'application/json'}, body: JSON.stringify(selectedFile), });
pSeven Enterprise does not require using exactly that endpoint name (
select-file
) - you can give it any name that is valid. -
Having received the above request from the UI, the block backend must call the
get-file
method of the pSeven Enterprise API and pass it the object received from the UI. The response from pSeven Enterprise will contain an object with the"file_path"
and"context_path"
properties - the paths you can now use to open the file. SeeExample user block:
server.py# A snippet from RequestHandler.do_POST(). # ... elif self.path == "/-/api/select-file": # call_pseven() - a wrapper method to send API requests, implemented in the example. # body - the body of the request from UI received at the backend. r = self.server.call_pseven("get-file", body.get("data")) # Absolute path to the file. file_path = r.get("file_path") # Absolute path to the context folder - if it was enabled in the dialog. context_path = r.get("context_path") # read_and_parse() - the method you implement to process the file; # it should prepare the response that the block will send to its UI. with open(file_path, 'r') as f: response = self.read_and_parse(f)
Do not save the received objects or paths
Objects received from the block UI and from pSeven Enterprise, as well as
file_path
andcontext_path
, are only valid for the current editing session. In another session you will not be able to access the file using an object or path saved earlier.If your UI needs any data from a file selected by the user, then it must receive that data from the block's backend - as in the example above. The backend must not return the object received from pSeven Enterprise, and must not pass any paths to the UI - only the data it reads from the file.
Block assets¶
If your block needs to read data from a file, which should not be placed into the block prototype, you can add that file to block assets in the shared data storage. Typical examples of such files are:
- A database or other file with reference data that updates more often than you update the block.
- A simulation model, which the block runs.
- A static file that does not change even if you update the block, or rarely changes.
Using block assets to store large files (models, databases) can save much disk space, but you will have to keep in mind that pSeven Enterprise does not handle any dependencies between your block and assets it may use.
When a user first adds your block to a workflow, that block is copied to the workflow library. This creates an independent copy of the protoblock folder in the workflow. All block instances in the workflow work with that copy of the protoblock folder and do not synchronize it with the protoblock in the shared data storage. Synchronization happens only if the user accepts a block update in workflow library. Keeping a copy of the protoblock in the workflow helps pSeven Enterprise to avoid many issues related to block version upgrades.
Block assets are not handled by pSeven Enterprise in a similar way. Assets are never copied automatically, and you have to track dependencies between block versions and assets yourself. If your block depends on a file from assets, all block instances in all user workflows read that same file. Moreover, in a given pSeven Enterprise deployment all block assets are common to all user blocks in that deployment - so other block developers may be using the same asset your block uses.
Before you start using block assets, consider the following:
- Assets are read-only - your block cannot write to a file in assets, because at the same time that file may be in use by another block. In general, no block has write access to asset files.
- Block developers and administrators of a pSeven Enterprise deployment, where block assets are used in development, have to be extra careful when publishing, updating and otherwise maintaining blocks because all dependencies between blocks and assets have to be tracked manually. Note that dependency problems are easily avoided by adding all required files to the block prototype, which makes the block self-contained.
- If different versions of your block need different versions of an asset file, you will have to keep each version of that file in assets. Removing an outdated version of an asset file is risky even if you have already removed the block version that had used it.
- When you remove, rename, or otherwise modify a file in assets, you risk creating issues in user workflows. As noted above, workflows store independent and non-synchronized copies of your protoblock, and some users may decide to keep an outdated version of your block in their workflows. Those workflows will likely run into an error if you remove an asset file required by the user's version of the block. Note that even if you completely unpublish an old version of a block, user workflows can still keep a copy of that version in the workflow library and continue using it. There is no way to force a block update or removal in user workflows.
An asset is a folder, which may contain many asset files or subfolders with files. To make those files available to user blocks, the asset has to be published by uploading the asset folder to the pSeven Enterprise shared data storage. Publishing an asset requires full access to the shared data storage, so it is done by a deployment administrator (see Publishing and updating assets). After an asset has been published, any user block can declare that it requires files from that asset (see Reading files from assets). Note that there are no restrictions for using the same asset in various blocks (with different prototypes).
Publishing and updating assets¶
Block assets can be published and updated only by a pSeven Enterprise
deployment administrator who has full access to the shared data storage. To
publish an asset, the administrator needs to copy the asset folder to the
assets
folder in the shared data storage and set permissions so pSeven
Enterprise has read and write access but user blocks have read-only access to
files in the copied folder. The steps to update a previously published asset
are the same - new versions of asset files are simply copied over existing
ones - but keep in mind that pSeven Enterprise does not track dependencies
between user blocks and assets. Before you update an asset, verify that the
update does not create issues in any of the blocks requiring files from that
asset.
How to locate the shared data storage
The shared data storage location is specified by the
storage.shareddata.*
parameters in the deployment configuration
file values.yaml
(see the
pSeven Enterprise deployment
guide).
Follow the steps below to publish or update a block asset:
- Connect to the shared data storage server as a user with the root permissions.
-
Upload the asset: copy the asset folder (for example,
common_data
) to the<shareddata>/assets
folder on the server, where<shareddata>
stands for the location of the shared data storage. In this example, the path to the uploaded asset folder would be<shareddata>/assets/common_data
.The name of the asset folder should contain only lowercase English letters, digits, and underscores (the
[a-z0-9_]
character set); other characters are forbidden in asset names. pSeven Enterprise does not check the folder name when you publish an asset, but forbidden characters in the name will cause an error in any user block that declares a dependency on that asset.You can use symlinks if you want to avoid renaming assets
If the asset folder name contains forbidden characters but you want to avoid changing it, you can create a symbolic link to the uploaded asset folder, giving the link a proper name. For example, the folder may be named
Common-Data
, which is an invalid name for an asset (uppercase letters and-
are forbidden). Instead of renaming the folder, you can upload it as is and then create a symbolic link namedcommon_data
:# ln -s <shareddata>/assets/Common-Data <shareddata>/assets/common_data
After this, user blocks can specify the asset name
common_data
to access files in the<shareddata>/assets/Common-Data
folder. -
pSeven Enterprise connects to the storage as a system user with UID 11111. That system user must be the owner of asset files and have read-write permissions in assets. A block connects to the storage as the user who configures or runs that block, so all users except system must have read-only access. For example, if you are publishing an asset named
common_data
, you can set the required permissions as follows:-
Set the pSeven Enterprise system user as the owner:
# chown -R 11111:11111 <shareddata>/assets/common_data/
-
All asset subdirectories must have
drwxr-xr-x
permissions:# find <shareddata>/assets/common_data/ -type d -exec chmod 755 {} \;
-
Asset files must be read-only to everyone except the owner and must keep the executable flag (
x
):# find <shareddata>/assets/common_data/ -type f -exec chmod "u+rw,go+r,go-w" {} \;
-
Reading files from assets¶
After an asset has been published to pSeven Enterprise, your block can read
asset files. To get access to files, the block must declare its asset
requirements, which are specified by the following parameters in the block's
manifest (manifest.yaml
):
edit.assets
- a list containing names of assets your block requires in the edit mode.run.assets
- a list containing names of assets your block requires in the run mode.
For example:
edit:
assets: [common_data, reference_docs]
run:
assets: [common_data, database]
The above example assumes there are published assets named common_data
,
database
, and reference_docs
in your pSeven Enterprise deployment. When
your block starts, pSeven Enterprise reads its manifest and, for each name
<asset_name>
in edit.assets
and run.assets
, populates the block
environment with a variable named DA__P7__BLOCK__ASSET__<ASSET_NAME>
, where
<ASSET_NAME>
is the uppercased name of an asset. The block should read that
variable to get the <asset_name>
folder path, which it can use.
Continuing the example above, the following environment variables would be added:
DA__P7__BLOCK__ASSET__COMMON_DATA
containing the path to thecommon_data
asset folder,DA__P7__BLOCK__ASSET__DATABASE
containing the path to thedatabase
asset folder, andDA__P7__BLOCK__ASSET__REFERENCE_DOCS
containing the path to thereference_docs
asset folder.
In the block code, you can use values of those environment variables to obtain
paths to asset files. For example, to get the path to the material.db
file
from the database
asset:
import os
# path to the asset folder containing database files
db_folder_path = os.environ['DA__P7__BLOCK__ASSET__DATABASE']
# path to the material.db file in that asset
db_file_path = os.path.join(db_folder_path, 'material.db')
Using assets to add Python modules¶
As noted in section Adding Python modules, the
preferred way to add custom Python modules to your block is to install the
module distribution to the block prototype using pip install
. By installing
modules to the block prototype, you avoid creating external code dependencies,
which helps maintaining and updating the block. However if you need a module
that cannot be installed with pip
- for example, a module from a privately
developed distribution - you can publish an asset containing that distribution
and import modules from there.
For example, assume that your block requires modules from several private
packages named private_sdk
, private_lib
, and so on. You can make them
available to the block as follows:
- Prepare an asset folder containing all required distributions. The folder
name should contain only lowercase English letters, digits, and
underscores - for example,
private_python
. In the example, the folder structure would be like the following:private_python/
- the asset folderprivate_sdk/
- a package folderprivate_lib/
- another package folder- ...other packages
- Publish the
private_python
asset as described in Publishing and updating assets. The block environment variable corresponding to yourprivate_python
asset will be namedDA__P7__BLOCK__ASSET__PRIVATE_PYTHON
. Its value will be the path to the published asset folder containing your private Python distributions. -
In your block startup scripts, add the path stored in the
DA__P7__BLOCK__ASSET__PRIVATE_PYTHON
environment variable to the Python module search path stored in thePYTHONPATH
block environment variable:- Locate the block startup scripts. The paths to those scripts are
specified by the
edit.exec
andrun.exec
parameters in the block's manifest (manifest.yaml
). In the user block example provided by pSeven Enterprise, there are two such scripts:scripts/start.sh
(for Linux) andscripts/start.bat
(for Windows). -
In startup scripts, find the commands that set the
PYTHONPATH
variable and append the value ofDA__P7__BLOCK__ASSET__PRIVATE_PYTHON
toPYTHONPATH
. For example:# Set PYTHONPATH so that Python finds the block code. export PYTHONPATH="$DA__P7__BLOCK__CODE_DIR:$DA__P7__BLOCK__ASSET__PRIVATE_PYTHON"
:: Set PYTHONPATH so that Python finds the block code. set PYTHONPATH="%DA__P7__BLOCK__CODE_DIR%;%DA__P7__BLOCK__ASSET__PRIVATE_PYTHON%"
- Locate the block startup scripts. The paths to those scripts are
specified by the
-
You can now import modules from the
private_python
asset as usual:import private_lib from private_sdk import utils
The block will search for modules and packages in the directories listed by the
PYTHONPATH
variable and find theprivate_lib
andprivate_sdk
packages in the publishedprivate_python
asset.
Testing and debugging a custom block¶
Typically, some debug workflow is used to test a block during its development. You can load a new block prototype without changing the block ID and version in the manifest. This will replace the block code in the library and in the workflow with the block code from the prototype folder. This feature allows you to quickly test changes in the block code.
Important
When testing and debugging a block on a Windows extension node, you must add the DEV tag to the block's manifest; otherwise, your changes in the block code might conflict with the earlier state of the block stored in the node's local cache. For details on the DEV tag, refer to section Tagging the block under development.
If you load a new block prototype with the same ID and a new version number, the new block appears in the workflow library along with the old one. This new block version does not automatically replace the old one in the workflow. To replace the block in the workflow, you need to click the update notification next to the old block in the Workflow library pane. This allows you to test the steps that the users of the block will need to follow after a new block version is published to the common library.
Observe the following basic rules when testing and debugging a custom block:
- Change the block ID only if you intend to design a new block rather than a new version of an existing block. For a new version of a block, leave the existing block ID unchanged.
- While testing a new version of a block, keep the same version number in the
block's manifest: do not change the
version
parameter in themanifest.yaml
file before you load the block into the workflow library. When loaded with the same ID and version number, the updated block automatically replaces the existing one in the library and in the workflow. - Use the DEV tag when testing the new version of the block on a Windows extension node (see Tagging the block under development).
- Do not manually delete the old block from the workflow library, as this will remove the block from the workflow. After you upgrade the block in the library, it will be automatically upgraded in the workflow.
Publishing a custom block¶
To publish a new block to the common library, you need an administrator account. Admins can publish blocks in pSeven Enterprise Studio UI or by copying a block prototype folder to the shared data storage. The second method does not require sign-in and can be integrated with CI/CD.
To publish a block from Studio UI:
- Sign in to the pSeven Enterprise user interface as an administrator.
Note this sign-in requires the
Studio & AppsHub user
role, which is typically unassigned for the admin accounts during their initial setup (see Admin account configuration). - Upload the block prototype folder (
.p7protoblock
) - you can drag it to the Explorer pane. - Create a new workflow, open the Block library pane and expand its Common library section.
- Drag the block prototype folder from Explorer to Common library. Wait until the block setup completes, and the new block appears in Common library.
When you publish a block from Studio, it becomes available to users as soon as you see it in Common library.
To publish a block by copying it to shared data:
- Connect to the shared data storage server.
Its location is specified by the
storage.shareddata.*
parameters invalues.yaml
. - Copy the block prototype folder (
.p7protoblock
) to the{shareddata}/protoblocks
folder on the server, where{shareddata}
stands for the location of the shared data storage. -
Set up file access. pSeven Enterprise connects to the storage as a system user with UID 11111. That system user needs read access to all files in the
protoblocks
folder. There are several ways to set the required permissions, commonly one of the following:-
Set UID 11111 as the owner of the
protoblocks
folder and its contents, recursively (-R
), and set read-only mode. This method is recommended although it also requires root privileges on the storage server.chown -R 11111:11111 protoblocks/ && chmod -R 550 protoblocks/
This way, only pSeven Enterprise will have access to block files, and this access is read-only.
-
Allow read-only access to the
protoblocks
folder and its contents (-R
) for everyone:chmod -R 755 protoblocks/
This way any user, including the system user 11111, can read block files.
-
When you publish a block by copying it to the storage, it becomes usable only after you set the file access permissions correctly.
Publishing block updates¶
When a new version of the block is ready to publish, a pSeven Enterprise
deployment administrator can upgrade the block using the same methods as publishing:
either in the Studio UI, or
by copying the .p7protoblock
folder of the new version
to the protoblocks
folder in the shared data storage.
The block prototype of the new version should be prepared and packaged as follows:
- Verify the
version
parameter value in the block's manifest. When you publish a new version of your block, increment theversion
parameter value from the previous version. - Test the new version of the block for backward compatibility to ensure that it works correctly with the configurations saved by any earlier version of that block.
- Do not change the block ID. Otherwise, the new version will be considered a new block, different from the one you intend to update.
- Remove the DEV tag from the
tags
parameter in the block's manifest (see Tagging the block under development for details). - Pack the new block version into a folder with a name that contains the
new version number, for example
<block-name>-<ID>-<new-version-number>.p7protoblock
.
An administrator can upload the new block prototype folder into Studio and publish the block there,
or copy that folder to the protoblocks
folder in the shared data storage.
Either way, it is advisable to also keep the existing (previous) versions of the block.
There is no need to delete them,
as the Common library pane displays only the latest version of the block to users.
Keeping previous versions gives you the option to quickly revert an unwanted update in case of errors.
In case of a version conflict, when two folders with the same ID and version number
in the block manifest are copied to the protoblocks
folder in the storage,
the Common library pane does not display this block, thus indicating that the update has failed.
If you update a block by copying it to the storage, make sure that
the block is displayed correctly in the Common library pane when you finish.
Upgrading the block in the workflow¶
After the publication of a new block version, users can upgrade the block in their workflows. The Workflow library pane displays the new block version along with the old one, and proposes to upgrade the block in the workflow. To upgrade the block, click the update notification next to the old version of the block in the Workflow library pane.
The workflow uses the old block version until you upgrade it. The upgrade replaces the block with the new version in the workflow, and removes the old block version from the workflow library. If needed, you can undo these changes, having the workflow revert to the old block version.
You should not delete the old block version from the workflow library before the upgrade as you cannot upgrade the block if it is deleted. However, if a block was deleted by accident or by mistake, you can undo the deletion. Note that the old block version is automatically removed from the workflow library after you upgrade the block.
References¶
Typesystem¶
pSeven Enterprise typesystem is extensible.
For example, based on the built-in "real number" type,
you can define your own type "real number in the range from 1.0 to 18.0".
Such extended types are defined in the block manifest - that is,
there is no common registry of type definitions.
Consequently, there is no strict typing:
blocks exchange data as JSON objects,
and the data structures they contain (the "data"
field) can be anything.
Your block declares what data types it expects on the input ports (1) -
but this does not guarantee that it will only receive those types when running.
In run mode, your block must handle any input data structure,
converting the data to another type if required, or
stopping with a specific error message if the conversion is not possible.
- the
schemas
port property inmanifest.yaml
Example: integer input port
Let the block algorithm require an integer parameter N
,
which the block receives through an input port.
There is no way to set up the port N
so it only accepts integers:
it'll accept any value and pass it to the block process.
When you develop the block,
you have to implement relevant checks and conversion rules - for example:
- If the value is a table, matrix, or another kind of array containing a single element, continue with that element (preferred behavior). Or: stop with an error, requiring the user to fix the workflow so that the value is scalar (acceptable but inconvenient for the user).
- If the value is an array that contains two or more elements,
stop with an error informing the user that
the value received to
N
must contain a single integer. - If the value is a string, try its conversion to a number. If the string does not represent a number, stop with an error.
- If the value is a non-integer number, stop with an error. Or: round it to the nearest integer, issue a warning about that to the run log, continue execution - and so on.
Port type declaration is necessary because it ensures that users cannot assign an invalid value to the port while editing the block settings. For example, when they open the Edit value dialog for a port, it'll only allow editing those types you have declared for that port. The dialog validates input values, so users simply cannot save a value of an incorrect type. If they try to do so, they'll get an error immediately in the dialog, and not later during a workflow run, once the block reads the value.
The data types expected by a port are described by its schemas
property in manifest.yaml
.
That property is a list of JSON schemas;
in block development, each schema in this list can be understood as a single type definition.
The schemas are used, for example, by the Edit value dialog
when it validates the value entered by the user.
Note that port schemas aren't used for data validation during the workflow execution,
and aren't checked when users link ports - that is,
there is no concept of data type compatibility for ports.
The port's schemas determine the data types that can be used
when editing (assigning) the port value;
this is the only purpose of port schemas.
pSeven Enterprise defines the base JSON schemas. Those can be understood as base data types: integer, float, string, list, dictionary, and so on. You can use the base types in your port schemas by referencing ($ref). For example:
-
Real number
inputs: "port_name": schemas: [{ $ref: "r" }]
-
General list, element type not restricted
inputs: "port_name": schemas: [{ $ref: "l" }]
If you need a non-default type - for example,
a numeric type with additional restrictions -
you can define a custom type, which adds certain properties to a base one.
To do this, use the allOf
composition
where the first element is a $ref
reference to the base type you inherit from.
For example:
-
Real number from the right-open interval [1.0, 18.0). Restricts the base real number type (
{ $ref: "r" }
).{ title: "Geometry parameter", description: "A geometry parameter that must be less than 18.0 but not less than 1.0", allOf: [ { $ref: "r" }, { minimum: 1.0, exclusiveMaximum: 18.0 } ] }
-
List of strings. Restricts the base list type
{ $ref: "l" }
to only allow string elements{ $ref: "s" }
.{ title: "Strings", description: "A list that can only contain strings", allOf: [ { $ref: "l" }, { items: { $ref: "s" } } ] }
-
A parameter that can only take values from a predefined list. Uses the base enumeration type
{ $ref: "e" }
.{ title: "Units", description: "Supported length units", allOf: [ { $ref: "e" }, { enum: ["mm", "cm", "m"] } ] }
Base types¶
This section lists the base JSON schemas defined in pSeven Enterprise by default. In block development, they essentially define the base data types. Reference ($ref) the base schemas when you create your own JSON schemas for custom data types.
- Real
{ $ref: "r" }
- Integer
{ $ref: "i" }
- Boolean
{ $ref: "b" }
- String
{ $ref: "s" }
- Secret string
{ $ref: "st" }
- Enumeration
{ $ref: "e" }
- List
{ $ref: "l" }
- Dictionary
{ $ref: "d" }
- Vector
{ $ref: "v" }
- Matrix
{ $ref: "m" }
- Table
{ $ref: "t" }
- Binary data (blob)
{ $ref: "bn" }
Real number ({ $ref: "r" }
).
Special values: inf
, infinity
, nan
(not a number);
case insensitive.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "r",
"title": "Real",
"description": "A default Real type",
"oneOf": [
{ "type": "number" },
{
"type": "string",
"pattern":
"^[+-]?([Ii][Nn][Ff][Ii][Nn][Ii][Tt][Yy]|[Ii][Nn][Ff]|[Nn][Aa][Nn])$"
},
{ "$ref": "n" }
],
"default": 0.0
}
Integer number ({ $ref: "i" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "i",
"title": "Integer",
"description": "A default Integer type",
"oneOf": [{ "type": "integer" }, { "$ref": "n" }],
"default": 0
}
String ({ $ref: "s" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "s",
"title": "String",
"description": "A default String type",
"oneOf": [{ "type": "string" }, { "$ref": "n" }],
"default": ""
}
Secret string ({ $ref: "st" }
).
Values of this type are never logged, and you can't get them in APIs (but can set).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "st",
"title": "Secret",
"description": "A default Secret type",
"oneOf": [{ "type": "string" }, { "$ref": "n" }],
"default": null,
"@nullStr": "<not set>"
}
Boolean value ({ $ref: "b" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "b",
"title": "Boolean",
"description": "A default Boolean type",
"type": "boolean",
"default": true
}
Enumeration ({ $ref: "e" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "e",
"title": "Enumeration",
"description": "A default Enumeration type",
"type": "string",
"default": ""
}
List ({ $ref: "l" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "l",
"title": "List",
"description": "A default List type",
"type": "array",
"items": {},
"default": []
}
Dictionary ({ $ref: "d" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "d",
"title": "Dictionary",
"description": "A default Dictionary type",
"type": "object",
"additionalProperties": true,
"default": {}
}
Vector ({ $ref: "v" }
).
All vector elements must have the same type.
That is: real vector, integer vector, or string vector.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "v",
"title": "Vector",
"description": "A default Vector type",
"anyOf": [
{ "type": "array", "items": { "$ref": "r" } },
{ "type": "array", "items": { "$ref": "i" } },
{ "type": "array", "items": { "$ref": "s" } },
],
"default": []
}
Matrix ({ $ref: "m" }
).
All matrix elements must have the same type.
That is: real matrix, integer matrix, or string matrix.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "m",
"title": "Matrix",
"description": "A default Matrix type",
"anyOf": [
{ "type": "array", "items": { "type": "array", "items": { "$ref": "i" } } },
{ "type": "array", "items": { "type": "array", "items": { "$ref": "r" } } },
{ "type": "array", "items": { "type": "array", "items": { "$ref": "s" } } },
],
"default": [[]]
}
Table ({ $ref: "t" }
).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "t",
"title": "Table",
"description": "A default Table type",
"type": "object",
"properties": {
"columns": {
"type": "array",
"items": {
"type": "string"
}
},
"data": {
"type": "object",
"patternProperties": {
"^.*$": {
"anyOf": [
{ "type": "array", "items": { "$ref": "r" } },
{ "type": "array", "items": { "$ref": "i" } },
{ "type": "array", "items": { "$ref": "s" } },
{
"type": "array",
"items": { "oneOf": [{ "$ref": "b" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "e" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "l" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "d" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "v" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "m" }, { "$ref": "n" }] }
},
{
"type": "array",
"items": { "oneOf": [{ "$ref": "t" }, { "$ref": "n" }] }
}
]
}
}
}
},
"additionalProperties": false,
"required": ["data"],
"default": { "columns": [], "data": {} }
}
Binary data ({ $ref: "bn" }
).
Base64-encoded byte array (blob).
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "bn",
"title": "Binary",
"description": "A default Binary type",
"type": "string",
"contentEncoding": "base64",
"default": ""
}