diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md index 309cf19..cfc7b05 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ This provides decorators and collecting of decorated code, formatting it and writing to yaml file. +## Requirements + +- Python >= 3.13 + ## Installation The package name is `reqstool-python-decorators`. diff --git a/pyproject.toml b/pyproject.toml index 83060db..0c1ab4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dynamic = ["version"] authors = [{ name = "reqstool" }] description = "Reqstool Python Decorators" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.13" classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", @@ -48,7 +48,7 @@ dependencies = [ [tool.black] line-length = 120 -target-version = ['py310'] +target-version = ['py313'] [tool.flake8] ignore = ["W503"] diff --git a/src/reqstool_python_decorators/decorators/decorators.py b/src/reqstool_python_decorators/decorators/decorators.py index 945aa09..7a9ce0c 100644 --- a/src/reqstool_python_decorators/decorators/decorators.py +++ b/src/reqstool_python_decorators/decorators/decorators.py @@ -1,17 +1,19 @@ # Copyright © LFV +from collections.abc import Callable -def Requirements(*requirements): - def decorator(func): - func.requirements = requirements + +def Requirements[T: Callable](*requirements: str) -> Callable[[T], T]: + def decorator(func: T) -> T: + func.requirements = requirements # type: ignore[attr-defined] return func return decorator -def SVCs(*svc_ids): - def decorator(func): - func.svc_ids = svc_ids +def SVCs[T: Callable](*svc_ids: str) -> Callable[[T], T]: + def decorator(func: T) -> T: + func.svc_ids = svc_ids # type: ignore[attr-defined] return func return decorator diff --git a/src/reqstool_python_decorators/processors/decorator_processor.py b/src/reqstool_python_decorators/processors/decorator_processor.py index 9f81d21..b53c3b5 100644 --- a/src/reqstool_python_decorators/processors/decorator_processor.py +++ b/src/reqstool_python_decorators/processors/decorator_processor.py @@ -2,6 +2,7 @@ from enum import Enum, unique import os +from typing import ReadOnly, TypedDict from ruamel.yaml import YAML import ast @@ -19,6 +20,35 @@ def get_from_to(self): return f"from: {self.from_value}, to: {self.to_value}" +class DecoratorInfo(TypedDict): + name: ReadOnly[str] + args: ReadOnly[list[str]] + + +class ElementResult(TypedDict): + fullyQualifiedName: ReadOnly[str] + elementKind: ReadOnly[str] + name: ReadOnly[str] + decorators: ReadOnly[list[DecoratorInfo]] + + +class FormattedEntry(TypedDict): + elementKind: str + fullyQualifiedName: str + + +class RequirementAnnotations(TypedDict): + implementations: dict[str, list[FormattedEntry]] + tests: dict[str, list[FormattedEntry]] + + +class FormattedData(TypedDict): + requirement_annotations: RequirementAnnotations + + +type Results = list[ElementResult] + + class DecoratorProcessor: """ A class for collecting and processing Requirements and SVCs annotations on functions and classes in a directory. @@ -47,7 +77,7 @@ def __init__(self, *args, **kwargs): """ super().__init__(*args, **kwargs) - self.req_svc_results = [] + self.req_svc_results: Results = [] def find_python_files(self, directory: str | os.PathLike) -> list[str]: """ @@ -86,7 +116,7 @@ def get_functions_and_classes(self, file_path: str | os.PathLike, decorator_name for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): - decorators_info = [] + decorators_info: list[DecoratorInfo] = [] for decorator_node in getattr(node, "decorator_list", []): if isinstance(decorator_node, ast.Call) and isinstance(decorator_node.func, ast.Name): decorator_name = decorator_node.func.id @@ -132,7 +162,7 @@ def map_type(self, input_str) -> str: mapping = {item.from_value: item.to_value for item in DECORATOR_TYPES} return mapping.get(input_str, input_str) - def format_results(self, results) -> dict: + def format_results(self, results: Results) -> FormattedData: """ Format the collected results into a structured data format for YAML. @@ -140,17 +170,17 @@ def format_results(self, results) -> dict: - `results` (list): List of dictionaries containing information about functions and classes. Returns: - - `formatted_data` (dict): Formatted data in a structured `yaml_language_server` compatible format. + - `formatted_data` (FormattedData): Formatted data in a structured `yaml_language_server` compatible format. This function formats a list of decorated data into the structure required by the `yaml_language_server`. It includes version information, requirement annotations, and relevant element information. """ - formatted_data = {} - implementations = {} - tests = {} - requirement_annotations = {"implementations": implementations, "tests": tests} - formatted_data["requirement_annotations"] = requirement_annotations + implementations: dict[str, list[FormattedEntry]] = {} + tests: dict[str, list[FormattedEntry]] = {} + formatted_data: FormattedData = { + "requirement_annotations": {"implementations": implementations, "tests": tests} + } for result in results: for decorator_info in result["decorators"]: