diff --git a/docs/build/variables/index.md b/docs/build/variables/index.md index 7ffeca6cc..c5ffcc4ee 100644 --- a/docs/build/variables/index.md +++ b/docs/build/variables/index.md @@ -17,17 +17,28 @@ They can be used in most Build configuration and input fields that take inputs o - integer parameters (any integer), - and boolean values (`true`/`false`). -Two kinds of variables can be defined: +Variables are organized in scopes. +Each scope is addressed by a prefix that is used when referencing a variable in a template, e.g. a project variable named `host` is referenced as `{{project.host}}`. +The following scopes are available: -`Global variables` +`Global variables` (`global.`) -: It is defined by the administrator in the configuration file at deployment time and cannot be set by a normal user. +: They are defined by the administrator in the configuration file at deployment time and cannot be set by a normal user. -`Project variables (User-defined)` +`Project variables (User-defined)` (`project.`) -: It is defined by the user in the UI. - Project variables can only be used in the same project. - If a project is exported those will be exported as well. +: They are defined by the user in the UI. +Project variables can only be used in the same project. +If a project is exported those will be exported as well. + +`Task variables (User-defined)` (`task.`) + +: They are defined by the user on an individual task and can only be used within that same task. +Task variables can reference global and project variables in their templates. + +`Execution variables` (`execution.`) + +: They are not defined statically but provided for a single workflow run, either when the workflow execution is triggered or while the workflow is running. Build variables can be particularly useful in scenarios where multiple tasks or components within a system need access to the same data or configuration values. Instead of repeating the same information in multiple places, project variables provide a centralized and reusable way to store and retrieve these values. @@ -35,20 +46,20 @@ Instead of repeating the same information in multiple places, project variables ## Benefits of using variables 1. When sending an email to all employees, instead of manually typing or copy pasting each email address, you can conveniently store all the email addresses once and utilize them with a single word. - This saves time and ensures that no email addresses are missed or incorrectly entered. + This saves time and ensures that no email addresses are missed or incorrectly entered. 2. Another scenario where variables can be beneficial is when dealing with lengthy or hard-to-remember values. - For instance, consider the value "xmhnjnnjkmnlbbhbvfhnbjkm". - By assigning it to a variable, you can store it once and easily recall it whenever needed. - This avoids the need to repeatedly type or remember complex values, enhancing efficiency and accuracy in documentation and other tasks. + For instance, consider the value "xmhnjnnjkmnlbbhbvfhnbjkm". + By assigning it to a variable, you can store it once and easily recall it whenever needed. + This avoids the need to repeatedly type or remember complex values, enhancing efficiency and accuracy in documentation and other tasks. 3. In software development, when working with URLs or file paths that are long or subject to change, you can store them in variables. - This allows for easy modification and reuse throughout the codebase, reducing the chances of errors and making maintenance more efficient. - For example, you can assign a URL like "" to a variable named `apiURL` for consistent referencing. + This allows for easy modification and reuse throughout the codebase, reducing the chances of errors and making maintenance more efficient. + For example, you can assign a URL like "" to a variable named `apiURL` for consistent referencing. 4. When creating templates or form letters, variables can be used to personalize the content. - For instance, you can include variables such as {firstName}, {lastName}, and {companyName} to dynamically populate the recipient's name and company information. - This way, you can generate customized communications quickly without manually editing each instance. + For instance, you can include variables such as {firstName}, {lastName}, and {companyName} to dynamically populate the recipient's name and company information. + This way, you can generate customized communications quickly without manually editing each instance. 5. You can save the message, port, host or IP address, tokens, passwords, properties etc. @@ -133,6 +144,73 @@ Type name as `email_ids`, in values we have updated all the email id’s of the ![](di-var-email-defined.png){ class="bordered" } +## Task Variables + +While project variables are shared across all tasks of a project, task variables are defined on a single task and are only available within that same task. +They are useful for values that are specific to one task and should not leak into the rest of the project. + +Task variables are managed in the same way as project variables, but from the configuration view of an individual task. +Open a task (for example a dataset, transformation or workflow) and locate the **Task variables** widget. +Click on :eccenca-item-add-artefact: to add a variable and provide a name, value and description in the same dialog used for project variables. + +!!! note + + The naming rules for task variables are the same as for project variables (letters, digits and underscores, not starting with a digit). + +Task variables are referenced with the `task.` prefix, for example `{{task.myVariable}}`. +In their templates they may themselves reference global and project variables, so a task variable can be composed from project-wide values. + +!!! note + + Task variables are stored together with the task. + When the task or its project is exported, the task variables are exported as well. + They are not visible to or usable by other tasks. + +## Execution Variables + +Execution variables are not defined statically in advance. +Instead, they are provided for a single workflow run and are available to all tasks of that workflow during the run. +They are referenced with the `execution.` prefix, for example `{{execution.myVariable}}`. + +!!! note "Execution scope fallback" + + When a template references `{{execution.}}` but `` has not been set directly in the execution scope, the value falls back to the variable of the same name in the `task`, then `project`, then `global` scope . + A value that is set directly in the execution scope (provided when starting the workflow, or written during workflow execution) always takes precedence and suppresses the fallback. + If the name is not defined in any of the execution, task, project or global scopes, the reference remains unbound and template evaluation fails. + + This makes execution variables convenient as overridable defaults: a workflow can reference `{{execution.}}` throughout, and unless a particular run overrides it, the value is taken from the task, project or global variable of the same name. + +There are two ways to supply execution variables: + +### Passing execution variables when starting a workflow + +When a workflow execution is triggered via the REST API, execution variables can be provided in the JSON request body under the `workflowVariables` key as a simple name/value map. +For example, executing a workflow with a single execution variable `testVar`: + +```json +{ + "workflowVariables": { + "testVar": "World" + } +} +``` + +Each entry is added to the `execution` scope and can be referenced anywhere in the workflow as `{{execution.}}`. +For instance, an operator configured with the template `{{value}} {{execution.testVar}}` would resolve `execution.testVar` to `World` for that run. + +### Setting execution variables during a workflow run + +Execution variables can also be created or updated while a workflow is running. +Two operators in the *Variables* category support this: + +- **Set execution variable** (workflow operator) — a standalone workflow node placed between any two nodes; reads a value from its input and writes it to the `execution` scope, passing the input through unchanged. +- **Set execution variable** (transform operator) — a transformer used inside a transformation mapping; writes the first value of its input to the `execution` scope and passes all values through unchanged. + +!!! note + + Both operators only have an effect while running inside a workflow execution. + Execution variables are scoped to a single workflow run; they are not persisted and are not shared between runs. + ## Using Variables Let's see how these variables are useful. diff --git a/docs/deploy-and-configure/configuration/label-resolution-and-full-text-search/index.md b/docs/deploy-and-configure/configuration/label-resolution-and-full-text-search/index.md index 9536ffe4b..269761f39 100644 --- a/docs/deploy-and-configure/configuration/label-resolution-and-full-text-search/index.md +++ b/docs/deploy-and-configure/configuration/label-resolution-and-full-text-search/index.md @@ -24,19 +24,46 @@ proxy: languagePreferencesAnyLangFallback: true ``` -These properties define not only which properties and languages should be considered, but also the precedence of languages and properties over each other. +These properties define not only which properties and languages should be considered, but also the precedence of properties and languages over each other. The retrieval process can be simplified to the following procedure: -- First, when determining the label for a resource, the language is evaluated, then the property is considered. -- Consequently, for a resource in the default case: +- When determining the label for a resource, the **property** is the primary criterion and the **language** the secondary one. In other words, the complete list of preferred languages is evaluated for the first property before Explore backend (DataPlatform) moves on to the next property. +- Consequently, for a resource with the default settings above, the candidates are tried in this order: 1. An english value for `rdfs:label` is searched. 2. A literal of the property `rdfs:label` without a language tag is searched (which is why there is an entry `""`). 3. An english value of `skos:prefLabel` is searched. 4. A literal of the property `skos:prefLabel` without a language tag is searched. - 5. If nothing is found, Explore backend (DataPlatform) tries to create a prefixed URI, otherwise the last segment of the resource identifier is used. + 5. If nothing is found and `languagePreferencesAnyLangFallback` is `true`, a value in any remaining language is used, again honoring the property precedence (see [Example](#example)). + 6. If still nothing is found, Explore backend (DataPlatform) tries to create a prefixed URI, otherwise the last segment of the resource identifier is used. -Additionally, in case more than one label could be retrieved, for example by conflicting values, the alphabetically first entry is used. +Additionally, in case more than one label could be retrieved for the same property and language, for example by conflicting values, the alphabetically first entry is used. + +!!! note "Property precedence beats language precedence" + + Because the property is the primary criterion, a value of an earlier property is preferred over a better-matching language on a later property. This especially affects untagged literals: an entry `""` in `languagePreferences` matches a literal without a language tag, so if an earlier property carries such an untagged literal, it wins over a later property that has a value in a preferred language. + + Consider the following configuration and resource: + + ``` yaml + proxy: + labelProperties: + - "http://www.w3.org/2004/02/skos/core#notation" + - "http://www.w3.org/2000/01/rdf-schema#label" + - "http://www.w3.org/2004/02/skos/core#prefLabel" + languagePreferences: + - "en" + - "de" + - "" + ``` + + ``` turtle + :labelEn a owl:Class ; + rdfs:label "label en"@en, "label de"@de ; + skos:notation "notation" . + ``` + + The resolved label is `notation`: `skos:notation` is the first property, and although it has no `en` or `de` value, its untagged literal is matched by the `""` language preference, so the search never reaches `rdfs:label`. To make `rdfs:label` win here, either list it before `skos:notation` in `labelProperties`, or remove the `""` entry from `languagePreferences` (which lets `skos:notation` fall through to `rdfs:label "label en"@en`). ## Example diff --git a/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.drawio.png b/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.drawio.png new file mode 100644 index 000000000..ca98e9343 Binary files /dev/null and b/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.drawio.png differ diff --git a/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.png b/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.png deleted file mode 100644 index b57cc3213..000000000 Binary files a/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.png and /dev/null differ diff --git a/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.svg b/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.svg deleted file mode 100644 index 6b3c8f876..000000000 --- a/docs/deploy-and-configure/installation/scenario-k8s-deployment/images/cmem-helm-architecture.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
                                                                                                                                                                                      eccenca Corporate Memory helm chart deployment
...
Data Sources
Data Sources
External Services
External Services
Ontotext GraphDB helm chart deployment
Ontotext GraphDB helm chart deployment
Keycloak helm chart deployment
Keycloak helm chart deployment
Ingress / Route
Ingress / Route
HTTP
HTTP
Explore
Explore
Build
Build
HTTP
HTTP
LDAP
Directory Information
Service

LDAP...
LDAPS
LDAPS

external
OpenID Connect or SAML zone
external...
Brokering 
Brokering 
HTTPS
HTTPS
cmemc


cmemc...
Ingress / Route
Ingress / Route
GraphDB
GraphDB

Files
(csv, json,
xml, ...)
Files...
JDBC
JDBC
HDFS
HDFS
Python Plugins
Python Plugins
salesforce
salesforce
GraphQL
GraphQL
kafka
kafka
Browser


Browser...
HTTPS
HTTPS
HTTPS
HTTPS
HTTPS
HTTPS
Bootstrap Job
cmemc
Bootstrap Job...
HTTPS
HTTPS
HTTPS
HTTPS
Build Service
Build Service
Explore Service
Explore Service
Port 8080
Port 8080
Port 8080
Port 8080
Port 8080
Port 8080
Port 8080
Port 8080
Graph Insights
Graph Insights
Graph Insights Service
Graph Insights Service
Port 8080
Port 8080
Port 8080
Port 8080
HTTP 
HTTP 
HTTP 
HTTP 
HTTPS
HTTPS
Cached
volume
Cached...
Cached
volume
Cached...
Cached
volume
Cached...
Volume
Volume
Volume
Volume
Volume
Volume
HTTP 
HTTP 


HTTP
HTTP
HTTP
HTTP
GraphDB Service
GraphDB Service
Port 7200
Port 7200
HTTPS
HTTPS
Backup Job
Backup Job
HTTP
HTTP
LLM Provider
LLM Provider
HTTP
HTTP
S3
S3
S3
S3
Keycloak
 
 
Keycloak...
PostgreSQL
PostgreSQL
JDBC
JDBC
Keycloak Service
Keycloak Service
Port 8080
Port 8080
Port 8080
Port 8080
HTTP
HTTP
HTTP 
HTTP 
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/deploy-and-configure/installation/scenario-k8s-deployment/index.md b/docs/deploy-and-configure/installation/scenario-k8s-deployment/index.md index dcf086e58..ef58a094e 100644 --- a/docs/deploy-and-configure/installation/scenario-k8s-deployment/index.md +++ b/docs/deploy-and-configure/installation/scenario-k8s-deployment/index.md @@ -24,7 +24,7 @@ provisioned cluster. ## Architecture - ![CMEM Helm Chart Architecture](images/cmem-helm-architecture.png) + ![CMEM Helm Chart Architecture](images/cmem-helm-architecture.drawio.png) ## General Notice diff --git a/docs/develop/.pages b/docs/develop/.pages index fa1d34c03..04dcc4001 100644 --- a/docs/develop/.pages +++ b/docs/develop/.pages @@ -2,6 +2,7 @@ nav: - Develop: index.md - Accessing Graphs with Java Applications: accessing-graphs-with-java-applications - Python Plugins: python-plugins + - Marketplace Packages: packages - Marketplace Packages: marketplace-packages - cmempy - Python API: cmempy-python-api - cmemc - Python Scripts: cmemc-scripts diff --git a/docs/develop/index.md b/docs/develop/index.md index c31d88f00..460c35a45 100644 --- a/docs/develop/index.md +++ b/docs/develop/index.md @@ -21,6 +21,12 @@ API documentation and programming recipes. For Python developers, we offer a [Plugin SDK](python-plugins/index.md) as well as an API for accessing and manipulating Corporate Memory Instances ([cmem-cmempy](cmempy-python-api/index.md)). +- :material-package: Packages + + --- + + [Create your own packages](packages/development) to allow easy distribution and reuse of existing content (data, workflows, ...) through [Marketplace Packages](packages/index.md). + - :material-api: OpenAPI specification --- diff --git a/docs/develop/packages/.pages b/docs/develop/packages/.pages new file mode 100644 index 000000000..fd7aaa43e --- /dev/null +++ b/docs/develop/packages/.pages @@ -0,0 +1,5 @@ +nav: + - Packages: index.md + - Installation and Usage: installation + - Development: development + diff --git a/docs/develop/packages/development/index.md b/docs/develop/packages/development/index.md new file mode 100644 index 000000000..9012d7f57 --- /dev/null +++ b/docs/develop/packages/development/index.md @@ -0,0 +1,152 @@ +--- +title: "Marketplace Package Development" +icon: material/code-json +tags: + - Marketplace + - Package +--- +# Marketplace Package Development + +## Introduction + +In order to support the development of packages, we published a [package template](https://github.com/eccenca/cmem-package-template). +Please have a look at this project to get started. + +This page walks you through a basic example of creating a new package, adding different type of content inside it and finally building it into a package archive ready for distribution. + +## Creating your own package + +## Initializing + +Using [described instructions](https://github.com/eccenca/cmem-package-template/tree/main#usage) have a local folder contain your newly templated project (`my-package-id`), for our example we will use : +```shell +🎤 Type of package + Project Package +🎤 Package ID (e.g., 'eccenca-supply-chain-vocab', 'w3c-org-vocab') + my-package-id +🎤 Human-readable package name (e.g., 'My Awesome Vocabulary', 'My Great Project' ..) + My own package +🎤 Short description of the package (e.g., 'A vocabulary for ...', 'A project ...') + My project and graphs +``` + +You should now have a folder with two level of files : + +- Top level — generic package information such as the changelog, README, CI instructions, and licensing. +- Nested folder (my-package-id) — the actual package content, along with a manifest. + +## Package content + +The nested folder `my-package-id` represents your working directory for developing the package. + +To add content to the package simply drag and drop files you want added into this folder or extract existing content from a live Corporate Memory instance to the working directory. + +!!! Example "Extracting Corporate Memory content to add to the package " + + ```shell + cmemc graph export https://my-company.org/queries/ --output-file my-package-id/queries.ttl + + cmemc project export MyProject_78e981443900a761 --output-dir my-package-id + Export project 1/1: MyProject_78e981443900a761 to my-package-id/2026-07-08-unnamed-MyProject_78e981443900a761.project.zip ... done + + mv my-package-id/2026-07-08-unnamed-MyProject_78e981443900a761.project.zip my-package-id/project.zip + ``` + +## Declaring the files in the manifest + +In order for the package to know about these added files, the `cpa-manifest.json` needs to be edited. + +The `"files":[]` section of the manifest references the files the package needs to bundle. +Complete information about the [package manifest can be found here](https://github.com/eccenca/cmem-package-template/tree/main#package-manifest) and more specifically [how to declare new files](https://github.com/eccenca/cmem-package-template/tree/main#adding-files). + +For our example we will be adding a query graph and a project file, make sure you use valid `file_path` relative to your package working directory (nested folder). + +### Adding graph and project + +```json +"files": [ + {...}, + { + "file_path": "queries.ttl", // inside my-package-id nested folder + "file_type": "graph", + "graph_iri": "https://my-company.org/queries/", + "import_into": [], + "register_as_vocabulary": false + }, + { + "file_path": "project.zip", // inside my-package-id nested folder + "file_type": "project", + "project_id": "MyProject_78e981443900a761" + } +] +``` + +## Testing your package + +To ensure the package correctly detects your added files, you can try to import it in a Corporate Memory instance. + +The package template comes with a predefined `Taskfile.yaml` allowing you to wrap your development steps in single commands. +```shell +task: Available tasks for this project: +* build: Build package archive +* check: Run whole test suite +* clean: Removes dist, *.cpa, ... +* delete: Delete (uninstall) package from Corporate Memory +* export: Export package content from Corporate Memory +* import: Import (install) package to Corporate Memory +* publish: Publish package archive to the marketplace +``` + +To tell the package system to take files from the local working directory and to import them inside Corporate Memory we use **task import**. +An import will always try to uninstall previously installed version of the same package, to ensure it is correctly replaced. + +```shell +task import +task: [delete] cmemc package uninstall $package_id +Package 'my-package-id' is not installed. +task: [import] cmemc package install --input $package_dir +Installing package 'my-package-id' from 'my-package-id' ... done +``` + +!!! Warning "Importing duplicated content" + + If you extracted from Corporate Memory already existing content and added it to your package with the same identifiers (graph URIs, projects IDs, ...), and try to import it back in the form of a new package, the instance might raise a `MarketplacePackagesImportError` due to conflicting elements : (Repository item 'https://my-company.org/queries/' already exists.) . + + In this case, you can simply delete your duplicated content inside Corporate Memory (make sure you do backups in case) before importing them back as a package content. + The difference will be that now Corporate Memory will know this content is part of a managed package, and will handle import/export on that file from now on. + +## Updating the package file content + +If you make modifications to your package content in Corporate Memory, the files will not automatically sync back with your local working directory. + +To extract all the updated content from Corporate Memory into your package working directory in a managed way, simply run **task export** + +```shell +task export +``` + +!!! Warning "Exporting without installing first" + + The platform can only export updated versions of package files that were imported at least once before. If you create new information directly in Corporate Memory that the package manifest doesn't yet declare, such as new graphs, you need to manually add them to your working directory and to your manifest and then import them. + + The rule of thumb is : if you need to make structural change to your package that requires for you to edit your manifest, then make sure to run `import` right after to let Corporate Memory keep track of new files. + + Adding a workflow inside a project is not impacted by this limitation, since it is part of the "project" that is managed and tracked by the package. + +## Building your package + +To generate a `.cpa` file ready to be distributed and installed in different Corporate Memory instances you can run **task build**. + +Make sure your local package folder is a git repository with a clean state (the commit hash is used to generate the cpa). +```shell +task build +``` + +To check how this output `.cpa` file can be imported in different places refer to the [Installation and Usage section](../installation/index.md). + +!!! Success "Next steps" + + There are many improvements you can add to your package, such as declaring dependencies to other plugins or packages, to ensure your `.cpa` file can be installed with all it's requirements everywhere, for that you can refer to existing package examples or the template documentation. + + The final step is usually publishing a version of the package to a remote Marketplace repository, to avoid having to manually transfert the `.cpa` archive. This requires you to have publishing permissions on an eccenca Marketplace instance (either a public or private instance). + This can be done with task publish, either manually or from a CI runner. diff --git a/docs/develop/packages/index.md b/docs/develop/packages/index.md new file mode 100644 index 000000000..2d3ae816b --- /dev/null +++ b/docs/develop/packages/index.md @@ -0,0 +1,33 @@ +--- +title: "Marketplace Packages: Overview" +icon: material/package +tags: + - Marketplace + - Package +hide: + - toc +--- +# Marketplace Packages + +A package is a bundle that groups different parts of Corporate Memory configuration — data, workflows and dependencies — letting you share and reuse them across projects, teams, and different Corporate Memory instances. + +A package is packed into a `.cpa` file, a zip-based archive you can either hand off directly or distribute through a Marketplace, a central repository that supports pushing and +pulling packages. + +The following pages give an overview about this feature: + +
+ +- :material-download-circle-outline: [Installation and Usage](installation/index.md) + + --- + + Intended for Linked Data Experts and Deployment Engineers, this page outlines how to install and use existing marketplace packages. + +- :material-code-json: [Development](development/index.md) + + --- + + Intended for Developers, this page explains how to start developing your own packages. + +
diff --git a/docs/develop/packages/installation/example-project.png b/docs/develop/packages/installation/example-project.png new file mode 100644 index 000000000..753ed3baf Binary files /dev/null and b/docs/develop/packages/installation/example-project.png differ diff --git a/docs/develop/packages/installation/example-vocabulary.png b/docs/develop/packages/installation/example-vocabulary.png new file mode 100644 index 000000000..013961a18 Binary files /dev/null and b/docs/develop/packages/installation/example-vocabulary.png differ diff --git a/docs/develop/packages/installation/index.md b/docs/develop/packages/installation/index.md new file mode 100644 index 000000000..62b7c5f67 --- /dev/null +++ b/docs/develop/packages/installation/index.md @@ -0,0 +1,53 @@ +--- +title: "Marketplace Packages: Installation and Usage" +icon: material/download-circle-outline +tags: + - Package + - Marketplace +--- +# Installation and Usage of Marketplace Packages + +## Installation + +If you want to install a marketplace package, you need to use cmemc's package command group. + +You can either install from a remote marketplace or from a locally obtained `.cpa` file + +```shell-session title="Install a package from marketplace" +$ cmemc package install w3c-xsd-vocab +Installing package 'w3c-xsd-vocab' from marketplace ... done +``` + +```shell-session title="Install a package from cpa file:" +$ cmemc package install --replace --input my-package-v0.0.0-4b7516f.cpa +Installing package 'my-package' from 'my-package-v0.0.0-4b7516f.cpa' +done +``` + +## Usage + +Depending on the content types inside it, an installed package can appear in different places in Corporate Memory, with each item (graph, workflows, projects, ...) surfacing in its respective component. + +
+ +!!! info inline "" + + ![Example: Graphs](example-vocabulary.png "Example: Graphs") + +**Graphs** such as data graphs but also **Vocabularies** or **Shapes Catelog** are listed in [**Explore > Graphs**](../../../explore-and-author/graph-exploration/#graphs). + +
+ +
+ +!!! info inline "" + + ![Example: Projects](example-project.png "Example: Projects") + +**Projects** are imported inside [**Build**](../../../build/introduction-to-the-user-interface#projects). +When you install your first project package, Corporate Memory also creates a special project to store all installed files. +This folder is automatically managed by the package system, and removed once the last package is uninstalled. + +
+ +
diff --git a/docs/develop/python-plugins/installation/index.md b/docs/develop/python-plugins/installation/index.md index 7b4a73896..9cfd0f98c 100644 --- a/docs/develop/python-plugins/installation/index.md +++ b/docs/develop/python-plugins/installation/index.md @@ -7,7 +7,7 @@ tags: --- # Installation and Usage of Python Plugins -Plugins are a released as parts of Python packages. +Plugins are released as parts of Python packages. They can but do not need to be open-source and published on [pypi.org](https://pypi.org/search/?q=%22cmem-plugin-%22) (a widely used Python Package Index). One package can contain multiple plugins. ## Installation