plesty-sdk 0.0.2.dev2__py3-none-any.whl → 0.0.2.dev4__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plesty-sdk
3
- Version: 0.0.2.dev2
3
+ Version: 0.0.2.dev4
4
4
  Summary: Command-line application for developing Python modules compliant with the Plesty standard.
5
5
  Author: Plesty Development Team
6
6
  License-File: COPYING
@@ -55,5 +55,6 @@ The suggested levels of plesty standards are:
55
55
 
56
56
 
57
57
  ## LICENSE
58
+
58
59
  Plesty is licensed under the GNU Lesser General Public License v3.0 or later
59
60
  (LGPL-3.0-or-later). See the LICENSE file.
@@ -0,0 +1,7 @@
1
+ plesty_sdk-0.0.2.dev4.dist-info/METADATA,sha256=8fy0ilYqdc0rnS2KVAi0k5Xtuez9kz-3nWY-fpwsh8E,2002
2
+ plesty_sdk-0.0.2.dev4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
3
+ plesty_sdk-0.0.2.dev4.dist-info/entry_points.txt,sha256=fNrx7o8Sf1lQfZJM-_uPYcPWbJMO94RAYh7z-DnGB5o,45
4
+ plesty_sdk-0.0.2.dev4.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
5
+ plesty_sdk-0.0.2.dev4.dist-info/licenses/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
6
+ plesty_sdk-0.0.2.dev4.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
7
+ plesty_sdk-0.0.2.dev4.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- plestysdk/__init__.py,sha256=m6UekKftTahNJ3W5K3mZSz4Y4ZZpHRxF_ZAxuaKYL7o,12
2
- plestysdk/cache.py,sha256=EvqvGCj00oQEzoKij7LyfIX0Her_U9nrezChkZ-u7EQ,3878
3
- plestysdk/cli.py,sha256=6IY-PvABpDxdl4f79QcvxdvzgErQByNv8acYlaj87ts,1123
4
- plestysdk/context.py,sha256=4OR7gXMMTG9mbs_yvEj365du23VtuVqbgDQmA_85ENA,5017
5
- plestysdk/assets/docs-build-gitlab-ci.yml,sha256=Ee-Iu1FymeGc7H93tVdBA-y5vJYNfCur4GVfEYNYd5s,185
6
- plestysdk/assets/ruff.toml,sha256=aluT6IyKKC66_Z8X4OCQ9ujE1-Cf_mrnVCWCoHMZFMM,222
7
- plestysdk/assets/logos/plesty_icon.png,sha256=yFrk2tQDGUzTTzRLdsbqUYwhe2YYa7VlXXfzZCUlIrk,18048
8
- plestysdk/assets/logos/plesty_logo.png,sha256=QgqgLl3s8s-NmrtjTHG650uCzJwblwHwI62nc5IG1bg,15218
9
- plestysdk/commands/check.py,sha256=xf_mVnece_KaGpmkJ_6oFXk9cdLPhayguSq7Ijp8zwk,5863
10
- plestysdk/commands/docs/__init__.py,sha256=R7SMCSGhQrm8UM5GqscqdhdjTOmYoP-Zt3MlP7pctkY,1380
11
- plestysdk/commands/docs/deploy.py,sha256=MJX97wKJKRJEn8_Ovj74Sr9VIirvZxfkyB5cuMjoB5c,8020
12
- plestysdk/commands/docs/serve.py,sha256=U_6CUtmZFoK3joWBD4maLyX-pDEn4o9ont3ae7SW8ds,2796
13
- plestysdk/commands/docs/sphinx_builder.py,sha256=DK1w8LAS36V5iKwz8JhSR15qGoecffF99djnutejofo,5392
14
- plesty_sdk-0.0.2.dev2.dist-info/METADATA,sha256=ocYElNE8o7bPko2QWYHRpK_Ux7GD_8k5Mm7oCNblWUM,2001
15
- plesty_sdk-0.0.2.dev2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
- plesty_sdk-0.0.2.dev2.dist-info/entry_points.txt,sha256=fNrx7o8Sf1lQfZJM-_uPYcPWbJMO94RAYh7z-DnGB5o,45
17
- plesty_sdk-0.0.2.dev2.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
18
- plesty_sdk-0.0.2.dev2.dist-info/licenses/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
19
- plesty_sdk-0.0.2.dev2.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
20
- plesty_sdk-0.0.2.dev2.dist-info/RECORD,,
plestysdk/__init__.py DELETED
@@ -1 +0,0 @@
1
- """Init."""
@@ -1,11 +0,0 @@
1
- stages: [deploy]
2
-
3
- pages:
4
- stage: deploy
5
- rules:
6
- - exists:
7
- - "html/latest/"
8
- script:
9
- - echo "Serving documentation on GitLab pages..."
10
- pages:
11
- publish: html/latest
Binary file
Binary file
@@ -1,11 +0,0 @@
1
- line-length = 99
2
- src = ["src"]
3
-
4
- [lint]
5
- # errors, pyflake, warnings, docstring (pydocstyle)
6
- select = ["E", "F", "W", "D"]
7
- # as before in the script
8
- ignore = ["E203", "D401", "D205"]
9
-
10
- [lint.pydocstyle]
11
- convention = "google"
plestysdk/cache.py DELETED
@@ -1,106 +0,0 @@
1
- """Cache management."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- from __future__ import annotations
21
- import os
22
- import shutil
23
- from collections.abc import Callable
24
- from importlib import metadata
25
- from typing import ClassVar
26
-
27
-
28
- class CachedFile:
29
- """Function decorator for defining cached (text) files with content generated at runtime.
30
- This class also serves as a registry of files to be cached.
31
- """
32
-
33
- registry: ClassVar[list[CachedFile]] = []
34
- """Registry of instanciated CachedFile objects."""
35
- filename: str
36
- """The path to the file to be cached, relative to the '.plesty' cache directory."""
37
- content: Callable[[], str]
38
- """The decorated function that generates the content of the cached file."""
39
-
40
- def __init__(self, filename: str) -> None:
41
- """Add the File object to the registry.
42
-
43
- Args:
44
- filename: The path to the file to be cached, relative to the '.plesty'
45
- cache directory.
46
- """
47
- self.filename = filename
48
- CachedFile.registry.append(self)
49
-
50
- def __call__(self, content: Callable[[], str]) -> None:
51
- """Apply the decorator to a function."""
52
- self.content = content
53
-
54
- def generate(self) -> None:
55
- """Write the content of the cached file to disk."""
56
- complete_file_path = os.path.join(os.getcwd(), '.plesty', self.filename)
57
- os.makedirs(os.path.dirname(complete_file_path), exist_ok=True)
58
- with open(complete_file_path, 'xt') as file:
59
- file.write(self.content())
60
-
61
- def is_correct(self) -> bool:
62
- """Check whether the cached file is up-to-date by comparing the generated content
63
- with the content of the existing file on disk.
64
-
65
- Returns:
66
- False, if the file does not exist on disk or its content is not up-to-date.
67
- True otherwise.
68
- """
69
- complete_file_path = os.path.join(os.getcwd(), '.plesty', self.filename)
70
- if not os.path.exists(complete_file_path):
71
- return False
72
- with open(complete_file_path, 'rt') as file:
73
- existing_content = file.read()
74
- if not existing_content == self.content():
75
- return False
76
- return True
77
-
78
-
79
- def ensure_cache() -> None:
80
- """Ensure that the cache directory '.plesty' exists and that all files are
81
- up-to-date. If not, (re)create the cache.
82
- """
83
- for file in CachedFile.registry:
84
- if not file.is_correct():
85
- break
86
- else:
87
- return
88
-
89
- cache_dir_path = os.path.join(os.getcwd(), '.plesty')
90
- if os.path.exists(cache_dir_path):
91
- shutil.rmtree(cache_dir_path)
92
- os.makedirs(cache_dir_path)
93
- for file in CachedFile.registry:
94
- file.generate()
95
-
96
-
97
- @CachedFile('.gitignore')
98
- def gitignore() -> str:
99
- """This file causes the entire '.plesty' cache directory to be ignored by version control."""
100
- return '*'
101
-
102
-
103
- @CachedFile('.sdk-version')
104
- def sdk_version() -> str:
105
- """This file specifies the version number of the Plesty SDK used when creating the cache."""
106
- return metadata.version('plesty-sdk')
plestysdk/cli.py DELETED
@@ -1,32 +0,0 @@
1
- """CLI tool."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import click
21
-
22
- from plesty_sdk.commands.check import check_command
23
- from plesty_sdk.commands.docs import docs_command
24
-
25
-
26
- @click.group("plesty")
27
- def app():
28
- """The Plesty CLI tool."""
29
-
30
-
31
- app.add_command(check_command)
32
- app.add_command(docs_command)
@@ -1,146 +0,0 @@
1
- """Check a module for plesty standard."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import asyncio
21
- import importlib
22
- import subprocess
23
- from importlib import resources
24
- from pathlib import Path
25
- from typing import Any, Literal
26
-
27
- import click
28
- from bluesky.protocols import Readable
29
- try:
30
- from PySide6.QtWidgets import QWidget
31
- PYSIDE6_IS_INSTALLED = True
32
- except ImportError:
33
- PYSIDE6_IS_INSTALLED = False
34
-
35
- from plesty_sdk.context import ApplicationContext, pass_context
36
-
37
-
38
- @click.command(name="check", short_help="Check compliance with the Plesty standard.")
39
- @click.option(
40
- "--standard",
41
- help="...",
42
- default="pixel",
43
- type=click.Choice(["pixel", "nebula", "quantum"]),
44
- show_choices=True,
45
- show_default=True,
46
- )
47
- @click.option("--device", help="...", is_flag=True)
48
- @pass_context
49
- def check_command(
50
- context: ApplicationContext, standard: Literal["pixel", "nebula", "quantum"], device: bool
51
- ):
52
- """..."""
53
- # ruff
54
- run_linter()
55
-
56
- check_gui(standard, context.pyproject_toml)
57
-
58
- if device:
59
- check_device(standard, context.pyproject_toml)
60
-
61
-
62
- def check_gui(standard: Literal["pixel", "nebula", "quantum"], pyproject_toml: dict[str, Any]):
63
- """Checks whether the module's GUI class, if present, is compatible with the standard."""
64
- click.echo("Checking GUI requirements...")
65
- project_name: str = pyproject_toml["project"]["name"]
66
- module = importlib.import_module(project_name.replace("-", "_"))
67
- try:
68
- gui_class = getattr(module, "GUI")
69
- except AttributeError:
70
- click.echo("Module does not implement a GUI.")
71
- return
72
- match standard:
73
- case "pixel":
74
- click.echo("Check passed, no GUI specific requirements in standard pixel.")
75
- case "nebula" | "quantum":
76
- if not PYSIDE6_IS_INSTALLED:
77
- raise click.ClickException(
78
- "PySide6 is not installed in the environment, but the GUI "
79
- "class is required to inherit from PySide6.QtWidgets.QtWidget "
80
- f"to be compatible with plesty standard {standard}."
81
- )
82
- if not issubclass(gui_class, QWidget):
83
- raise click.ClickException(
84
- "GUI class does not inherit from PySide6.QtWidgets.QtWidget "
85
- f"required by plesty standard {standard}."
86
- )
87
- try:
88
- if not callable(getattr(gui_class, "connect")):
89
- raise AttributeError
90
- except AttributeError:
91
- raise click.ClickException(
92
- "GUI class does not implement a connect method "
93
- f"required by plesty standard {standard}."
94
- )
95
- click.echo(f"Check passed for standard {standard}.")
96
-
97
-
98
- def check_device(standard: Literal["pixel", "nebula", "quantum"], pyproject_toml: dict[str, Any]):
99
- """Checks whether the device has a Bluesky compatible interface."""
100
- click.echo("Checking device specific requirements...")
101
- match standard:
102
- case "pixel":
103
- click.echo("Check passed, no device specific requirements in standard pixel.")
104
- case "nebula" | "quantum":
105
- device_class = pyproject_toml["tool"]["plesty"]["device-class"]
106
- project_name = pyproject_toml["project"]["name"]
107
- module = importlib.import_module(project_name.replace("-", "_"))
108
- device_instance = getattr(module, device_class)()
109
- # check for compatibility with bluesky Readable
110
- if not isinstance(device_instance, Readable):
111
- raise click.ClickException(
112
- "Device class does not implement the 'Readable' protocol from Bluesky required"
113
- f"by standard {standard}."
114
- )
115
- # check for async connect method
116
- if not asyncio.iscoroutinefunction(getattr(device_instance, "connect")):
117
- raise click.ClickException(
118
- "Device class does not implement an asyncronous 'connect' functions requried"
119
- f"by plesty standard {standard}."
120
- )
121
- click.echo(f"Check passed for standard {standard}.")
122
-
123
-
124
- def run_linter():
125
- """Run ruff linter.
126
-
127
- Prints errors if checks are not fulfilled and exits.
128
- Checks pyflakes, pycodestyle errors & warnings and pydocstyle.
129
- Independent of standard level, as it needs to be fulfilled for every level.
130
- """
131
- pkg_root = resources.files("plesty_sdk")
132
- config = pkg_root.joinpath("assets/ruff.toml")
133
-
134
- repo_root = str(Path.cwd())
135
- with resources.as_file(config) as config_path:
136
- commands = [
137
- ["ruff", "check", "--config", str(config_path), repo_root],
138
- ["ruff", "format", "--check", "--config", str(config_path), repo_root],
139
- ]
140
-
141
- for cmd in commands:
142
- p = subprocess.run(cmd, text=True, capture_output=True)
143
- print(p.stdout)
144
- print(p.stderr)
145
- if p.returncode != 0:
146
- raise SystemExit(p.returncode)
@@ -1,35 +0,0 @@
1
- """CLI commands for previewing and deploying Sphinx documentation pages."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import click
21
-
22
- from plesty_sdk.commands.docs.deploy import deploy_command
23
- from plesty_sdk.commands.docs.serve import serve_command
24
- from plesty_sdk.context import ApplicationContext, pass_context
25
-
26
-
27
- @click.group('docs')
28
- @pass_context
29
- def docs_command(context: ApplicationContext):
30
- """Preview and deploy documentation pages."""
31
- context.ensure_docs_index()
32
-
33
-
34
- docs_command.add_command(deploy_command)
35
- docs_command.add_command(serve_command)
@@ -1,193 +0,0 @@
1
- """The plesty docs deploy command."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import contextlib
21
- import json
22
- import os
23
- import shutil
24
- import subprocess
25
- from importlib import metadata, resources
26
- from packaging.version import InvalidVersion, Version
27
- from tempfile import TemporaryDirectory
28
-
29
- import click
30
-
31
- from plesty_sdk.commands.docs.sphinx_builder import build_sphinx_app
32
- from plesty_sdk.context import ApplicationContext, pass_context
33
-
34
-
35
- @click.command(
36
- name='deploy', short_help="Build and push documentation pages to a remote branch."
37
- )
38
- @click.argument('remote_url', required=True)
39
- @click.argument('target_branch', required=True)
40
- @click.option(
41
- "--release/--latest",
42
- is_flag=True,
43
- required=True,
44
- help="Whether to deploy the documentation pages for a "
45
- "release version or the latest development version.",
46
- )
47
- @pass_context
48
- def deploy_command(
49
- context: ApplicationContext, remote_url: str, target_branch: str, release: bool
50
- ):
51
- """Build and push the documentation pages for the Plesty project in the
52
- current working directory to the <TARGET_BRANCH> of the repository at
53
- <REMOTE_URL>. The branch is created if it does not yet exist.
54
- """
55
- if release:
56
- try:
57
- version_str = metadata.version(context.pyproject_toml['project']['name'])
58
- except metadata.PackageNotFoundError:
59
- try:
60
- version_str = context.pyproject_toml['project']['version']
61
- except KeyError:
62
- raise click.ClickException(
63
- "The project version could not be retrieved either from the build "
64
- "metadata - as the project is not installed in the current "
65
- "environment - or directly from the pyproject.toml file."
66
- )
67
- try:
68
- version = Version(version_str)
69
- except InvalidVersion:
70
- raise click.ClickException("The project version is not PEP 440 compliant.")
71
- version_tag = f'{version.major}.{version.minor}'
72
- build_dir = os.path.join('html', 'releases', version_tag)
73
- else:
74
- version_tag = None
75
- build_dir = os.path.join('html', 'latest')
76
-
77
- with TemporaryDirectory() as tmp_dir:
78
- with contextlib.chdir(tmp_dir):
79
- # check whether target branch exists on remote
80
- if run_git(
81
- ['ls-remote', '--exit-code', '--heads', remote_url, target_branch],
82
- expected_exit_codes={0, 2}
83
- ) == 0:
84
- click.echo(f"Branch {target_branch} exists on remote. Cloning...")
85
- run_git(['clone', '--branch', target_branch, '--depth', '1', remote_url, '.'])
86
- else:
87
- click.echo(
88
- f"Branch {target_branch} does not yet exist "
89
- "on remote. Creating orphan branch..."
90
- )
91
- run_git(['init'])
92
- run_git(['checkout', '--orphan', target_branch])
93
- with resources.as_file(
94
- resources.files('plesty_sdk').joinpath('assets', 'docs-build-gitlab-ci.yml')
95
- ) as gitlab_ci_yml:
96
- shutil.copyfile(gitlab_ci_yml, os.path.join(tmp_dir, '.gitlab-ci.yml'))
97
- run_git(['add', '.gitlab-ci.yml'])
98
- run_git(['commit', '--message', 'Initialize documentation branch.'])
99
-
100
- click.echo("Building documentation...")
101
- build_sphinx_app(
102
- context.docs_settings,
103
- version_tag,
104
- build_dir=os.path.join(tmp_dir, build_dir),
105
- delete_artifacts=True,
106
- )
107
- if version_tag is not None:
108
- update_versions_json(os.path.join(tmp_dir, 'html', 'releases'), version_tag)
109
-
110
- with contextlib.chdir(tmp_dir):
111
- click.echo("Analyzing changes...")
112
- run_git(['add', '--all'])
113
- if run_git(['diff', '--staged', '--quiet'], expected_exit_codes={0, 1}) == 0:
114
- click.echo("No changes made to the documentation pages.")
115
- else:
116
- commit_message = (
117
- f"Add or update documentation pages for version {version_tag}."
118
- ) if version_tag is not None else "Update latest documentation pages."
119
- run_git(['commit', '--message', commit_message])
120
- run_git(['push', remote_url, target_branch])
121
- click.echo("Changes to the documentation pages successfully pushed.")
122
-
123
-
124
- def run_git(args: list[str], expected_exit_codes: set[int] = {0}) -> int:
125
- """Run a git command in the current working directory.
126
-
127
- Args:
128
- args: The arguments and flags to be passed to the git command.
129
- expected_exit_codes: Exit codes that should not raise an exception.
130
-
131
- Returns:
132
- The exit code of the git command.
133
-
134
- Raises:
135
- click.ClickException: If the exit code is not in `expected_exit_codes`.
136
- """
137
- if shutil.which('git') is None:
138
- raise click.ClickException("Git is not installed or not found in PATH.")
139
- result = subprocess.run((command := ['git'] + args), capture_output=True)
140
- if result.returncode in expected_exit_codes:
141
- return result.returncode
142
- else:
143
- error_message = result.stderr.decode('utf-8', errors='replace')
144
- raise click.ClickException(f"Failed to run {' '.join(command)}.\n{error_message}")
145
-
146
-
147
- def version_tuple(version_tag: str) -> tuple[int, int]:
148
- """Parse a version tag into a tuple of the form (major, minor).
149
-
150
- Args:
151
- version_tag: The version tag to be parsed in 'major.minor' format.
152
-
153
- Returns:
154
- Version tuple of the form (major, minor) as integers.
155
- """
156
- major, minor = version_tag.split('.')
157
- return (int(major), int(minor))
158
-
159
-
160
- def update_versions_json(releases_dir: str, new_version_tag: str) -> None:
161
- """Update the versions.json file to include the newly built version in the correct order
162
- without duplicates. If necessary, update the automatic redirection to the newest version.
163
-
164
- Args:
165
- releases_dir: The directory containing the versions.json file.
166
- new_version_tag: The version tag to be added.
167
- """
168
- versions_json_path = os.path.join(releases_dir, 'versions.json')
169
- try:
170
- with open(versions_json_path, 'rt', encoding='utf-8') as file:
171
- version_list: list[dict[str, str]] = json.load(file)
172
- except FileNotFoundError:
173
- version_list = []
174
-
175
- new_version = version_tuple(new_version_tag)
176
- new_version_entry = {'version': new_version_tag, 'url': f'/{new_version_tag}/'}
177
- i = 0
178
- while True:
179
- if i == len(version_list):
180
- version_list.append(new_version_entry)
181
- break
182
- old_version = version_tuple(version_list[i]['version'])
183
- if new_version == old_version:
184
- break
185
- if new_version > old_version:
186
- version_list.insert(i, new_version_entry)
187
- break
188
- i += 1
189
-
190
- with open(versions_json_path, 'wt', encoding='utf-8') as file:
191
- json.dump(version_list, file, indent=4)
192
- with open(os.path.join(releases_dir, 'index.html'), 'wt') as file:
193
- file.write(f'<script>window.location.replace("{version_list[0]['url']}")</script>')
@@ -1,80 +0,0 @@
1
- """The plesty docs serve command."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import logging
21
- import os
22
- from threading import Thread
23
-
24
- import click
25
- import livereload
26
- import watchfiles
27
-
28
- from plesty_sdk.cache import ensure_cache
29
- from plesty_sdk.commands.docs.sphinx_builder import build_sphinx_app
30
- from plesty_sdk.context import ApplicationContext, pass_context
31
-
32
-
33
- logging.getLogger('livereload').disabled = True
34
- for name in ['tornado.access', 'tornado.application', 'tornado.general']:
35
- logging.getLogger(name).setLevel(logging.CRITICAL)
36
-
37
-
38
- @click.command(
39
- name='serve', short_help="Preview the documentation pages using a local webserver."
40
- )
41
- @click.option(
42
- '--dev-addr',
43
- default='localhost:8000',
44
- help="IP address and port",
45
- show_default=True,
46
- metavar="<IP:PORT>",
47
- )
48
- @pass_context
49
- def serve_command(context: ApplicationContext, dev_addr: str):
50
- """Start a local development webserver to preview the documentation pages
51
- for the Plesty project in the current working directory. Changes to the
52
- .md or .rst files are applied immediately via live reload.
53
- """
54
- ensure_cache()
55
- sphinx_app = build_sphinx_app(
56
- context.docs_settings,
57
- version_tag=None,
58
- build_dir=os.path.join('.plesty', 'docs-build'),
59
- delete_artifacts=False,
60
- )
61
-
62
- def watch_changes():
63
- for _ in watchfiles.watch(sphinx_app.srcdir):
64
- sphinx_app.build()
65
-
66
- watcher_thread = Thread(target=watch_changes, daemon=True)
67
- watcher_thread.start()
68
-
69
- server = livereload.Server()
70
- dev_host, dev_port = dev_addr.split(':')
71
- click.echo(f"Serving preview at http://{dev_host}:{dev_port}.")
72
- click.echo(
73
- f"Changes in ./{os.path.relpath(sphinx_app.srcdir)} "
74
- "are detected and immediately visible."
75
- )
76
- click.echo("Press Ctrl+C to stop the server.")
77
- server.serve(
78
- host=dev_host, port=int(dev_port), root=os.path.join('.plesty', 'docs-build')
79
- )
80
- click.echo("Server shut down.")
@@ -1,158 +0,0 @@
1
- """Sphinx app configuration and build process."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import os
21
- import shutil
22
- from datetime import date
23
- from importlib import resources
24
- from typing import Any
25
-
26
- from sphinx.application import Sphinx
27
-
28
- from plesty_sdk.context import DocumentationSettings
29
-
30
-
31
- def make_sphinx_config(
32
- settings: DocumentationSettings, version_tag: str | None, logo_dir: os.PathLike[str]
33
- ) -> dict[str, Any]:
34
- """Set up the Sphinx configuration dictionary customized for the project.
35
-
36
- Args:
37
- settings: The documentation settings.
38
- version_tag: The version for which the documentation pages are built.
39
- logo_dir: The directory containing the logo files.
40
-
41
- Returns:
42
- The Sphinx configuration dictionary.
43
- """
44
- config = dict[str, Any](
45
- project = settings.site_name,
46
- copyright = f"{date.today().year}, Plesty Development Team",
47
- extensions = [
48
- 'myst_parser',
49
- 'sphinx.ext.autodoc',
50
- 'sphinx.ext.napoleon',
51
- 'sphinxcontrib.mermaid',
52
- 'sphinx_design',
53
- 'sphinx_copybutton',
54
- ],
55
- source_suffix = {
56
- '.rst': 'restructuredtext',
57
- '.md': 'markdown',
58
- },
59
- # HTML theme
60
- html_theme = 'pydata_sphinx_theme',
61
- html_theme_options = {
62
- 'navbar_center': ['navbar-nav'],
63
- 'secondary_sidebar_items': ['page-toc', 'sourcelink'],
64
- 'footer_start': ['copyright'],
65
- 'footer_center': [],
66
- 'footer_end': ['theme-version'],
67
- 'logo': {
68
- 'text': settings.site_name,
69
- },
70
- },
71
- html_sidebars = {
72
- '*': [],
73
- },
74
- html_logo = os.path.join(logo_dir, 'plesty_logo.png'),
75
- html_favicon = os.path.join(logo_dir, 'plesty_icon.png'),
76
- # MyST configuration
77
- myst_enable_extensions = ['colon_fence'],
78
- myst_fence_as_directive = ['mermaid'],
79
- )
80
-
81
- if version_tag is not None:
82
- config['html_theme_options']['navbar_center'].insert(0, 'version-switcher')
83
- config['html_theme_options']['switcher'] = {
84
- 'json_url': '/versions.json',
85
- 'version_match': version_tag,
86
- }
87
-
88
- if settings.api_reference:
89
- project_root = os.getcwd()
90
- if not os.path.isdir(src_dir := os.path.join(project_root, 'src')):
91
- src_dir = project_root
92
-
93
- import_packages = [
94
- entry.path for entry in os.scandir(src_dir) if (
95
- entry.is_dir()
96
- and entry.name != "tests"
97
- and os.path.isfile(os.path.join(entry.path, '__init__.py'))
98
- )
99
- ]
100
-
101
- if import_packages:
102
- config['extensions'].append('autoapi.extension')
103
- config.update(
104
- autoapi_generate_api_docs = True,
105
- autoapi_add_toctree_entry = True,
106
- autoapi_root = 'reference',
107
- autoapi_dirs = import_packages,
108
- autoapi_python_class_content = 'both',
109
- napoleon_google_docstring = True,
110
- autodoc_typehints = 'both',
111
- )
112
-
113
- if settings.toc_file is not None:
114
- config['extensions'].append('sphinx_external_toc')
115
- config.update(
116
- suppress_warnings = ["etoc.toctree"],
117
- external_toc_path = settings.toc_file,
118
- )
119
-
120
- return config
121
-
122
-
123
- def build_sphinx_app(
124
- settings: DocumentationSettings,
125
- version_tag: str | None,
126
- build_dir: str,
127
- delete_artifacts: bool,
128
- ) -> Sphinx:
129
- """Build the Sphinx application.
130
-
131
- Args:
132
- settings: The documentation settings.
133
- version_tag: The version for which the documentation pages are built.
134
- build_dir: The location where the documentation pages are built.
135
- delete_artifacts: Whether to delete Sphinx build artifacts.
136
-
137
- Returns:
138
- The Sphinx application object.
139
- """
140
- if os.path.isdir(build_dir):
141
- shutil.rmtree(build_dir)
142
-
143
- with resources.as_file(resources.files('plesty_sdk').joinpath('assets', 'logos')) as logo_dir:
144
- app = Sphinx(
145
- srcdir=settings.docs_dir,
146
- confdir=None,
147
- outdir=build_dir,
148
- doctreedir=os.path.join(build_dir, '.doctrees'),
149
- buildername='html',
150
- confoverrides=make_sphinx_config(settings, version_tag, logo_dir),
151
- )
152
- app.build()
153
-
154
- if delete_artifacts:
155
- shutil.rmtree(os.path.join(build_dir, '.doctrees'))
156
- os.remove(os.path.join(build_dir, '.buildinfo'))
157
-
158
- return app
plestysdk/context.py DELETED
@@ -1,116 +0,0 @@
1
- """Cross-command context management."""
2
-
3
- # Copyright (C) 2025, 2026 Christopher Borchers, fvrehlinger, Maximilian
4
- # Heller, Michael Zopf (names in alphabetic order wrt. surnames)
5
- #
6
- # This file is part of plesty-sdk which is part of the Plesty library.
7
- #
8
- # plesty-sdk is free software: you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation, either version 3 of the License, or (at your option) any
11
- # later version.
12
- #
13
- # plesty-sdk is distributed in the hope that it will be useful, but WITHOUT ANY
14
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU Lesser General Public License along
18
- # with plesty-sdk. If not, see <https://www.gnu.org/licenses/>.
19
-
20
- import os
21
- import tomllib
22
- from typing import Any
23
-
24
- import click
25
-
26
-
27
- class DocumentationSettings:
28
- """Settings for generating the documentation pages,
29
- which can be configured in the pyproject.toml file.
30
- """
31
-
32
- site_name: str
33
- """The title of the website present on each page next to the Plesty logo."""
34
- docs_dir: str
35
- """The directory containing the .md or .rst files to generate the documentation from."""
36
- api_reference: bool
37
- """Whether to automatically generate API reference pages."""
38
- toc_file: str | None
39
- """The path to the external TOC file."""
40
-
41
- def __init__(self, pyproject_toml: dict[str, Any]) -> None:
42
- """Read the documentation settings specified by the user in the [tool.plesty.docs]
43
- section of the pyproject.toml file and set default values for missing settings.
44
-
45
- Args:
46
- pyproject_toml: The parsed content of the pyproject.toml file.
47
- """
48
- try:
49
- plesty_docs_section: dict[str, Any] = pyproject_toml['tool']['plesty']['docs']
50
- except KeyError:
51
- plesty_docs_section = {}
52
-
53
- self.site_name = plesty_docs_section.get('site-name', pyproject_toml['project']['name'])
54
- self.docs_dir = os.path.join(os.getcwd(), plesty_docs_section.get('docs-dir', 'docs'))
55
- self.api_reference = plesty_docs_section.get('api-reference', True)
56
-
57
- if (toc_file := plesty_docs_section.get('toc-file', 'toc.yaml')) == '':
58
- self.toc_file = None
59
- else:
60
- self.toc_file = os.path.join(self.docs_dir, toc_file)
61
-
62
-
63
- class ApplicationContext:
64
- """Container class for storing data shared across multiple commands."""
65
-
66
- pyproject_toml: dict[str, Any]
67
- """The parsed content of the pyproject.toml file in the current working directory."""
68
- docs_settings: DocumentationSettings
69
- """The documentation settings."""
70
-
71
- def __init__(self) -> None:
72
- """Parse the pyproject.toml file and read the documentation settings."""
73
- pyproject_toml_path = os.path.join(os.getcwd(), 'pyproject.toml')
74
- try:
75
- with open(pyproject_toml_path, 'rb') as file:
76
- self.pyproject_toml = tomllib.load(file)
77
- except FileNotFoundError:
78
- raise click.ClickException(
79
- "No pyproject.toml file found in the current "
80
- f"working directory at {pyproject_toml_path}."
81
- )
82
-
83
- self.docs_settings = DocumentationSettings(self.pyproject_toml)
84
-
85
- def ensure_docs_index(self) -> None:
86
- """Ensure that the docs directory exists and contains an index.md or index.rst
87
- file, as well as the external TOC file, if specified in the documentation settings.
88
-
89
- Raises:
90
- click.ClickException: If a required file is missing.
91
- """
92
- if not os.path.isdir(self.docs_settings.docs_dir):
93
- raise click.ClickException(
94
- f"No docs directory found at {self.docs_settings.docs_dir}. Create one or ensure "
95
- "that the correct docs directory is specified in the pyproject.toml file."
96
- )
97
- if not (
98
- os.path.isfile(os.path.join(self.docs_settings.docs_dir, 'index.md'))
99
- or os.path.isfile(os.path.join(self.docs_settings.docs_dir, 'index.rst'))
100
- ):
101
- raise click.ClickException(
102
- "No index.md or index.rst master file found in the docs "
103
- "directory (entry point for the documentation generation)."
104
- )
105
- if not (
106
- self.docs_settings.toc_file is None or os.path.isfile(self.docs_settings.toc_file)
107
- ):
108
- raise click.ClickException(
109
- f"No external TOC file found at {self.docs_settings.toc_file}. Create one or "
110
- "ensure that the correct path is specified in the pyproject.toml file. If you "
111
- "do not want to use an external table of contents, specify an empty string."
112
- )
113
-
114
-
115
- pass_context = click.make_pass_decorator(ApplicationContext, ensure=True)
116
- """Decorator to pass the application context to a Click command as the first argument."""