-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feature/new_clip_block
- Loading branch information
Showing
54 changed files
with
5,047 additions
and
781 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import json | ||
import os | ||
from collections import defaultdict | ||
from typing import List, Dict, Optional | ||
|
||
import pytest | ||
|
||
from tests.workflows.integration_tests.execution.workflows_gallery_collector.decorators import GALLERY_ENTRIES, \ | ||
WorkflowGalleryEntry | ||
|
||
INTEGRATION_TESTS_DIRECTORY = os.path.abspath(os.path.join( | ||
os.path.dirname(__file__), | ||
"..", | ||
"..", | ||
"tests/workflows/integration_tests/execution" | ||
)) | ||
DOCS_ROOT = os.path.abspath(os.path.join( | ||
os.path.dirname(__file__), | ||
"..", | ||
"..", | ||
"docs" | ||
)) | ||
WORKFLOWS_DOCS_ROOT_PATH = os.path.join(DOCS_ROOT, "workflows") | ||
|
||
GALLERY_INDEX_PATH = os.path.join(WORKFLOWS_DOCS_ROOT_PATH, "gallery_index.md") | ||
GALLERY_DIR_PATH = os.path.join(WORKFLOWS_DOCS_ROOT_PATH, "gallery") | ||
|
||
|
||
def generate_gallery() -> None: | ||
run_pytest_collection_to_fill_workflows_gallery() | ||
categorised_gallery = categorise_gallery(gallery=GALLERY_ENTRIES) | ||
generate_gallery_index(categories=list(categorised_gallery.keys())) | ||
for category, entries in categorised_gallery.items(): | ||
generate_gallery_page_for_category(category=category, entries=entries) | ||
|
||
|
||
def run_pytest_collection_to_fill_workflows_gallery() -> None: | ||
pytest.main(["--collect-only", INTEGRATION_TESTS_DIRECTORY]) | ||
|
||
|
||
def categorise_gallery(gallery: List[WorkflowGalleryEntry]) -> Dict[str, List[WorkflowGalleryEntry]]: | ||
result = defaultdict(list) | ||
for item in gallery: | ||
result[item.category].append(item) | ||
return result | ||
|
||
|
||
def generate_gallery_index(categories: List[str]) -> None: | ||
index = read_file(path=GALLERY_INDEX_PATH) | ||
index_lines = index.split("\n") | ||
list_start_index = find_line_with_marker(lines=index_lines, marker='<ul id="workflows-gallery">') | ||
list_end_index = find_line_with_marker(lines=index_lines, marker="</ul>") | ||
if list_start_index is None or list_end_index is None: | ||
raise RuntimeError("Could not find expected <ul> markers in gallery index file") | ||
categories_entries = [ | ||
f'\t<li><a href="{generate_gallery_page_link(category=category)}">{category}</a></li>' | ||
for category in categories | ||
] | ||
new_index = index_lines[:list_start_index + 1] | ||
new_index.extend(categories_entries) | ||
new_index.extend(index_lines[list_end_index:]) | ||
new_index_content = "\n".join(new_index) | ||
write_file(path=GALLERY_INDEX_PATH, content=new_index_content) | ||
|
||
|
||
GALLERY_PAGE_TEMPLATE = """ | ||
# Example Workflows - {category} | ||
Below you can find example workflows you can use as inspiration to build your apps. | ||
{examples} | ||
""".strip() | ||
|
||
|
||
def generate_gallery_page_for_category( | ||
category: str, | ||
entries: List[WorkflowGalleryEntry], | ||
) -> None: | ||
examples = [ | ||
generate_gallery_entry_docs(entry=entry) | ||
for entry in entries | ||
] | ||
page_content = GALLERY_PAGE_TEMPLATE.format( | ||
category=category, | ||
examples="\n\n".join(examples) | ||
) | ||
file_path = generate_gallery_page_file_path(category=category) | ||
write_file(path=file_path, content=page_content) | ||
|
||
|
||
GALLERY_ENTRY_TEMPLATE = """ | ||
## {title} | ||
{description} | ||
??? tip "Workflow definition" | ||
```json | ||
{workflow_definition} | ||
``` | ||
""".strip() | ||
|
||
|
||
def generate_gallery_entry_docs(entry: WorkflowGalleryEntry) -> str: | ||
return GALLERY_ENTRY_TEMPLATE.format( | ||
title=entry.use_case_title, | ||
description=entry.use_case_description, | ||
workflow_definition="\n\t".join(json.dumps(entry.workflow_definition, indent=4).split("\n")), | ||
) | ||
|
||
|
||
def read_file(path: str) -> str: | ||
with open(path, "r") as f: | ||
return f.read() | ||
|
||
|
||
def write_file(path: str, content: str) -> None: | ||
path = os.path.abspath(path) | ||
parent_dir = os.path.dirname(path) | ||
os.makedirs(parent_dir, exist_ok=True) | ||
with open(path, "w") as f: | ||
f.write(content) | ||
|
||
|
||
def find_line_with_marker(lines: List[str], marker: str) -> Optional[int]: | ||
for i, line in enumerate(lines): | ||
if marker in line: | ||
return i | ||
return None | ||
|
||
|
||
def generate_gallery_page_link(category: str) -> str: | ||
file_path = generate_gallery_page_file_path(category=category) | ||
return file_path[len(DOCS_ROOT):-3] | ||
|
||
|
||
def generate_gallery_page_file_path(category: str) -> str: | ||
category_slug = slugify_category(category=category) | ||
return os.path.join(GALLERY_DIR_PATH, f"{category_slug}.md") | ||
|
||
|
||
def slugify_category(category: str) -> str: | ||
return category.lower().replace(" ", "_").replace("/", "_") | ||
|
||
|
||
if __name__ == "__main__": | ||
generate_gallery() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Bundling Workflows blocks | ||
|
||
To efficiently manage the Workflows ecosystem, a standardized method for building and distributing blocks is | ||
essential. This allows users to create their own blocks and bundle them into Workflow plugins. A Workflow plugin | ||
is essentially a Python library that implements a defined interface and can be structured in various ways. | ||
|
||
This page outlines the mandatory interface requirements and suggests a structure for blocks that aligns with | ||
the [Workflows versioning](/workflows/versioning) guidelines. | ||
|
||
## Proposed structure of plugin | ||
|
||
We propose the following structure of plugin: | ||
|
||
``` | ||
. | ||
├── requirements.txt # file with requirements | ||
├── setup.py # use different package creation method if you like | ||
├── {plugin_name} | ||
│ ├── __init__.py # main module that contains loaders | ||
│ ├── kinds.py # optionally - definitions of custom kinds | ||
│ ├── {block_name} # package for your block | ||
│ │ ├── v1.py # version 1 of your block | ||
│ │ ├── ... # ... next versions | ||
│ │ └── v5.py # version 5 of your block | ||
│ └── {block_name} # package for another block | ||
└── tests # tests for blocks | ||
``` | ||
|
||
## Required interface | ||
|
||
Plugin must only provide few extensions to `__init__.py` in main package | ||
compared to standard Python library: | ||
|
||
* `load_blocks()` function to provide list of blocks' classes (required) | ||
|
||
* `load_kinds()` function to return all custom [kinds](/workflows/kinds/) the plugin defines (optional) | ||
|
||
* `REGISTERED_INITIALIZERS` module property which is a dict mapping name of block | ||
init parameter into default value or parameter-free function constructing that value - optional | ||
|
||
|
||
### `load_blocks()` function | ||
|
||
Function is supposed to enlist all blocks in the plugin - it is allowed to define | ||
a block once. | ||
|
||
Example: | ||
|
||
```python | ||
from typing import List, Type | ||
from inference.core.workflows.prototypes.block import WorkflowBlock | ||
|
||
# example assumes that your plugin name is `my_plugin` and | ||
# you defined the blocks that are imported here | ||
from my_plugin.block_1.v1 import Block1V1 | ||
from my_plugin.block_2.v1 import Block2V1 | ||
|
||
def load_blocks() -> List[Type[WorkflowBlock]]: | ||
return [ | ||
Block1V1, | ||
Block2V1, | ||
] | ||
``` | ||
|
||
### `load_kinds()` function | ||
|
||
`load_kinds()` function to return all custom kinds the plugin defines. It is optional as your blocks | ||
may not need custom kinds. | ||
|
||
Example: | ||
|
||
```python | ||
from typing import List | ||
from inference.core.workflows.execution_engine.entities.types import Kind | ||
|
||
# example assumes that your plugin name is `my_plugin` and | ||
# you defined the imported kind | ||
from my_plugin.kinds import MY_KIND | ||
|
||
|
||
def load_kinds() -> List[Kind]: | ||
return [MY_KIND] | ||
``` | ||
|
||
|
||
## `REGISTERED_INITIALIZERS` dictionary | ||
|
||
As you know from [the docs describing the Workflows Compiler](/workflows/workflows_compiler/) | ||
and the [blocks development guide](/workflows/create_workflow_block/), Workflow | ||
blocs are dynamically initialized during compilation and may require constructor | ||
parameters. Those parameters can default to values registered in the `REGISTERED_INITIALIZERS` | ||
dictionary. To expose default a value for an init parameter of your block - | ||
simply register the name of the init param and its value (or a function generating a value) in the dictionary. | ||
This is optional part of the plugin interface, as not every block requires a constructor. | ||
|
||
Example: | ||
|
||
```python | ||
import os | ||
|
||
def init_my_param() -> str: | ||
# do init here | ||
return "some-value" | ||
|
||
REGISTERED_INITIALIZERS = { | ||
"param_1": 37, | ||
"param_2": init_my_param, | ||
} | ||
``` | ||
|
||
## Enabling plugin in your Workflows ecosystem | ||
|
||
To load a plugin you must: | ||
|
||
* install the Python package with the plugin in the environment you run Workflows | ||
|
||
* export an environment variable named `WORKFLOWS_PLUGINS` set to a comma-separated list of names | ||
of plugins you want to load. | ||
|
||
* Example: to load two plugins `plugin_a` and `plugin_b`, you need to run | ||
`export WORKFLOWS_PLUGINS="plugin_a,plugin_b"` |
Oops, something went wrong.