diff --git a/docs/customization.md b/docs/customization.md new file mode 100644 index 0000000..2f489f2 --- /dev/null +++ b/docs/customization.md @@ -0,0 +1,54 @@ +# Customization +To customize the simulated example to your own needs, you will need to take a couple of steps. + +- [Find lab resource classes](#resource_classes) +- [Find or implement lab SiLA servers](#sila_servers) +- [Write your device wrappers](#device_wrappers) +- [Configure your lab definition](#lab_definition) +- [Write your process descriptions](#process_descriptions) +- [Customize the robot arm](#robot_arm) + +## Find lab resource classes {#resource_classes} +The first step is to find the lab resource classes that you want to use in your lab. The +[PythonLab](https://gitlab.com/OpenLabAutomation/lab-automation-packages/pythonLab) package provides a set +of predefined resource classes (see [PythonLab api reference](pythonlab/api_reference.md)) that you can use as a starting +point. + +## Find or implement lab SiLA servers {#sila_servers} +The default method for communicating with lab devices in the LARA lab automation suite is through SiLA servers. +[SiLA](https://sila-standard.com/) is an open source standard for communication between lab devices and has an active +community of developers and users. SiLA server implementations for common lab devices can be found in various repositories, +some of which we link to below: + +- [LARA Lab Automation device integration](https://gitlab.com/OpenLabAutomation/device-integration) +- [SiLA Awesome List of Servers](https://gitlab.com/SiLA2/sila_awesome#servers) + + +## Write your device wrappers {#device_wrappers} +The communication between the lab orchestrator and the sila servers is handled by device wrappers. These wrappers +translate the high-level commands defined in the process descriptions to specific SiLA commands. The +[quickstart](quickstart.md) example comes with a couple of example wrappers that you can use as a starting point. +You can find more information about writing device wrappers in the [wrappers documentation](wrappers.md). + +## Configure your lab definition {#lab_definition} +Your lab config file describes the available resources in your lab and their capacities. In the quickstart template this +file is named `platform_config.yaml`. You will need to modify this file to match the resources available in your lab. +More information about the lab definition can be found at [lab configuration](lab configuration.md). + +## Write your process descriptions {#process_descriptions} +A Pythonlab process description describes the steps that should be executed in the lab. They are written in python and +parsed into a workflow graph by the orchestrator. The process descriptions are the main part of your lab automation and +you will need to write them to match your specific use case. You can find more information about writing process +descriptions in the [pythonLab introduction](pythonlab/processes.md). + +## Customize the robot arm {#robot_arm} +Customizing the robot arm involves two parts. First of all, you will need to configure the +[GenericRobotArm](https://gitlab.com/OpenLabAutomation/device-integration/genericroboticarm) SiLA server to match the +brand and model of your robot arm. +The [adaptation guide for the GenericRoboticArm](https://gitlab.com/OpenLabAutomation/device-integration/genericroboticarm/-/blob/main/docs/adaption.md) +provides some guidance on how to do this. + +The second part is to configure locations of your labware and the devices with respect to the robot arm. +This is done with the RobotTeachingService endpoint on the GenericRobotArm SiLA server. + +More info on configuring the arm and the locations can be found in the [robot arm documentation](robot arm.md). \ No newline at end of file diff --git a/docs/lab configuration.md b/docs/lab configuration.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/pythonlab/api_reference.md b/docs/pythonlab/api_reference.md new file mode 100644 index 0000000..e30cb80 --- /dev/null +++ b/docs/pythonlab/api_reference.md @@ -0,0 +1,32 @@ +## Resources + +### ServiceResources + + +::: pythonlab.resources.services.analysis.PlateReaderServiceResource + + +::: pythonlab.resources.services.labware_storage.LabwareStorageResource + + +::: pythonlab.resources.services.labware_storage.LabwareStorageResourcePool + + +::: pythonlab.resources.services.incubation.IncubatorServiceResource + + +::: pythonlab.resources.services.incubation.IncubatorServiceResourcePool + + +::: pythonlab.resources.services.moving.MoverServiceResource + + +::: pythonlab.resources.services.centrifugation.CentrifugeServiceResource + + + +### LabwareResources + + +::: pythonlab.resource.DynamicLabwareResource + diff --git a/docs/pythonlab.md b/docs/pythonlab/introduction.md similarity index 100% rename from docs/pythonlab.md rename to docs/pythonlab/introduction.md diff --git a/docs/pythonlab/processes.md b/docs/pythonlab/processes.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/quickstart.md b/docs/quickstart.md index 1e27fbd..f942ebb 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,7 +1,8 @@ # Quickstart ## Simulated example The fastest way to try out the LARA suite is by running the simulated example provided by the -[adaptation template](https://gitlab.com/OpenLabAutomation/adaption-template). +[adaptation template](https://gitlab.com/OpenLabAutomation/adaption-template). After the adaptation is set up, you can start customizing it to your needs by following the +instructions in the [customization guide](customization.md). There are two ways to run the example: using Docker or running it directly with Python. diff --git a/docs/robot arm.md b/docs/robot arm.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/wrappers.md b/docs/wrappers.md index 3be8add..3cbd68e 100644 --- a/docs/wrappers.md +++ b/docs/wrappers.md @@ -1,6 +1,6 @@ # Wrappers Wrappers are the interface between the lab orchestrator and the actual devices. They translate the high-level commands -defined in ?? to specific SILA commands. +defined in PythonLab to specific SILA commands. ## The wrapper structure @@ -19,8 +19,6 @@ class MyWrapper(DeviceInterface): ### ProcessStep Describes the step that should be executed. -hoi -bla ### ContainerInfo Describes the container that is handled diff --git a/generate_docs.py b/generate_docs.py new file mode 100644 index 0000000..37e2614 --- /dev/null +++ b/generate_docs.py @@ -0,0 +1,43 @@ +from pythonlab.resources.services import * +from pythonlab.resources import LabwareResource +from pythonlab.resource import ServiceResource +import jinja2 +from pathlib import Path + + +template_dir = Path("./templates") +output_dir = Path("./docs/pythonlab") + + +def get_all_subclasses(cls): + """Recursively get all subclasses of a class.""" + all_subclasses = [] + for subclass in cls.__subclasses__(): + all_subclasses.append(subclass) + all_subclasses.extend(get_all_subclasses(subclass)) + return all_subclasses + + +def main(): + # Get all subclasses + service_subclasses = get_all_subclasses(ServiceResource) + labware_subclasses = get_all_subclasses(LabwareResource) + + # Set up Jinja2 environment + env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) + template = env.get_template('api_reference.md.jinja2') + + # Render template + output = template.render( + service_subclasses=service_subclasses, + labware_subclasses=labware_subclasses + ) + + # Write output file + output_file = output_dir / "api_reference.md" + output_file.write_text(output) + print(f"Generated {output_file}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 6dde0b9..10509e1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,5 +41,14 @@ theme: plugins: - search - mermaid2 + - mkdocstrings: + handlers: + python: + options: + show_source: false + show_root_heading: true + show_root_full_path: true + docstring_style: sphinx + - panzoom: full_screen: true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7c16c74..5c3bb45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,17 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ + "jinja2>=3.1.6", "mkdocs>=1.6.1", "mkdocs-glightbox>=0.5.1", "mkdocs-material>=9.6.22", "mkdocs-mermaid2-plugin>=1.2.3", "mkdocs-panzoom-plugin>=0.4.2", + "mkdocstrings[python]>=0.18", "pygments>=2.19.2", "pymdown-extensions>=10.16.1", + "pythonlab", ] + +[tool.uv.sources] +pythonlab = { url = "https://gitlab.com/api/v4/projects/70367030/packages/pypi/files/8343bd3a8ed255c9e47e2cba859bd0545639b02e148993b82c3a4d8c9952ec1a/pythonlab-0.2.3-py3-none-any.whl" } diff --git a/templates/api_reference.md.jinja2 b/templates/api_reference.md.jinja2 new file mode 100644 index 0000000..4157f4e --- /dev/null +++ b/templates/api_reference.md.jinja2 @@ -0,0 +1,15 @@ +## Resources + +### ServiceResources + +{% for service in service_subclasses %} +::: {{ service.__module__ }}.{{ service.__qualname__ }} + +{% endfor %} + +### LabwareResources + +{% for labware in labware_subclasses %} +::: {{ labware.__module__ }}.{{ labware.__qualname__ }} + +{% endfor %} diff --git a/uv.lock b/uv.lock index ce6ddb0..fc48ecc 100644 --- a/uv.lock +++ b/uv.lock @@ -130,6 +130,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "griffe" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffecli" }, + { name = "griffelib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, +] + +[[package]] +name = "griffecli" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "griffelib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, +] + +[[package]] +name = "griffelib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -169,24 +210,30 @@ name = "lara-tutorial" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "jinja2" }, { name = "mkdocs" }, { name = "mkdocs-glightbox" }, { name = "mkdocs-material" }, { name = "mkdocs-mermaid2-plugin" }, { name = "mkdocs-panzoom-plugin" }, + { name = "mkdocstrings", extra = ["python"] }, { name = "pygments" }, { name = "pymdown-extensions" }, + { name = "pythonlab" }, ] [package.metadata] requires-dist = [ + { name = "jinja2", specifier = ">=3.1.6" }, { name = "mkdocs", specifier = ">=1.6.1" }, { name = "mkdocs-glightbox", specifier = ">=0.5.1" }, { name = "mkdocs-material", specifier = ">=9.6.22" }, { name = "mkdocs-mermaid2-plugin", specifier = ">=1.2.3" }, { name = "mkdocs-panzoom-plugin", specifier = ">=0.4.2" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=0.18" }, { name = "pygments", specifier = ">=2.19.2" }, { name = "pymdown-extensions", specifier = ">=10.16.1" }, + { name = "pythonlab", url = "https://gitlab.com/api/v4/projects/70367030/packages/pypi/files/8343bd3a8ed255c9e47e2cba859bd0545639b02e148993b82c3a4d8c9952ec1a/pythonlab-0.2.3-py3-none-any.whl" }, ] [[package]] @@ -283,6 +330,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] +[[package]] +name = "mkdocs-autorefs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, +] + [[package]] name = "mkdocs-get-deps" version = "0.2.0" @@ -369,6 +430,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/92/211706b72821d8afbd5f1b17e7adc8db01bd0a10bab8537a14ae42683960/mkdocs_panzoom_plugin-0.4.2-py3-none-any.whl", hash = "sha256:6f3f4f29baef73ad4310535427e92ee4f71075bb3900b7a7756110c095de3dc5", size = 22522, upload-time = "2025-09-05T17:01:29.831Z" }, ] +[[package]] +name = "mkdocstrings" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/84/78243847ad9d5c21d30a2842720425b17e880d99dfe824dee11d6b2149b4/mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8", size = 199124, upload-time = "2026-02-09T15:12:01.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/31/7ee938abbde2322e553a2cb5f604cdd1e4728e08bba39c7ee6fae9af840b/mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0", size = 104900, upload-time = "2026-02-09T15:12:00.166Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -439,6 +545,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "pythonlab" +version = "0.2.3" +source = { url = "https://gitlab.com/api/v4/projects/70367030/packages/pypi/files/8343bd3a8ed255c9e47e2cba859bd0545639b02e148993b82c3a4d8c9952ec1a/pythonlab-0.2.3-py3-none-any.whl" } +dependencies = [ + { name = "graphviz" }, + { name = "networkx" }, +] +wheels = [ + { url = "https://gitlab.com/api/v4/projects/70367030/packages/pypi/files/8343bd3a8ed255c9e47e2cba859bd0545639b02e148993b82c3a4d8c9952ec1a/pythonlab-0.2.3-py3-none-any.whl", hash = "sha256:8343bd3a8ed255c9e47e2cba859bd0545639b02e148993b82c3a4d8c9952ec1a" }, +] + +[package.metadata] +requires-dist = [ + { name = "graphviz" }, + { name = "networkx" }, +] + [[package]] name = "pyyaml" version = "6.0.3"