aind-clabe 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. aind_clabe-0.5.0/.flake8 +8 -0
  2. aind_clabe-0.5.0/.gitattributes +4 -0
  3. aind_clabe-0.5.0/.github/workflows/ci.yml +26 -0
  4. aind_clabe-0.5.0/.github/workflows/docs.yml +31 -0
  5. aind_clabe-0.5.0/.github/workflows/tag_and_publish.yml +68 -0
  6. aind_clabe-0.5.0/.gitignore +82 -0
  7. aind_clabe-0.5.0/.gitmodules +3 -0
  8. aind_clabe-0.5.0/.python-version +3 -0
  9. aind_clabe-0.5.0/LICENSE +21 -0
  10. aind_clabe-0.5.0/PKG-INFO +121 -0
  11. aind_clabe-0.5.0/README.md +76 -0
  12. aind_clabe-0.5.0/docs/_regenerate_api.py +159 -0
  13. aind_clabe-0.5.0/docs/api/index.md +3 -0
  14. aind_clabe-0.5.0/docs/dark-logo.svg +129 -0
  15. aind_clabe-0.5.0/docs/docs_examples/behavior_launcher.md +9 -0
  16. aind_clabe-0.5.0/docs/docs_examples/slims_launcher.md +9 -0
  17. aind_clabe-0.5.0/docs/favicon.ico +0 -0
  18. aind_clabe-0.5.0/docs/index.md +1 -0
  19. aind_clabe-0.5.0/docs/light-logo.svg +128 -0
  20. aind_clabe-0.5.0/docs/logo.svg +42 -0
  21. aind_clabe-0.5.0/examples/_._ +0 -0
  22. aind_clabe-0.5.0/examples/behavior_launcher.py +143 -0
  23. aind_clabe-0.5.0/examples/slims_launcher.py +126 -0
  24. aind_clabe-0.5.0/mkdocs.yml +125 -0
  25. aind_clabe-0.5.0/pyproject.toml +101 -0
  26. aind_clabe-0.5.0/scripts/_._ +0 -0
  27. aind_clabe-0.5.0/setup.cfg +4 -0
  28. aind_clabe-0.5.0/setup.py +4 -0
  29. aind_clabe-0.5.0/src/aind_clabe.egg-info/PKG-INFO +121 -0
  30. aind_clabe-0.5.0/src/aind_clabe.egg-info/SOURCES.txt +81 -0
  31. aind_clabe-0.5.0/src/aind_clabe.egg-info/dependency_links.txt +1 -0
  32. aind_clabe-0.5.0/src/aind_clabe.egg-info/requires.txt +31 -0
  33. aind_clabe-0.5.0/src/aind_clabe.egg-info/top_level.txt +1 -0
  34. aind_clabe-0.5.0/src/clabe/__init__.py +14 -0
  35. aind_clabe-0.5.0/src/clabe/apps/__init__.py +5 -0
  36. aind_clabe-0.5.0/src/clabe/apps/_base.py +98 -0
  37. aind_clabe-0.5.0/src/clabe/apps/_bonsai.py +342 -0
  38. aind_clabe-0.5.0/src/clabe/apps/_python_script.py +280 -0
  39. aind_clabe-0.5.0/src/clabe/behavior_launcher/__init__.py +24 -0
  40. aind_clabe-0.5.0/src/clabe/behavior_launcher/_aind_auth.py +92 -0
  41. aind_clabe-0.5.0/src/clabe/behavior_launcher/_cli.py +32 -0
  42. aind_clabe-0.5.0/src/clabe/behavior_launcher/_launcher.py +594 -0
  43. aind_clabe-0.5.0/src/clabe/behavior_launcher/_model_modifiers.py +108 -0
  44. aind_clabe-0.5.0/src/clabe/behavior_launcher/_services.py +316 -0
  45. aind_clabe-0.5.0/src/clabe/behavior_launcher/slims_picker.py +607 -0
  46. aind_clabe-0.5.0/src/clabe/data_mapper/__init__.py +10 -0
  47. aind_clabe-0.5.0/src/clabe/data_mapper/_base.py +95 -0
  48. aind_clabe-0.5.0/src/clabe/data_mapper/aind_data_schema.py +77 -0
  49. aind_clabe-0.5.0/src/clabe/data_mapper/helpers.py +105 -0
  50. aind_clabe-0.5.0/src/clabe/data_transfer/__init__.py +2 -0
  51. aind_clabe-0.5.0/src/clabe/data_transfer/_base.py +63 -0
  52. aind_clabe-0.5.0/src/clabe/data_transfer/aind_watchdog.py +704 -0
  53. aind_clabe-0.5.0/src/clabe/data_transfer/robocopy.py +211 -0
  54. aind_clabe-0.5.0/src/clabe/git_manager/__init__.py +1 -0
  55. aind_clabe-0.5.0/src/clabe/git_manager/_git.py +258 -0
  56. aind_clabe-0.5.0/src/clabe/launcher/__init__.py +11 -0
  57. aind_clabe-0.5.0/src/clabe/launcher/_base.py +690 -0
  58. aind_clabe-0.5.0/src/clabe/launcher/cli.py +129 -0
  59. aind_clabe-0.5.0/src/clabe/logging_helper.py +164 -0
  60. aind_clabe-0.5.0/src/clabe/py.typed +0 -0
  61. aind_clabe-0.5.0/src/clabe/resource_monitor/__init__.py +9 -0
  62. aind_clabe-0.5.0/src/clabe/resource_monitor/_base.py +256 -0
  63. aind_clabe-0.5.0/src/clabe/resource_monitor/_constraints.py +92 -0
  64. aind_clabe-0.5.0/src/clabe/services.py +358 -0
  65. aind_clabe-0.5.0/src/clabe/ui/__init__.py +4 -0
  66. aind_clabe-0.5.0/src/clabe/ui/picker.py +295 -0
  67. aind_clabe-0.5.0/src/clabe/ui/ui_helper.py +372 -0
  68. aind_clabe-0.5.0/tests/__init__.py +67 -0
  69. aind_clabe-0.5.0/tests/assets/bonsai.config +23 -0
  70. aind_clabe-0.5.0/tests/assets/expected_curriculum_metrics.json +1 -0
  71. aind_clabe-0.5.0/tests/assets/expected_curriculum_suggestion.json +1 -0
  72. aind_clabe-0.5.0/tests/test_app.py +171 -0
  73. aind_clabe-0.5.0/tests/test_behavior_launcher.py +198 -0
  74. aind_clabe-0.5.0/tests/test_curriculum.py +67 -0
  75. aind_clabe-0.5.0/tests/test_data_mapper.py +44 -0
  76. aind_clabe-0.5.0/tests/test_data_transfer.py +333 -0
  77. aind_clabe-0.5.0/tests/test_launcher.py +88 -0
  78. aind_clabe-0.5.0/tests/test_logging.py +28 -0
  79. aind_clabe-0.5.0/tests/test_resource_monitor.py +92 -0
  80. aind_clabe-0.5.0/tests/test_services.py +65 -0
  81. aind_clabe-0.5.0/tests/test_slims_picker.py +224 -0
  82. aind_clabe-0.5.0/tests/test_ui.py +95 -0
  83. aind_clabe-0.5.0/uv.lock +1699 -0
@@ -0,0 +1,8 @@
1
+ [flake8]
2
+ exclude =
3
+ .git,
4
+ __pycache__,
5
+ .venv,
6
+ build
7
+ max-complexity = 10
8
+ max-line-length = 120
@@ -0,0 +1,4 @@
1
+ * text=auto
2
+ *.cmd text eol=crlf
3
+ *.bat text eol=crlf
4
+ *.bonsai text
@@ -0,0 +1,26 @@
1
+ name: ci/cd
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ branches:
7
+ - main
8
+ - dev
9
+ push:
10
+ branches:
11
+ - main
12
+ - dev
13
+
14
+ jobs:
15
+
16
+ python-linting:
17
+ uses: AllenNeuralDynamics/Aind.Behavior.GitHubActions/.github/workflows/python-linting.yml@main
18
+ with:
19
+ runs-on: windows-latest
20
+
21
+ aind-behavior-framework-testing:
22
+ uses: AllenNeuralDynamics/Aind.Behavior.GitHubActions/.github/workflows/python-unit-test.yml@main
23
+ with:
24
+ python-version-path: .python-version
25
+ runs-on: windows-latest
26
+ run-coverage: true
@@ -0,0 +1,31 @@
1
+ name: deploy-docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ deploy:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: "3.11"
20
+ enable-cache: true
21
+
22
+ - name: Install dependencies
23
+ run: uv sync --extra docs
24
+
25
+ - name: Configure Git user
26
+ run: |
27
+ git config user.name "github-actions[bot]"
28
+ git config user.email "github-actions[bot]@users.noreply.github.com"
29
+
30
+ - name: Deploy documentation
31
+ run: uv run mkdocs gh-deploy --force
@@ -0,0 +1,68 @@
1
+ name: Tag and Publish
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ publish:
7
+ type: boolean
8
+ description: 'Whether to publish the package to PyPI'
9
+ required: false
10
+ default: true
11
+
12
+ jobs:
13
+ tag:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v3
18
+ with:
19
+ enable-cache: true
20
+ - name: Set up Python
21
+ run: uv python install
22
+
23
+ - name: Extract version from __init__.py and package name from pyproject.toml
24
+ id: get_version_and_name
25
+ run: |
26
+ version=$(uv run python -c "import re;
27
+ with open(f'./src/clabe/__init__.py', 'r') as f:
28
+ content = f.read();
29
+ match = re.search(r'__version__\s*=\s*[\'\"]([^\'\"]+)[\'\"]', content);
30
+ print(match.group(1)) if match else exit(1)")
31
+ echo "PACKAGE_NAME=aind-clabe" >> $GITHUB_ENV
32
+ echo "PACKAGE_VERSION=$version" >> $GITHUB_ENV
33
+ shell: bash
34
+
35
+ - name: Create Git tag
36
+ run: |
37
+ git config --global user.name "github-actions[bot]"
38
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
39
+ git tag -a v${{ env.PACKAGE_VERSION }} -m "v${{ env.PACKAGE_VERSION }}"
40
+ git push origin v${{ env.PACKAGE_VERSION }}
41
+
42
+ - name: Create GitHub Release
43
+ uses: softprops/action-gh-release@v2
44
+ with:
45
+ tag_name: v${{ env.PACKAGE_VERSION }}
46
+ name: Release v${{ env.PACKAGE_VERSION }}
47
+ generate_release_notes: true
48
+
49
+ publish:
50
+ needs: tag
51
+ if: github.event_name == 'workflow_dispatch' && inputs.publish || github.event_name == 'push'
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: astral-sh/setup-uv@v3
56
+ with:
57
+ enable-cache: true
58
+
59
+ - name: Set up Python
60
+ run: uv python install
61
+
62
+ - name: Build
63
+ run: uv build
64
+
65
+ - name: Publish
66
+ run: uv publish --token ${{ secrets.AIND_PYPI_TOKEN }}
67
+ - name: Publish
68
+ run: uv publish --token ${{ secrets.AIND_PYPI_TOKEN }}
@@ -0,0 +1,82 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Sphinx documentation
55
+ docs/_build/
56
+
57
+ # Mkdocs
58
+ docs/site/
59
+ site/
60
+
61
+ # Jupyter Notebook
62
+ .ipynb_checkpoints
63
+
64
+ # IPython
65
+ profile_default/
66
+ ipython_config.py
67
+
68
+ # pyenv
69
+ # For a library or package, you might want to ignore these files since the code is
70
+ # intended to run in multiple environments; otherwise, check them in:
71
+ # .python-version
72
+
73
+ # vscode
74
+ .vscode/
75
+
76
+ # venv
77
+ .venv/
78
+
79
+ # pycharm
80
+ .idea/
81
+
82
+ local
@@ -0,0 +1,3 @@
1
+ [submodule "tests/assets/Aind.Behavior.curriculumTemplate"]
2
+ path = tests/assets/Aind.Behavior.curriculumTemplate
3
+ url = https://github.com/AllenNeuralDynamics/Aind.Behavior.curriculumTemplate
@@ -0,0 +1,3 @@
1
+ 3.11
2
+ 3.12
3
+ 3.13
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Allen Institute for Neural Dynamics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: aind-clabe
3
+ Version: 0.5.0
4
+ Summary: A library for a minimal framework that can be used to build experimental interfaces.
5
+ Author-email: Bruno Cruz <bruno.cruz@alleninstitute.org>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://allenneuraldynamics.github.io/clabe/
8
+ Project-URL: Repository, https://github.com/AllenNeuralDynamics/clabe/
9
+ Project-URL: Issues, https://github.com/AllenNeuralDynamics/clabe/issues
10
+ Project-URL: Changelog, https://github.com/AllenNeuralDynamics/clabe/releases
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pydantic>=2.7
19
+ Requires-Dist: pydantic-settings
20
+ Requires-Dist: gitpython
21
+ Requires-Dist: semver
22
+ Requires-Dist: rich
23
+ Requires-Dist: aind_behavior_services<1
24
+ Provides-Extra: aind-services
25
+ Requires-Dist: aind-slims-api; extra == "aind-services"
26
+ Requires-Dist: aind-watchdog-service; extra == "aind-services"
27
+ Requires-Dist: aind-data-schema<2; extra == "aind-services"
28
+ Requires-Dist: ms-active-directory; extra == "aind-services"
29
+ Requires-Dist: cryptography; extra == "aind-services"
30
+ Requires-Dist: winkerberos; sys_platform == "win32" and extra == "aind-services"
31
+ Requires-Dist: ldap3; sys_platform == "win32" and extra == "aind-services"
32
+ Provides-Extra: dev
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Requires-Dist: codespell; extra == "dev"
35
+ Requires-Dist: coverage; extra == "dev"
36
+ Requires-Dist: aind-clabe[aind-services]; extra == "dev"
37
+ Provides-Extra: docs
38
+ Requires-Dist: aind-clabe[aind-services]; extra == "docs"
39
+ Requires-Dist: mkdocs; extra == "docs"
40
+ Requires-Dist: mkdocs-material; extra == "docs"
41
+ Requires-Dist: mkdocstrings[python]; extra == "docs"
42
+ Requires-Dist: pymdown-extensions; extra == "docs"
43
+ Requires-Dist: ruff; extra == "docs"
44
+ Dynamic: license-file
45
+
46
+ <div align="center">
47
+
48
+ <pre>
49
+ ██████╗██╗ █████╗ ██████╗ ███████╗
50
+ ██╔════╝██║ ██╔══██╗██╔══██╗██╔════╝
51
+ ██║ ██║ ███████║██████╔╝█████╗
52
+ ██║ ██║ ██╔══██║██╔══██╗██╔══╝
53
+ ╚██████╗███████╗██║ ██║██████╔╝███████╗
54
+ ╚═════╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝
55
+
56
+ Command-line-interface Launcher for AIND Behavior Experiments
57
+ </pre>
58
+ </div>
59
+
60
+
61
+
62
+ ![CI](https://github.com/AllenNeuralDynamics/Aind.Behavior.ExperimentLauncher/actions/workflows/ci.yml/badge.svg)
63
+ [![PyPI - Version](https://img.shields.io/pypi/v/aind-clabe)](https://pypi.org/project/aind-clabe/)
64
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
65
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
66
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
67
+
68
+ # clabe
69
+
70
+ A library for building workflows for behavior experiments.
71
+
72
+ > ⚠️ **Caution:**
73
+ > This repository is currently under active development and is subject to frequent changes. Features and APIs may evolve without prior notice.
74
+
75
+ ## Installing and Upgrading
76
+
77
+ If you choose to clone the repository, you can install the package by running the following command from the root directory of the repository:
78
+
79
+ ```bash
80
+ pip install .
81
+ ```
82
+
83
+ Otherwise, you can use pip:
84
+
85
+ ```bash
86
+ pip install aind-clabe
87
+ ```
88
+
89
+ ## Getting started and API usage
90
+
91
+ The library provides a main class "Launcher" that can be used to create a linear workflow for behavior experiments. These workflows rely on modular interfaces that can be used to interact with various components of the experiment and other services.
92
+ Some of these services are specific for AIND:
93
+
94
+ - [aind-data-schema](https://github.com/AllenNeuralDynamics/aind-data-schema)
95
+ - [aind-data-schema-models](https://github.com/AllenNeuralDynamics/aind-data-schema-models)
96
+ - [aind-watchdog-service](https://github.com/AllenNeuralDynamics/aind-watchdog-service)
97
+ - [aind-slims-api](https://github.com/AllenNeuralDynamics/aind-slims-api)
98
+ - [aind-data-mapper](https://github.com/AllenNeuralDynamics/aind-metadata-mapper)
99
+
100
+ We will also try to scope all dependencies of the related to AIND Services to its own optional dependency list in the `./pyproject.toml` file of this repository. Therefore, in order to use this module, you will need to install these optional dependencies by running:
101
+
102
+ ```uv sync --extra aind-services```
103
+
104
+ A basic example of how to use the Launcher class can be found in the `examples` directory of this repository.
105
+
106
+ ## Contributors
107
+
108
+ Contributions to this repository are welcome! However, please ensure that your code adheres to the recommended DevOps practices below:
109
+
110
+ ### Linting
111
+
112
+ We use [ruff](https://docs.astral.sh/ruff/) as our primary linting tool.
113
+
114
+ ### Testing
115
+
116
+ Attempt to add tests when new features are added.
117
+ To run the currently available tests, run `uv run -m unittest` from the root of the repository.
118
+
119
+ ### Lock files
120
+
121
+ We use [uv](https://docs.astral.sh/uv/) to manage our lock files and therefore encourage everyone to use uv as a package manager as well.
@@ -0,0 +1,76 @@
1
+ <div align="center">
2
+
3
+ <pre>
4
+ ██████╗██╗ █████╗ ██████╗ ███████╗
5
+ ██╔════╝██║ ██╔══██╗██╔══██╗██╔════╝
6
+ ██║ ██║ ███████║██████╔╝█████╗
7
+ ██║ ██║ ██╔══██║██╔══██╗██╔══╝
8
+ ╚██████╗███████╗██║ ██║██████╔╝███████╗
9
+ ╚═════╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝
10
+
11
+ Command-line-interface Launcher for AIND Behavior Experiments
12
+ </pre>
13
+ </div>
14
+
15
+
16
+
17
+ ![CI](https://github.com/AllenNeuralDynamics/Aind.Behavior.ExperimentLauncher/actions/workflows/ci.yml/badge.svg)
18
+ [![PyPI - Version](https://img.shields.io/pypi/v/aind-clabe)](https://pypi.org/project/aind-clabe/)
19
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
20
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
21
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
22
+
23
+ # clabe
24
+
25
+ A library for building workflows for behavior experiments.
26
+
27
+ > ⚠️ **Caution:**
28
+ > This repository is currently under active development and is subject to frequent changes. Features and APIs may evolve without prior notice.
29
+
30
+ ## Installing and Upgrading
31
+
32
+ If you choose to clone the repository, you can install the package by running the following command from the root directory of the repository:
33
+
34
+ ```bash
35
+ pip install .
36
+ ```
37
+
38
+ Otherwise, you can use pip:
39
+
40
+ ```bash
41
+ pip install aind-clabe
42
+ ```
43
+
44
+ ## Getting started and API usage
45
+
46
+ The library provides a main class "Launcher" that can be used to create a linear workflow for behavior experiments. These workflows rely on modular interfaces that can be used to interact with various components of the experiment and other services.
47
+ Some of these services are specific for AIND:
48
+
49
+ - [aind-data-schema](https://github.com/AllenNeuralDynamics/aind-data-schema)
50
+ - [aind-data-schema-models](https://github.com/AllenNeuralDynamics/aind-data-schema-models)
51
+ - [aind-watchdog-service](https://github.com/AllenNeuralDynamics/aind-watchdog-service)
52
+ - [aind-slims-api](https://github.com/AllenNeuralDynamics/aind-slims-api)
53
+ - [aind-data-mapper](https://github.com/AllenNeuralDynamics/aind-metadata-mapper)
54
+
55
+ We will also try to scope all dependencies of the related to AIND Services to its own optional dependency list in the `./pyproject.toml` file of this repository. Therefore, in order to use this module, you will need to install these optional dependencies by running:
56
+
57
+ ```uv sync --extra aind-services```
58
+
59
+ A basic example of how to use the Launcher class can be found in the `examples` directory of this repository.
60
+
61
+ ## Contributors
62
+
63
+ Contributions to this repository are welcome! However, please ensure that your code adheres to the recommended DevOps practices below:
64
+
65
+ ### Linting
66
+
67
+ We use [ruff](https://docs.astral.sh/ruff/) as our primary linting tool.
68
+
69
+ ### Testing
70
+
71
+ Attempt to add tests when new features are added.
72
+ To run the currently available tests, run `uv run -m unittest` from the root of the repository.
73
+
74
+ ### Lock files
75
+
76
+ We use [uv](https://docs.astral.sh/uv/) to manage our lock files and therefore encourage everyone to use uv as a package manager as well.
@@ -0,0 +1,159 @@
1
+ import logging
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Union
5
+
6
+ import yaml
7
+
8
+ ROOT_DIR = Path(__file__).parent.parent
9
+ PACKAGE_NAME = "clabe"
10
+ SRC_DIR = ROOT_DIR / "src" / PACKAGE_NAME
11
+ DOCS_DIR = ROOT_DIR / "docs"
12
+ API_DIR = DOCS_DIR / "api"
13
+ MKDOCS_YML = ROOT_DIR / "mkdocs.yml"
14
+ API_LABEL = "API Reference"
15
+ INCLUDE_PRIVATE_MODULES = False
16
+
17
+ TO_COPY = ["assets", "examples", "LICENSE"]
18
+ log = logging.getLogger("mkdocs")
19
+
20
+
21
+ def discover_python_modules(package_root: Path, include_private: bool = False) -> List[str]:
22
+ modules = []
23
+
24
+ def _find_modules(current_path: Path, prefix: str = "") -> None:
25
+ if not current_path.exists() or not current_path.is_dir():
26
+ return
27
+
28
+ for item in current_path.iterdir():
29
+ if not item.is_dir():
30
+ continue
31
+ if not (item / "__init__.py").exists():
32
+ continue
33
+
34
+ if item.name.startswith("_") and not include_private:
35
+ continue
36
+
37
+ module_name = f"{prefix}{item.name}" if prefix else item.name
38
+ modules.append(module_name)
39
+
40
+ # Recursively search subdirectories for nested modules
41
+ _find_modules(item, f"{module_name}.")
42
+
43
+ _find_modules(package_root)
44
+ return sorted(modules)
45
+
46
+
47
+ def discover_module_files(module_path: Path, include_private: bool = False) -> List[str]:
48
+ files = []
49
+
50
+ def _find_files(current_path: Path, prefix: str = "") -> None:
51
+ if not current_path.exists() or not current_path.is_dir():
52
+ return
53
+
54
+ for item in current_path.iterdir():
55
+ if item.is_file() and item.suffix == ".py":
56
+ if item.name.startswith("_") and not include_private:
57
+ continue
58
+ file_name = f"{prefix}{item.stem}" if prefix else item.stem
59
+ files.append(file_name)
60
+ elif item.is_dir() and not item.name.startswith("__"):
61
+ if item.name.startswith("_") and not include_private:
62
+ continue
63
+ # Recursively search subdirectories
64
+ _find_files(item, f"{prefix}{item.name}." if prefix else f"{item.name}.")
65
+
66
+ _find_files(module_path)
67
+ return sorted(files)
68
+
69
+
70
+ def on_pre_build(config: Dict[str, Any]) -> None:
71
+ """Mkdocs pre-build hook."""
72
+ for file_or_dir in TO_COPY:
73
+ src: Path = ROOT_DIR / file_or_dir
74
+ dest: Path = DOCS_DIR / file_or_dir
75
+
76
+ if src.exists():
77
+ log.info(f"Copying {file_or_dir} to docs...")
78
+
79
+ if src.is_file():
80
+ print(f"Copying file {src} to {dest}")
81
+ shutil.copy(src, dest)
82
+ else:
83
+ if dest.exists():
84
+ shutil.rmtree(dest)
85
+ shutil.copytree(src, dest)
86
+ log.info(f"{file_or_dir} copied successfully.")
87
+ else:
88
+ log.warning(f"Source: {file_or_dir} not found, skipping.")
89
+
90
+ main()
91
+
92
+
93
+ def generate_api_structure() -> Dict[str, List[Dict[str, str]]]:
94
+ api_structure: Dict[str, List[Dict[str, str]]] = {}
95
+ modules = discover_python_modules(SRC_DIR, INCLUDE_PRIVATE_MODULES)
96
+
97
+ for module_name in modules:
98
+ module_structure: List[Dict[str, str]] = []
99
+ module_path = SRC_DIR / module_name.replace(".", "/")
100
+
101
+ # Add the module's __init__.py as the main module entry
102
+ safe_module_name = module_name.replace(".", "_")
103
+ module_structure.append({module_name: f"api/{safe_module_name}/{safe_module_name}.md"})
104
+
105
+ module_files = discover_module_files(module_path, INCLUDE_PRIVATE_MODULES)
106
+ for file_name in module_files:
107
+ safe_file_name = file_name.replace(".", "_")
108
+ module_structure.append({file_name: f"api/{safe_module_name}/{safe_file_name}.md"})
109
+
110
+ (API_DIR / safe_module_name).mkdir(parents=True, exist_ok=True)
111
+
112
+ with open(DOCS_DIR / f"api/{safe_module_name}/{safe_module_name}.md", "w") as f:
113
+ f.write(f"# {module_name}\n\n")
114
+ f.write(f"::: {PACKAGE_NAME}.{module_name}\n")
115
+
116
+ for file_name in module_files:
117
+ safe_file_name = file_name.replace(".", "_")
118
+ with open(DOCS_DIR / f"api/{safe_module_name}/{safe_file_name}.md", "w") as f:
119
+ f.write(f"# {module_name}.{file_name}\n\n")
120
+ f.write(f"::: {PACKAGE_NAME}.{module_name}.{file_name}\n")
121
+
122
+ api_structure[module_name] = module_structure
123
+
124
+ return api_structure
125
+
126
+
127
+ def update_mkdocs_yml(api_structure: Dict[str, List[Dict[str, str]]]) -> None:
128
+ with open(MKDOCS_YML, "r") as f:
129
+ config: Dict[str, Any] = yaml.safe_load(f)
130
+
131
+ nav: List[Union[str, Dict[str, Any]]] = config.get("nav", [])
132
+
133
+ for entry in nav:
134
+ if isinstance(entry, dict) and API_LABEL in entry:
135
+ api_ref: List[Union[str, Dict[str, List[Dict[str, str]]]]] = ["api/index.md"]
136
+
137
+ for module_name, module_content in api_structure.items():
138
+ api_ref.append({module_name.capitalize().replace("_", " "): module_content})
139
+
140
+ entry[API_LABEL] = api_ref
141
+
142
+ with open(MKDOCS_YML, "w") as f:
143
+ yaml.dump(config, f, sort_keys=False, default_flow_style=False)
144
+
145
+
146
+ def main() -> None:
147
+ log.info("Regenerating API documentation...")
148
+
149
+ # Generate API structure
150
+ api_structure: Dict[str, List[Dict[str, str]]] = generate_api_structure()
151
+
152
+ # Update mkdocs.yml
153
+ update_mkdocs_yml(api_structure)
154
+
155
+ log.info("API documentation regenerated successfully.")
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
@@ -0,0 +1,3 @@
1
+ # API Reference
2
+
3
+ The `clabe` package