Skip to content

Commit 873477a

Browse files
committed
Change PluginInfo and add some type annotation
Add some new properties sdk_version, python_verion for plugins and add some type annotations.
1 parent 5063d9f commit 873477a

14 files changed

Lines changed: 107 additions & 49 deletions

File tree

hookman/__main__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,17 @@ def generate_project_files(specs_path, dst_path):
4444
@click.argument("plugin_id")
4545
@click.argument("author_name")
4646
@click.argument("author_email")
47+
@click.argument("sdk_version")
48+
@click.argument("python_version")
4749
@click.option("--dst-path", default="./", help="Path to where the files will be written")
4850
def generate_plugin_template(
4951
specs_path: str,
5052
caption: str,
5153
plugin_id: str,
5254
author_email: str,
5355
author_name: str,
56+
sdk_version: str,
57+
python_version: str,
5458
dst_path: Path,
5559
):
5660
"""
@@ -61,6 +65,8 @@ def generate_plugin_template(
6165
PLUGIN_ID A unique string to identify the plugin.
6266
AUTHOR_NAME Name of the plugin author to be displayed.
6367
AUTHOR_EMAIL Email of the plugin author to be displayed.
68+
SDK_VERSION The SDK version to be used by the plugin.
69+
PYHOP_VERSION The Python version to be used by the plugin.
6470
6571
"""
6672
hm_generator = HookManGenerator(hook_spec_file_path=specs_path)
@@ -69,6 +75,8 @@ def generate_plugin_template(
6975
plugin_id=plugin_id,
7076
author_email=author_email,
7177
author_name=author_name,
78+
sdk_version=sdk_version,
79+
python_version=python_version,
7280
dst_path=Path(dst_path),
7381
)
7482
return 0

hookman/hookman_generator.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ def generate_plugin_template(
143143
plugin_id: str,
144144
author_email: str,
145145
author_name: str,
146+
sdk_version: str,
147+
python_version: str,
146148
dst_path: Path,
147149
extra_includes: Optional[List[str]] = None,
148150
extra_body_lines: Optional[List[str]] = None,
@@ -203,7 +205,7 @@ def generate_plugin_template(
203205
self._plugin_cmake_file_content(plugin_id)
204206
)
205207
Path(assets_folder / "plugin.yaml").write_text(
206-
self._plugin_config_file_content(caption, plugin_id, author_email, author_name, extras)
208+
self._plugin_config_file_content(caption, plugin_id, sdk_version, python_version, author_email, author_name, extras)
207209
)
208210
Path(assets_folder / "README.md").write_text(
209211
self._readme_content(caption, author_email, author_name)
@@ -382,21 +384,21 @@ def _validate_package_folder(self, artifacts_dir, assets_dir):
382384
if not assets_dir.joinpath("CHANGELOG.rst").is_file():
383385
raise FileNotFoundError(f"Unable to locate the file CHANGELOG.rst in {assets_dir}")
384386

385-
def _validate_plugin_config_file(self, plugin_config_file: Path):
387+
def _validate_plugin_config_file(self, plugin_config_file: Path) -> None:
386388
"""
387389
Check if the given plugin_file is valid, by creating a instance of PluginInfo.
388390
All checks are made in the __init__
389391
"""
390392
plugin_file_content = PluginInfo(plugin_config_file, hooks_available=None)
391-
semantic_version_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)") # Ex.: 1.0.0
393+
semantic_version_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)") # Ex.: 1.0.0 or 2025.1.0
392394
version = semantic_version_re.match(plugin_file_content.version)
393395

394396
if not version:
395397
raise ValueError(
396-
f"Version attribute does not follow semantic version, got {plugin_file_content.version!r}"
398+
f"Version attribute does not follow the calendar or semantic versioning, got {plugin_file_content.version!r}"
397399
)
398400

399-
def _hook_specs_header_content(self, plugin_id) -> str:
401+
def _hook_specs_header_content(self, plugin_id: str) -> str:
400402
"""
401403
Create a C header file with the content informed on the hook_specs
402404
"""
@@ -634,6 +636,8 @@ def _plugin_config_file_content(
634636
self,
635637
caption: str,
636638
plugin_id: str,
639+
sdk_version: str,
640+
python_version: str,
637641
author_email: str,
638642
author_name: str,
639643
extras: dict,
@@ -647,6 +651,8 @@ def _plugin_config_file_content(
647651
caption: '{caption}'
648652
email: '{author_email}'
649653
id: '{plugin_id}'
654+
sdk_version: '{sdk_version}'
655+
python_version: '{python_version}'
650656
version: '1.0.0'
651657
"""
652658
)

hookman/plugin_config.py

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import ctypes
22
import sys
33
from pathlib import Path
4-
from typing import List
4+
from typing import List, Sequence, OrderedDict
55
from zipfile import ZipFile
66

7-
import attr
8-
from attr import attrib
7+
from attr import define, field
98
from strictyaml import Map
109
from strictyaml import MapPattern
1110
from strictyaml import Optional
@@ -21,60 +20,62 @@
2120
"author": Str(),
2221
"email": Str(),
2322
"id": Str(),
23+
"sdk_version": Str(),
24+
"python_version": Str(),
2425
Optional("extras"): MapPattern(Str(), Str()),
2526
}
2627
)
2728

2829

29-
@attr.s
30-
class PluginInfo(object):
30+
@define
31+
class PluginInfo:
3132
"""
3233
Class that holds all information related to the plugin with some auxiliary methods
3334
"""
3435

35-
yaml_location = attrib(type=Path)
36-
hooks_available = attrib(validator=attr.validators.optional(attr.validators.instance_of(dict)))
37-
38-
author = attrib(type=str, init=False)
39-
description = attrib(type=str, default="Could not find a description", init=False)
40-
email = attrib(type=str, init=False)
41-
hooks_implemented = attrib(type=list, init=False)
42-
caption = attrib(type=str, init=False)
43-
shared_lib_name = attrib(type=str, init=False)
44-
shared_lib_path = attrib(type=Path, init=False)
45-
version = attrib(type=str, init=False)
46-
extras = attrib(attr.Factory(dict), init=False)
47-
48-
def __attrs_post_init__(self):
36+
yaml_location: Path
37+
hooks_available: dict | None = None
38+
39+
description: str = ""
40+
author: str = ""
41+
email: str = ""
42+
hooks_implemented: Sequence[str] = []
43+
caption: str = ""
44+
shared_lib_name: str = ""
45+
shared_lib_path: Path = field(init=False)
46+
version: str = ""
47+
sdk_version: str = ""
48+
python_version: str = ""
49+
extras: dict = {}
50+
id: str = ""
51+
52+
def __attrs_post_init__(self) -> None:
4953
plugin_config_file_content = self._load_yaml_file(
5054
self.yaml_location.read_text(encoding="utf-8")
5155
)
5256

5357
name = plugin_config_file_content["id"]
5458
shared_lib_name = f"{name}.dll" if sys.platform == "win32" else f"lib{name}.so"
5559

56-
object.__setattr__(self, "shared_lib_name", shared_lib_name)
57-
object.__setattr__(
58-
self, "shared_lib_path", self.yaml_location.parents[1] / "artifacts" / shared_lib_name
59-
)
60-
61-
object.__setattr__(self, "author", plugin_config_file_content["author"])
62-
object.__setattr__(self, "caption", plugin_config_file_content["caption"])
63-
object.__setattr__(self, "email", plugin_config_file_content["email"])
64-
object.__setattr__(self, "version", plugin_config_file_content["version"])
65-
object.__setattr__(self, "extras", plugin_config_file_content.get("extras", {}))
60+
self.shared_lib_name = shared_lib_name
61+
self.shared_lib_path = self.yaml_location.parents[1] / "artifacts" / shared_lib_name
62+
self.author = plugin_config_file_content["author"]
63+
self.caption = plugin_config_file_content["caption"]
64+
self.email = plugin_config_file_content["email"]
65+
self.version = plugin_config_file_content["version"]
66+
self.sdk_version = plugin_config_file_content["sdk_version"]
67+
self.python_version = plugin_config_file_content["python_version"]
68+
self.extras = plugin_config_file_content.get("extras", {})
6669

6770
# The id bellow guarantee to me that the plugin_id to be used in the application was not changed by a config file.
68-
object.__setattr__(
69-
self, "id", self._get_plugin_id_from_dll(plugin_config_file_content["id"])
70-
)
71+
self.id = self._get_plugin_id_from_dll(plugin_config_file_content["id"])
72+
73+
readme_file = self.yaml_location.parent / "README.md"
74+
self.description = readme_file.read_text(encoding="utf-8") if readme_file.is_file() else "Could not find a description"
7175

7276
if not self.hooks_available is None:
73-
object.__setattr__(self, "hooks_implemented", self._get_hooks_implemented())
77+
self.hooks_implemented = self._get_hooks_implemented()
7478

75-
readme_file = self.yaml_location.parent / "README.md"
76-
if readme_file.exists():
77-
object.__setattr__(self, "description", readme_file.read_text())
7879

7980
def _check_if_shared_lib_exists(self):
8081
if not self.shared_lib_path.is_file():
@@ -95,11 +96,14 @@ def _get_plugin_id_from_dll(self, plugin_id_from_plugin_yaml: str) -> str:
9596
raise RuntimeError(msg)
9697
return plugin_id_from_shared_lib
9798

98-
def _get_hooks_implemented(self) -> List[str]:
99+
def _get_hooks_implemented(self) -> Sequence[str]:
99100
"""
100101
Return a list of which hooks from "hooks_available" the shared library implements
101102
"""
102103
self._check_if_shared_lib_exists()
104+
if self.hooks_available is None:
105+
return []
106+
103107
with load_shared_lib(str(self.shared_lib_path)) as plugin_dll:
104108
hooks_implemented = [
105109
hook_name
@@ -124,7 +128,7 @@ def is_implemented_on_plugin(cls, plugin_dll: ctypes.CDLL, hook_name: str) -> bo
124128
return True
125129

126130
@classmethod
127-
def _load_yaml_file(cls, yaml_content):
131+
def _load_yaml_file(cls, yaml_content: str) -> OrderedDict:
128132
import strictyaml
129133

130134
plugin_config_file_content = strictyaml.load(yaml_content, PLUGIN_CONFIG_SCHEMA).data
@@ -139,7 +143,7 @@ def _load_yaml_file(cls, yaml_content):
139143
return plugin_config_file_content
140144

141145
@classmethod
142-
def validate_plugin_file(cls, plugin_file_zip: ZipFile):
146+
def validate_plugin_file(cls, plugin_file_zip: ZipFile) -> None:
143147
"""
144148
Check if the given plugin_file is valid,
145149
currently the only check that this method do is to verify if the id is available

tests/plugins/acme/simple_plugin/assets/plugin.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ version: '1.0.0'
33

44
author: 'simple_plugin_author'
55
email: 'simple_plugin_author@simple_plugin_author.com'
6-
76
id: 'simple_plugin'
7+
sdk_version: '>= 1.2.0'
8+
python_version: '>= 3.10'

tests/plugins/acme/simple_plugin_2/assets/plugin.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
caption: 'Simple Plugin 2'
22
version: '1.0.0'
3+
sdk_version: '>= 1.2.0'
4+
python_version: '>= 3.10'
35

46
author: 'simple_plugin_2_author'
57
email: 'simple_plugin_2_author@simple_plugin_2_author.com'

tests/test_cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def test_generate_plugin_template(datadir):
4949
"my_plugin",
5050
"Jonh",
5151
"jonh@somewhere",
52+
">=1.2.0",
53+
">=3.10",
5254
"--dst-path",
5355
datadir,
5456
],
@@ -82,6 +84,8 @@ def test_package_plugin(datadir, mock_plugin_id_from_dll):
8284
"My Plugin",
8385
"my_plugin",
8486
"Jonh",
87+
">=1.2.0",
88+
">=3.10",
8589
"jonh@somewhere",
8690
"--dst-path",
8791
datadir,

tests/test_hookman_generator.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def test_generate_plugin_template(datadir, file_regression):
4646
hg.generate_plugin_template(
4747
caption="Acme",
4848
plugin_id="acme",
49+
sdk_version= '>= 1.2.0',
50+
python_version='>= 3.10',
4951
author_name="FOO",
5052
author_email="FOO@FOO.com",
5153
dst_path=plugin_dir,
@@ -98,6 +100,8 @@ def test_generate_plugin_template_source_content_with_extra_includes(datadir, fi
98100
caption="Acme",
99101
plugin_id="acme",
100102
author_name="FOO",
103+
sdk_version='>= 1.2.0',
104+
python_version='>= 3.10',
101105
author_email="FOO@FOO.com",
102106
dst_path=plugin_dir,
103107
extra_includes=["<my_sdk/sdk.h>"],
@@ -123,6 +127,8 @@ def test_generate_plugin_template_source_content_with_default_impls(datadir, fil
123127
caption="Acme",
124128
plugin_id="acme",
125129
author_name="FOO",
130+
sdk_version='>= 1.2.0',
131+
python_version='>= 3.10',
126132
author_email="FOO@FOO.com",
127133
dst_path=plugin_dir,
128134
extra_body_lines=extra_body_lines,
@@ -177,6 +183,8 @@ def test_generate_plugin_package_invalid_shared_lib_name(acme_hook_specs_file, t
177183
plugin_id="acm#e",
178184
author_email="acme1",
179185
author_name="acme2",
186+
sdk_version='>= 1.2.0',
187+
python_version='>= 3.10',
180188
dst_path=Path(tmpdir),
181189
)
182190

@@ -186,6 +194,8 @@ def test_generate_plugin_package_invalid_shared_lib_name(acme_hook_specs_file, t
186194
plugin_id="acm e",
187195
author_email="acme1",
188196
author_name="acme2",
197+
sdk_version='>= 1.2.0',
198+
python_version='>= 3.10',
189199
dst_path=Path(tmpdir),
190200
)
191201

@@ -195,6 +205,8 @@ def test_generate_plugin_package_invalid_shared_lib_name(acme_hook_specs_file, t
195205
plugin_id="acm e",
196206
author_email="acme1",
197207
author_name="acme2",
208+
sdk_version='>= 1.2.0',
209+
python_version='>= 3.10',
198210
dst_path=Path(tmpdir),
199211
)
200212

@@ -210,6 +222,8 @@ def test_generate_plugin_package(
210222
plugin_id="acme",
211223
author_email="acme1",
212224
author_name="acme2",
225+
sdk_version=">= 1.2.0",
226+
python_version=">= 3.10",
213227
dst_path=Path(tmpdir),
214228
extras={"key": "override", "key2": "value2"},
215229
)
@@ -276,6 +290,8 @@ def test_generate_plugin_package(
276290
caption: acme
277291
email: acme1
278292
id: acme
293+
sdk_version: '>= 1.2.0'
294+
python_version: '>= 3.10'
279295
version: 1.0.0
280296
extras:
281297
key: override
@@ -336,6 +352,8 @@ def test_generate_plugin_package_with_missing_folders(acme_hook_specs_file, tmpd
336352
f"""\
337353
caption: 'ACME'
338354
version: '1.0.0'
355+
sdk_version: '>= 1.2.0'
356+
python_version: '>= 3.10'
339357
340358
author: 'acme_author'
341359
email: 'acme_email'
@@ -387,7 +405,7 @@ def test_generate_plugin_package_invalid_version(
387405
):
388406
hg = HookManGenerator(hook_spec_file_path=acme_hook_specs_file)
389407
plugin_id = "acme"
390-
hg.generate_plugin_template(plugin_id, plugin_id, "acme1", "acme2", tmp_path)
408+
hg.generate_plugin_template(plugin_id, plugin_id,">=1.2.0",">=3.10", "acme1", "acme2", tmp_path)
391409

392410
plugin_yaml = tmp_path / "acme/assets/plugin.yaml"
393411
new_content = plugin_yaml.read_text().replace("version: '1.0.0'", "version: '1'")
@@ -398,6 +416,6 @@ def test_generate_plugin_package_invalid_version(
398416
)
399417

400418
with pytest.raises(
401-
ValueError, match="Version attribute does not follow semantic version, got '1'"
419+
ValueError, match="Version attribute does not follow the calendar or semantic versioning, got '1'"
402420
):
403421
hg.generate_plugin_package(plugin_id, plugin_dir=tmp_path / plugin_id)

tests/test_hookman_generator/generate_plugin.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ author: 'FOO'
22
caption: 'Acme'
33
email: 'FOO@FOO.com'
44
id: 'acme'
5+
sdk_version: '>= 1.2.0'
6+
python_version: '>= 3.10'
57
version: '1.0.0'

tests/test_hookman_utils/bar/plugin.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
caption: 'Hookman Utils'
22
version: '1.0.0'
3+
sdk_version: '>= 1.2.0'
4+
python_version: '>= 3.10'
35

46
author: 'test_hookman_utils'
57
email: 'test_hookman_utils@test_hookman_utils'

tests/test_hookman_utils/foo/plugin.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
caption: 'Hookman Utils'
22
version: '1.0.0'
3+
sdk_version: '>= 1.2.0'
4+
python_version: '>= 3.10'
35

46
author: 'test_hookman_utils'
57
email: 'test_hookman_utils@test_hookman_utils'

0 commit comments

Comments
 (0)