quickpub 4.1.4__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.
Files changed (58) hide show
  1. quickpub/__init__.py +10 -0
  2. quickpub/__main__.py +276 -0
  3. quickpub/classifiers.py +138 -0
  4. quickpub/dist_files.py +33 -0
  5. quickpub/enforcers.py +26 -0
  6. quickpub/files.py +246 -0
  7. quickpub/logging_.py +74 -0
  8. quickpub/package_manager_detection.py +43 -0
  9. quickpub/proxy.py +35 -0
  10. quickpub/py.typed +0 -0
  11. quickpub/qa.py +409 -0
  12. quickpub/strategies/__init__.py +8 -0
  13. quickpub/strategies/build_schema.py +21 -0
  14. quickpub/strategies/constraint_enforcer.py +17 -0
  15. quickpub/strategies/implementations/__init__.py +6 -0
  16. quickpub/strategies/implementations/build_schemas/__init__.py +2 -0
  17. quickpub/strategies/implementations/build_schemas/setuptools_build_schema.py +48 -0
  18. quickpub/strategies/implementations/build_schemas/uv_build_schema.py +50 -0
  19. quickpub/strategies/implementations/constraint_enforcers/__init__.py +5 -0
  20. quickpub/strategies/implementations/constraint_enforcers/license_enforcer.py +29 -0
  21. quickpub/strategies/implementations/constraint_enforcers/local_version_enforcer.py +80 -0
  22. quickpub/strategies/implementations/constraint_enforcers/pypi_remote_version_enforcer.py +88 -0
  23. quickpub/strategies/implementations/constraint_enforcers/pypirc_enforcer.py +45 -0
  24. quickpub/strategies/implementations/constraint_enforcers/readme_enforcer.py +29 -0
  25. quickpub/strategies/implementations/package_managers/__init__.py +2 -0
  26. quickpub/strategies/implementations/package_managers/installed_package_parser.py +29 -0
  27. quickpub/strategies/implementations/package_managers/pip_package_manager.py +31 -0
  28. quickpub/strategies/implementations/package_managers/uv_package_manager.py +45 -0
  29. quickpub/strategies/implementations/python_providers/__init__.py +4 -0
  30. quickpub/strategies/implementations/python_providers/conda_python_provider.py +54 -0
  31. quickpub/strategies/implementations/python_providers/default_python_provider.py +47 -0
  32. quickpub/strategies/implementations/python_providers/union_provider.py +52 -0
  33. quickpub/strategies/implementations/python_providers/uv_python_provider.py +39 -0
  34. quickpub/strategies/implementations/quality_assurance_runners/__init__.py +4 -0
  35. quickpub/strategies/implementations/quality_assurance_runners/mypy_qa_runner.py +98 -0
  36. quickpub/strategies/implementations/quality_assurance_runners/pylint_qa_runner.py +106 -0
  37. quickpub/strategies/implementations/quality_assurance_runners/pytest_qa_runner.py +188 -0
  38. quickpub/strategies/implementations/quality_assurance_runners/unittest_qa_runner.py +138 -0
  39. quickpub/strategies/implementations/upload_targets/__init__.py +2 -0
  40. quickpub/strategies/implementations/upload_targets/github_upload_target.py +61 -0
  41. quickpub/strategies/implementations/upload_targets/pypirc_upload_target.py +90 -0
  42. quickpub/strategies/package_manager.py +54 -0
  43. quickpub/strategies/python_provider.py +60 -0
  44. quickpub/strategies/quality_assurance_runner.py +208 -0
  45. quickpub/strategies/quickpub_strategy.py +14 -0
  46. quickpub/strategies/upload_target.py +19 -0
  47. quickpub/structures/__init__.py +3 -0
  48. quickpub/structures/bound.py +52 -0
  49. quickpub/structures/dependency.py +82 -0
  50. quickpub/structures/version.py +41 -0
  51. quickpub/validators.py +87 -0
  52. quickpub/worker_pool.py +16 -0
  53. quickpub-4.1.4.dist-info/METADATA +246 -0
  54. quickpub-4.1.4.dist-info/RECORD +58 -0
  55. quickpub-4.1.4.dist-info/WHEEL +5 -0
  56. quickpub-4.1.4.dist-info/entry_points.txt +2 -0
  57. quickpub-4.1.4.dist-info/licenses/LICENSE +21 -0
  58. quickpub-4.1.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,60 @@
1
+ import logging
2
+ from abc import abstractmethod
3
+ from typing import Tuple, Set, List, AsyncIterator
4
+
5
+ from .quickpub_strategy import QuickpubStrategy
6
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class PythonProvider(AsyncIterator, QuickpubStrategy):
12
+ """Base class for Python environment providers. Subclass this to define custom Python environment management strategies."""
13
+
14
+ def __init__(
15
+ self,
16
+ auto_install_dependencies: bool = True,
17
+ *,
18
+ requested_envs: List[str],
19
+ explicit_versions: List[str],
20
+ exit_on_fail: bool = False,
21
+ ) -> None:
22
+ self.auto_install_dependencies = auto_install_dependencies
23
+ self.requested_envs = requested_envs
24
+ self.explicit_versions = explicit_versions
25
+ self.exit_on_fail = exit_on_fail
26
+ self.aiter_index = 0
27
+
28
+ def __aiter__(self) -> AsyncIterator[Tuple[str, AsyncLayeredCommand]]:
29
+ self.aiter_index = 0
30
+ return self
31
+
32
+ @abstractmethod
33
+ async def __anext__(self) -> Tuple[str, AsyncLayeredCommand]: ...
34
+
35
+ async def _get_available_envs(self) -> Set[str]:
36
+ KEY = "__available_envs__"
37
+ if (res := getattr(self, KEY, None)) is not None:
38
+ logger.debug(
39
+ "Using cached available environments for %s", self.__class__.__name__
40
+ )
41
+ return res
42
+
43
+ logger.debug("Fetching available environments for %s", self.__class__.__name__)
44
+ setattr(self, KEY, res := await self._get_available_envs_impl())
45
+ logger.debug(
46
+ "Found %d available environments for %s", len(res), self.__class__.__name__
47
+ )
48
+ return res
49
+
50
+ @abstractmethod
51
+ async def _get_available_envs_impl(self) -> Set[str]: ...
52
+
53
+ def __len__(self) -> int:
54
+ return len(self.requested_envs)
55
+
56
+ @abstractmethod
57
+ def get_python_executable_name(self) -> str: ...
58
+
59
+
60
+ __all__ = ["PythonProvider"]
@@ -0,0 +1,208 @@
1
+ import logging
2
+ import subprocess
3
+ import sys
4
+ import time
5
+ from abc import abstractmethod
6
+ from typing import Union, List, Optional, cast, Dict, Tuple, TYPE_CHECKING
7
+ from danielutils import LayeredCommand, file_exists
8
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
9
+
10
+ from quickpub import Bound
11
+
12
+ if TYPE_CHECKING:
13
+ from .package_manager import PackageManager
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Configurable:
19
+ @property
20
+ def has_config(self) -> bool:
21
+ return self.config_path is not None
22
+
23
+ def __init__(self, config_path: Optional[str] = None):
24
+ self.config_path = config_path
25
+ if self.has_config:
26
+ logger.debug("Using configuration file: %s", self.config_path)
27
+ if not file_exists(self.config_path):
28
+ logger.error("Configuration file not found: %s", self.config_path)
29
+ raise FileNotFoundError(f"Can't find config file {self.config_path}")
30
+
31
+
32
+ class HasOptionalExecutable:
33
+ PYTHON: str = sys.executable
34
+
35
+ @property
36
+ def use_executable(self) -> bool:
37
+ return self.executable_path is not None
38
+
39
+ def __init__(self, name: str, executable_path: Optional[str] = None) -> None:
40
+ self.name = name
41
+ self.executable_path = executable_path
42
+ if self.use_executable:
43
+ logger.debug("Using custom executable: %s", self.executable_path)
44
+ if not file_exists(self.executable_path):
45
+ logger.error("Executable not found: %s", self.executable_path)
46
+ raise FileNotFoundError(f"Executable not found {self.executable_path}")
47
+ else:
48
+ logger.debug("Using system executable for: %s", name)
49
+
50
+ def get_executable(self, use_system_interpreter: bool = False) -> str:
51
+ if self.use_executable:
52
+ return cast(str, self.executable_path)
53
+
54
+ p = self.PYTHON
55
+ if use_system_interpreter:
56
+ p = sys.executable
57
+ return f"{p} -m {self.name}"
58
+
59
+
60
+ SPEICLA_EXIT_CODES: Dict[int, Tuple[str, str]] = {
61
+ -1073741515: (
62
+ "Can't find python in path.",
63
+ "Executing command '{command}' failed with exit code {ret} which in hex is {hex} which corresponds to STATUS_DLL_NOT_FOUND",
64
+ ),
65
+ 3221225781: (
66
+ "Can't find python in path.",
67
+ "Executing command '{command}' failed with exit code {ret} which in hex is {hex} which corresponds to STATUS_DLL_NOT_FOUND",
68
+ ),
69
+ }
70
+
71
+
72
+ class QualityAssuranceRunner(Configurable, HasOptionalExecutable):
73
+ def __init__(
74
+ self,
75
+ *,
76
+ name: str,
77
+ bound: Union[str, Bound],
78
+ target: Optional[str] = None,
79
+ configuration_path: Optional[str] = None,
80
+ executable_path: Optional[str] = None,
81
+ package_manager: Optional["PackageManager"] = None,
82
+ ) -> None:
83
+ from .package_manager import PackageManager as PackageManagerType
84
+ from .implementations.package_managers.pip_package_manager import (
85
+ PipPackageManager,
86
+ )
87
+
88
+ Configurable.__init__(self, configuration_path)
89
+ HasOptionalExecutable.__init__(self, name, executable_path)
90
+ self.package_manager: PackageManagerType = (
91
+ package_manager if package_manager is not None else PipPackageManager()
92
+ )
93
+ self.bound: Bound = (
94
+ bound if isinstance(bound, Bound) else Bound.from_string(bound)
95
+ )
96
+ self.target = target
97
+ logger.debug(
98
+ "QualityAssuranceRunner '%s' initialized with bound=%s, target=%s",
99
+ name,
100
+ self.bound,
101
+ target,
102
+ )
103
+
104
+ @abstractmethod
105
+ def _build_command(
106
+ self, target: str, use_system_interpreter: bool = False
107
+ ) -> str: ...
108
+
109
+ @abstractmethod
110
+ def _install_dependencies(self, base: LayeredCommand) -> None: ...
111
+
112
+ def _pre_command(self) -> None: ...
113
+
114
+ def _post_command(self) -> None: ...
115
+
116
+ def _handle_special_exit_codes(self, ret: int, command: str) -> None:
117
+ if ret in SPEICLA_EXIT_CODES:
118
+ title, explanation = SPEICLA_EXIT_CODES[ret]
119
+ unsigned_integer_ret = ret + 2**32
120
+ logger.error("Special exit code %d encountered: %s", ret, title)
121
+ raise RuntimeError(
122
+ title
123
+ + "\n\t"
124
+ + explanation.format(
125
+ command=command, ret=ret, hex=hex(unsigned_integer_ret)
126
+ )
127
+ )
128
+
129
+ def _validate_score_against_bound(
130
+ self, score: float, env_name: str, verbose: bool = False
131
+ ) -> None:
132
+ from quickpub.enforcers import exit_if # pylint: disable=import-error
133
+
134
+ logger.debug(
135
+ "QA runner '%s' scored %.3f (bound: %s)",
136
+ self.__class__.__name__,
137
+ score,
138
+ self.bound,
139
+ )
140
+
141
+ if not self.bound.compare_against(score):
142
+ logger.error(
143
+ "QA runner '%s' failed bound check: %s vs %s",
144
+ self.__class__.__name__,
145
+ score,
146
+ self.bound,
147
+ )
148
+
149
+ exit_if(
150
+ not self.bound.compare_against(score),
151
+ f"On env '{env_name}' runner '{self.__class__.__name__}' failed to pass its defined bound. Got a score of {score} but expected {self.bound}",
152
+ )
153
+
154
+ async def run(
155
+ self,
156
+ target: str,
157
+ executor: AsyncLayeredCommand,
158
+ *,
159
+ verbose: bool = True, # type: ignore
160
+ use_system_interpreter: bool = False,
161
+ env_name: str,
162
+ ) -> None:
163
+ logger.debug(
164
+ "Running %s on environment '%s' with target '%s'",
165
+ self.__class__.__name__,
166
+ env_name,
167
+ target,
168
+ )
169
+
170
+ command = self._build_command(target, use_system_interpreter)
171
+ command = self.package_manager.wrap_command(command)
172
+ logger.debug("Built command: %s", command)
173
+
174
+ self._pre_command()
175
+ start_time = time.perf_counter()
176
+ try:
177
+ ret, out, err = await executor(command, command_raise_on_fail=False)
178
+ self._handle_special_exit_codes(ret, command)
179
+
180
+ score = self._calculate_score(ret, out + err, verbose=verbose)
181
+ self._validate_score_against_bound(score, env_name, verbose)
182
+ except Exception as e:
183
+ logger.error(
184
+ "QA runner '%s' failed on env '%s': %s",
185
+ self.__class__.__name__,
186
+ env_name,
187
+ e,
188
+ )
189
+ raise RuntimeError(
190
+ f"On env {env_name}, failed to run {self.__class__.__name__}. Try running manually:\n{executor._build_command(command)}",
191
+ e,
192
+ ) from e
193
+ finally:
194
+ elapsed = time.perf_counter() - start_time
195
+ logger.info(
196
+ "QA runner '%s' finished in %.3fs",
197
+ self.__class__.__name__,
198
+ elapsed,
199
+ )
200
+ self._post_command()
201
+
202
+ @abstractmethod
203
+ def _calculate_score(
204
+ self, ret: int, command_output: List[str], *, verbose: bool = False
205
+ ) -> float: ...
206
+
207
+
208
+ __all__ = ["QualityAssuranceRunner"]
@@ -0,0 +1,14 @@
1
+ from typing import Type
2
+
3
+ from danielutils.university.oop.strategy import Strategy
4
+
5
+ from ..enforcers import ExitEarlyError
6
+
7
+
8
+ class QuickpubStrategy(Strategy):
9
+ EXCEPTION_TYPE: Type[Exception] = ExitEarlyError
10
+
11
+
12
+ __all__ = [
13
+ "QuickpubStrategy",
14
+ ]
@@ -0,0 +1,19 @@
1
+ from abc import abstractmethod
2
+ from typing import Any
3
+
4
+ from .quickpub_strategy import QuickpubStrategy
5
+
6
+
7
+ class UploadTarget(QuickpubStrategy):
8
+ """Base class for upload target implementations. Subclass this to define custom upload strategies."""
9
+
10
+ def __init__(self, verbose: bool = True) -> None:
11
+ self.verbose = verbose
12
+
13
+ @abstractmethod
14
+ def upload(self, **kwargs: Any) -> None: ...
15
+
16
+
17
+ __all__ = [
18
+ "UploadTarget",
19
+ ]
@@ -0,0 +1,3 @@
1
+ from .bound import *
2
+ from .version import *
3
+ from .dependency import *
@@ -0,0 +1,52 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from typing import Literal, List
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ @dataclass
9
+ class Bound:
10
+ operator: Literal["<", "<=", "==", ">", ">="]
11
+ value: float
12
+
13
+ def compare_against(self, score: float) -> bool:
14
+ result = {
15
+ ">": score > self.value,
16
+ ">=": score >= self.value,
17
+ "<": score < self.value,
18
+ "<=": score <= self.value,
19
+ "==": score == self.value,
20
+ }[self.operator]
21
+ logger.debug(
22
+ "Bound comparison: %s %s %s = %s", score, self.operator, self.value, result
23
+ )
24
+ return result
25
+
26
+ @staticmethod
27
+ def from_string(s: str) -> "Bound":
28
+ logger.debug("Parsing bound from string: '%s'", s)
29
+ valid_operators: List[Literal["<", "<=", "==", ">", ">="]] = [
30
+ ">=",
31
+ "<=",
32
+ "==",
33
+ ">",
34
+ "<",
35
+ ]
36
+ for op in valid_operators:
37
+ splits = s.split(op)
38
+ if len(splits) == 2:
39
+ bound = Bound(op, float(splits[-1]))
40
+ logger.debug("Parsed bound: %s", bound)
41
+ return bound
42
+ logger.error("Failed to parse bound from string: '%s'", s)
43
+ raise ValueError("Invalid 'Bound' format")
44
+
45
+ def __str__(self) -> str:
46
+ return f"{self.operator}{self.value}"
47
+
48
+ def __repr__(self) -> str:
49
+ return f"{self.__class__.__name__}(operator='{self.operator}', value='{self.value}')"
50
+
51
+
52
+ __all__ = ["Bound"]
@@ -0,0 +1,82 @@
1
+ import logging
2
+ from typing import Literal, Callable, Dict, Any, List
3
+
4
+ from .version import Version
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class Dependency:
10
+ """Represents a package dependency with version constraints. Use from_string() to parse dependency strings."""
11
+
12
+ def _build_func_map(self) -> Dict[str, Callable[[Version], bool]]:
13
+ return {
14
+ "==": lambda v: v == self.ver,
15
+ ">=": lambda v: v >= self.ver,
16
+ "<=": lambda v: v <= self.ver,
17
+ ">": lambda v: v > self.ver,
18
+ "<": lambda v: v < self.ver,
19
+ }
20
+
21
+ def __init__(
22
+ self,
23
+ name: str,
24
+ operator: Literal["<", "<=", "==", ">", ">="] = ">=",
25
+ ver: Version = Version(0, 0, 0),
26
+ ) -> None:
27
+ self.name: str = name
28
+ self.operator: Literal["<", "<=", "==", ">", ">="] = operator
29
+ self.ver: Version = ver or Version(0, 0, 0)
30
+ logger.debug("Dependency created: %s %s %s", self.name, self.operator, self.ver)
31
+
32
+ def __eq__(self, other: Any) -> bool:
33
+ if not isinstance(other, Dependency):
34
+ return False
35
+ return (
36
+ self.name == other.name
37
+ and self.operator == other.operator
38
+ and self.ver == other.ver
39
+ )
40
+
41
+ def __hash__(self) -> int:
42
+ return hash((self.name, self.operator, self.ver))
43
+
44
+ @staticmethod
45
+ def from_string(s: str) -> "Dependency":
46
+ logger.debug("Parsing dependency from string: '%s'", s)
47
+ valid_operators: List[Literal["<", "<=", "==", ">", ">="]] = [
48
+ ">=",
49
+ "<=",
50
+ ">",
51
+ "<",
52
+ "==",
53
+ ]
54
+ for op in valid_operators:
55
+ splits = s.split(op)
56
+ if len(splits) == 2:
57
+ dep = Dependency(
58
+ splits[0],
59
+ op,
60
+ Version.from_str(splits[-1]),
61
+ )
62
+ logger.debug("Parsed dependency: %s", dep)
63
+ return dep
64
+ dep = Dependency(s, ">=", Version(0, 0, 0))
65
+ logger.debug("Parsed dependency (default): %s", dep)
66
+ return dep
67
+
68
+ def __str__(self) -> str:
69
+ if self.ver == Version(0, 0, 0):
70
+ return self.name
71
+ return f"{self.name}{self.operator}{self.ver}"
72
+
73
+ def __repr__(self) -> str:
74
+ return f"{self.__class__.__name__}(name='{self.name}', operator='{self.operator}', version='{self.ver}')"
75
+
76
+ def is_satisfied_by(self, ver: Version) -> bool:
77
+ result = self._build_func_map()[self.operator](ver)
78
+ logger.debug("Dependency '%s' satisfied by version '%s': %s", self, ver, result)
79
+ return result
80
+
81
+
82
+ __all__ = ["Dependency"]
@@ -0,0 +1,41 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+
7
+ @dataclass(order=True)
8
+ class Version:
9
+ @staticmethod
10
+ def from_str(version_str: str) -> "Version":
11
+ try:
12
+ version = Version(*list(map(int, version_str.split("."))))
13
+ return version
14
+ except Exception as e:
15
+ logger.error("Failed to parse version from string '%s': %s", version_str, e)
16
+ raise ValueError(
17
+ f"Failed converting '{version_str}' to instance of 'Version' in 'Version.from_str"
18
+ ) from e
19
+
20
+ major: int = 0
21
+ minor: int = 0
22
+ patch: int = 0
23
+
24
+ def __init__(self, major: int = 0, minor: int = 0, patch: int = 0) -> None:
25
+ if not all(map(lambda x: isinstance(x, int) and x >= 0, [major, minor, patch])):
26
+ logger.error(
27
+ "Invalid version components: major=%s, minor=%s, patch=%s",
28
+ major,
29
+ minor,
30
+ patch,
31
+ )
32
+ raise ValueError("Version supports positive integers only")
33
+ self.major = major
34
+ self.minor = minor
35
+ self.patch = patch
36
+
37
+ def __str__(self) -> str:
38
+ return f"{self.major}.{self.minor}.{self.patch}"
39
+
40
+
41
+ __all__ = ["Version"]
quickpub/validators.py ADDED
@@ -0,0 +1,87 @@
1
+ import logging
2
+ from typing import Optional, Union, List
3
+
4
+ from danielutils import get_python_version
5
+
6
+ from .enforcers import ExitEarlyError
7
+ from .structures import Version, Dependency
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def validate_version(version: Optional[Union[str, Version]] = None) -> Version:
13
+ logger.debug("Validating version: %s", version)
14
+ if not bool(version):
15
+ logger.error("Version validation failed: no version provided")
16
+ raise ExitEarlyError(f"Must supply a version number. got '{version}'")
17
+
18
+ if isinstance(version, Version):
19
+ logger.debug("Version is already a Version object: %s", version)
20
+ return version
21
+ logger.debug("Converting string version to Version object: %s", version)
22
+ if not isinstance(version, str):
23
+ raise ExitEarlyError(
24
+ f"Version must be a string or Version object, got '{type(version)}'"
25
+ )
26
+ return Version.from_str(version)
27
+
28
+
29
+ def validate_python_version(min_python: Optional[Version]) -> Version:
30
+ logger.debug("Validating Python version. min_python: %s", min_python)
31
+ if min_python is not None:
32
+ logger.debug("Using provided minimum Python version: %s", min_python)
33
+ return min_python
34
+
35
+ current_version = Version(*get_python_version())
36
+ logger.debug("Using current Python version: %s", current_version)
37
+ return current_version
38
+
39
+
40
+ def validate_keywords(keywords: Optional[List[str]]) -> List[str]:
41
+ logger.debug("Validating keywords: %s", keywords)
42
+ if keywords is None:
43
+ logger.debug("No keywords provided, returning empty list")
44
+ return []
45
+ logger.debug("Using provided keywords: %s", keywords)
46
+ return keywords
47
+
48
+
49
+ def validate_dependencies(
50
+ dependencies: Optional[List[Union[str, Dependency]]],
51
+ ) -> List[Dependency]:
52
+ logger.debug("Validating dependencies: %s", dependencies)
53
+ if dependencies is None:
54
+ logger.debug("No dependencies provided, returning empty list")
55
+ return []
56
+
57
+ res = []
58
+ for dep in dependencies:
59
+ if isinstance(dep, str):
60
+ logger.debug("Converting string dependency to Dependency object: %s", dep)
61
+ res.append(Dependency.from_string(dep))
62
+ else:
63
+ logger.debug("Using existing Dependency object: %s", dep)
64
+ res.append(dep)
65
+
66
+ logger.debug("Validated %d dependencies", len(res))
67
+ return res
68
+
69
+
70
+ def validate_source(name: str, src: Optional[str] = None) -> str:
71
+ logger.debug("Validating source for package '%s'. src: %s", name, src)
72
+ if src is not None:
73
+ logger.debug("Using provided source path: %s", src)
74
+ return src
75
+
76
+ default_src = f"./{name}"
77
+ logger.debug("Using default source path: %s", default_src)
78
+ return default_src
79
+
80
+
81
+ __all__ = [
82
+ "validate_version",
83
+ "validate_python_version",
84
+ "validate_keywords",
85
+ "validate_dependencies",
86
+ "validate_source",
87
+ ]
@@ -0,0 +1,16 @@
1
+ import logging
2
+ from typing import Any
3
+ from danielutils import AsyncWorkerPool
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class WorkerPool(AsyncWorkerPool):
9
+ @staticmethod
10
+ def log(level: int, message: str, *args: Any, **kwargs: Any) -> None:
11
+ logger.log(level, message, *args, **kwargs)
12
+
13
+
14
+ __all__ = [
15
+ "WorkerPool",
16
+ ]