quickpub 2.0.4__tar.gz → 3.0.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 (57) hide show
  1. {quickpub-2.0.4/quickpub.egg-info → quickpub-3.0.0}/PKG-INFO +7 -3
  2. {quickpub-2.0.4 → quickpub-3.0.0}/README.md +1 -1
  3. {quickpub-2.0.4 → quickpub-3.0.0}/pyproject.toml +1 -1
  4. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/__init__.py +2 -0
  5. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/__main__.py +18 -10
  6. quickpub-3.0.0/quickpub/enforcers.py +27 -0
  7. quickpub-3.0.0/quickpub/qa.py +265 -0
  8. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/constraint_enforcer.py +2 -1
  9. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/pypi_remote_version_enforcer.py +4 -3
  10. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/pypirc_enforcer.py +1 -1
  11. quickpub-3.0.0/quickpub/strategies/implementations/python_providers/conda_python_provider.py +35 -0
  12. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/python_providers/default_python_provider.py +12 -3
  13. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/quality_assurance_runners/mypy_qa_runner.py +11 -4
  14. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/quality_assurance_runners/pylint_qa_runner.py +7 -2
  15. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/quality_assurance_runners/pytest_qa_runner.py +3 -2
  16. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/quality_assurance_runners/unittest_qa_runner.py +10 -11
  17. quickpub-3.0.0/quickpub/strategies/python_provider.py +55 -0
  18. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/quality_assurance_runner.py +14 -10
  19. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/quickpub_strategy.py +3 -1
  20. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/validators.py +5 -3
  21. {quickpub-2.0.4 → quickpub-3.0.0/quickpub.egg-info}/PKG-INFO +7 -3
  22. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub.egg-info/SOURCES.txt +2 -0
  23. quickpub-2.0.4/quickpub/enforcers.py +0 -17
  24. quickpub-2.0.4/quickpub/qa.py +0 -180
  25. quickpub-2.0.4/quickpub/strategies/implementations/python_providers/conda_python_provider.py +0 -32
  26. quickpub-2.0.4/quickpub/strategies/python_provider.py +0 -42
  27. {quickpub-2.0.4 → quickpub-3.0.0}/LICENSE +0 -0
  28. {quickpub-2.0.4 → quickpub-3.0.0}/MANIFEST.in +0 -0
  29. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/classifiers.py +0 -0
  30. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/files.py +0 -0
  31. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/functions.py +0 -0
  32. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/proxy.py +0 -0
  33. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/py.typed +0 -0
  34. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/__init__.py +0 -0
  35. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/build_schema.py +0 -0
  36. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/__init__.py +0 -0
  37. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/build_schemas/__init__.py +0 -0
  38. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/build_schemas/setuptools_build_schema.py +0 -0
  39. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/__init__.py +0 -0
  40. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/license_enforcer.py +0 -0
  41. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/local_version_enforcer.py +0 -0
  42. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/constraint_enforcers/readme_enforcer.py +0 -0
  43. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/python_providers/__init__.py +0 -0
  44. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/quality_assurance_runners/__init__.py +0 -0
  45. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/upload_targets/__init__.py +0 -0
  46. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/upload_targets/github_upload_target.py +0 -0
  47. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/implementations/upload_targets/pypirc_upload_target.py +0 -0
  48. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/strategies/upload_target.py +0 -0
  49. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/structures/__init__.py +0 -0
  50. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/structures/bound.py +0 -0
  51. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/structures/dependency.py +0 -0
  52. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub/structures/version.py +0 -0
  53. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub.egg-info/dependency_links.txt +0 -0
  54. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub.egg-info/requires.txt +0 -0
  55. {quickpub-2.0.4 → quickpub-3.0.0}/quickpub.egg-info/top_level.txt +0 -0
  56. {quickpub-2.0.4 → quickpub-3.0.0}/setup.cfg +0 -0
  57. {quickpub-2.0.4 → quickpub-3.0.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: quickpub
3
- Version: 2.0.4
3
+ Version: 3.0.0
4
4
  Summary: A python package to quickly configure and publish a new package
5
5
  Author-email: danielnachumdev <danielnachumdev@gmail.com>
6
6
  License: MIT License
@@ -33,8 +33,12 @@ Classifier: Operating System :: Microsoft :: Windows
33
33
  Requires-Python: >=3.8.0
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
+ Requires-Dist: danielutils>=1.0.0
37
+ Requires-Dist: requests
38
+ Requires-Dist: fire
39
+ Dynamic: license-file
36
40
 
37
- # quickpub V2.0.2
41
+ # quickpub V3.0.0
38
42
  **Tested python versions**: `3.8.0`, `3.9.0`, `3.10.13`,
39
43
 
40
44
  Example usage of how this package was published
@@ -1,4 +1,4 @@
1
- # quickpub V2.0.2
1
+ # quickpub V3.0.0
2
2
  **Tested python versions**: `3.8.0`, `3.9.0`, `3.10.13`,
3
3
 
4
4
  Example usage of how this package was published
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quickpub"
7
- version = "2.0.4"
7
+ version = "3.0.0"
8
8
  authors = [
9
9
  { name = "danielnachumdev", email = "danielnachumdev@gmail.com" },
10
10
  ]
@@ -1,3 +1,5 @@
1
1
  from .structures import *
2
2
  from .strategies import *
3
+ from .enforcers import ExitEarlyError
4
+ from .qa import SupportsProgress
3
5
  from .__main__ import publish
@@ -1,8 +1,10 @@
1
- from typing import Optional, Union, List, Any
1
+ import asyncio
2
+ from typing import Optional, Union, List, Any, Callable
2
3
 
3
4
  import fire
4
5
  from danielutils import warning, error
5
6
 
7
+ from quickpub import ExitEarlyError
6
8
  from .strategies import BuildSchema, ConstraintEnforcer, UploadTarget, QualityAssuranceRunner, PythonProvider, \
7
9
  DefaultPythonProvider
8
10
  from .validators import validate_version, validate_python_version, validate_keywords, validate_dependencies, \
@@ -10,7 +12,7 @@ from .validators import validate_version, validate_python_version, validate_keyw
10
12
  from .structures import Version, Dependency
11
13
  from .files import create_toml, create_setup, create_manifest
12
14
  from .classifiers import *
13
- from .qa import qa
15
+ from .qa import qa, SupportsProgress
14
16
 
15
17
 
16
18
  def publish(
@@ -34,12 +36,16 @@ def publish(
34
36
  dependencies: Optional[List[Union[str, Dependency]]] = None,
35
37
  keywords: Optional[List[str]] = None,
36
38
  explicit_src_folder_path: Optional[str] = None,
39
+ # ========== QA Parameters ==========
40
+ log: Optional[Callable[[Any], None]] = None,
41
+ pbar: Optional[SupportsProgress] = None, # tqdm
37
42
 
38
43
  demo: bool = False,
39
44
  config: Optional[Any] = None,
40
45
  ) -> None:
41
46
  """The main function for publishing a package. It performs all necessary steps to prepare and publish the package.
42
47
 
48
+
43
49
  :param name: The name of the package.
44
50
  :param author: The name of the author.
45
51
  :param author_email: The email of the author.
@@ -56,6 +62,8 @@ def publish(
56
62
  :param min_python: The minimum Python version required for the package. Defaults to the Python version running this script.
57
63
  :param keywords: A list of keywords describing areas of interest for the package. Defaults to None.
58
64
  :param dependencies: A list of dependencies for the package. Defaults to None.
65
+ :param log: A function to receive log statements about the process and print them (or do something else idk)
66
+ :param pbar: and object that can be notified about an update of progress like a tqdm progress bar.
59
67
  :param demo: Whether to perform checks without making any changes. Defaults to False.
60
68
  :param config: Reserved for future use. Defaults to None.
61
69
 
@@ -70,27 +78,27 @@ def publish(
70
78
  min_python = validate_python_version(min_python) # type:ignore
71
79
  keywords = validate_keywords(keywords)
72
80
  validated_dependencies: List[Dependency] = validate_dependencies(dependencies)
73
-
74
81
  for enforcer in enforcers or []:
75
82
  enforcer.enforce(name=name, version=version, demo=demo)
76
-
77
83
  try:
78
- res = qa(
84
+ res = asyncio.get_event_loop().run_until_complete(qa(
79
85
  python_interpreter_provider,
80
86
  global_quality_assurance_runners or [],
81
87
  name,
82
88
  explicit_src_folder_path,
83
- validated_dependencies
84
- )
89
+ validated_dependencies,
90
+ log,
91
+ pbar
92
+ ))
85
93
  if not res:
86
94
  error(f"quickpub.publish exited early as '{name}' "
87
95
  "did not pass quality assurance step, see above "
88
96
  "logs to pass this step.")
89
- raise SystemExit(1)
90
- except SystemExit as e:
97
+ raise ExitEarlyError("QA step Failed")
98
+ except ExitEarlyError as e:
91
99
  raise e
92
100
  except Exception as e:
93
- raise RuntimeError("Quality assurance stage has failed") from e
101
+ raise RuntimeError("Quality assurance stage has failed", e) from e
94
102
 
95
103
  create_setup()
96
104
  create_toml(
@@ -0,0 +1,27 @@
1
+ import sys
2
+ from typing import Union, Callable
3
+
4
+ from danielutils import error
5
+
6
+
7
+ class ExitEarlyError(Exception):
8
+ pass
9
+
10
+
11
+ def exit_if(
12
+ predicate: Union[bool, Callable[[], bool]],
13
+ msg: str,
14
+ *,
15
+ verbose: bool = True,
16
+ err_func: Callable[[str], None] = error
17
+ ) -> None:
18
+ if (isinstance(predicate, bool) and predicate) or (callable(predicate) and predicate()):
19
+ if verbose:
20
+ err_func(msg)
21
+ raise ExitEarlyError(msg)
22
+
23
+
24
+ __all__ = [
25
+ "exit_if",
26
+ "ExitEarlyError",
27
+ ]
@@ -0,0 +1,265 @@
1
+ import re
2
+ import sys
3
+ from abc import abstractmethod
4
+ from datetime import datetime
5
+ from typing import ContextManager, List, Callable, Tuple, Dict, Union, Any, Literal, Optional, Protocol, \
6
+ runtime_checkable
7
+ from danielutils import TemporaryFile, AsyncWorkerPool, RandomDataGenerator
8
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
9
+
10
+ from .enforcers import ExitEarlyError
11
+ from .strategies import PythonProvider, QualityAssuranceRunner # pylint: disable=relative-beyond-top-level
12
+ from .structures import Dependency, Version # pylint: disable=relative-beyond-top-level
13
+ from .enforcers import exit_if # pylint: disable=relative-beyond-top-level
14
+
15
+ try:
16
+ from danielutils import MultiContext # type:ignore
17
+ except ImportError:
18
+ class MultiContext(ContextManager): # type: ignore # pylint: disable=missing-class-docstring
19
+ def __init__(self, *contexts: ContextManager):
20
+ self.contexts = contexts
21
+
22
+ def __enter__(self):
23
+ for context in self.contexts:
24
+ context.__enter__()
25
+ return self
26
+
27
+ def __exit__(self, exc_type, exc_val, exc_tb):
28
+ for context in self.contexts:
29
+ context.__exit__(exc_type, exc_val, exc_tb)
30
+
31
+ def __getitem__(self, index):
32
+ return self.contexts[index]
33
+
34
+ ASYNC_POOL_NAME: str = "Quickpub QA"
35
+
36
+
37
+ @runtime_checkable
38
+ class SupportsProgress(Protocol):
39
+ @abstractmethod
40
+ def update(self, amount: int) -> None:
41
+ ...
42
+
43
+ @property
44
+ @abstractmethod
45
+ def total(self) -> int: ...
46
+
47
+ @total.setter
48
+ @abstractmethod
49
+ def total(self, amount: int) -> None: ...
50
+
51
+
52
+ async def global_import_sanity_check(
53
+ package_name: str,
54
+ executor: AsyncLayeredCommand,
55
+ is_system_interpreter: bool,
56
+ env_name: str,
57
+ pbar: Optional[SupportsProgress] = None
58
+ ) -> None:
59
+ """
60
+ Will check that importing from the package works as a sanity check.
61
+ :param package_name: Name of the package
62
+ :param executor: the previously ued AsyncLayeredCommand executor
63
+ :param is_system_interpreter: whether or not the system interpreter is used
64
+ :param env_name: The name of the currently tested environment
65
+ :return: None
66
+ """
67
+ try:
68
+ p = sys.executable if is_system_interpreter else "python"
69
+ file_name = f"./{RandomDataGenerator().name(15)}__sanity_check_main.py"
70
+ with TemporaryFile(file_name) as f:
71
+ f.writelines([f"from {package_name} import *"])
72
+ cmd = f"{p} {file_name}"
73
+ code, stdout, stderr = await executor(cmd)
74
+ msg = f"Env '{env_name}' failed sanity check."
75
+ if stderr:
76
+ if stderr[0] == 'Traceback (most recent call last):':
77
+ msg += f" Got error '{stderr[-1]}' when tried 'from {package_name} import *'"
78
+ else:
79
+ msg += f" Try manually running the following script 'from {package_name} import *'"
80
+ exit_if(
81
+ code != 0, msg,
82
+ verbose=True,
83
+ err_func=lambda msg: None # TODO remove
84
+ )
85
+ finally:
86
+ if pbar is not None:
87
+ pbar.update(1)
88
+
89
+
90
+ VERSION_REGEX: re.Pattern = re.compile(r"^\d+\.\d+\.\d+$")
91
+
92
+
93
+ async def validate_dependencies(
94
+ validation_exit_on_fail: bool,
95
+ required_dependencies: List[Dependency],
96
+ executor: AsyncLayeredCommand,
97
+ env_name: str,
98
+ pbar: Optional[SupportsProgress] = None
99
+ ) -> None:
100
+ """
101
+ will check if all the dependencies of the package are installed on current env.
102
+ :param validation_exit_on_fail:
103
+ :param required_dependencies: the dependencies to check
104
+ :param executor: the current AsyncLayeredCommand executor
105
+ :param env_name: name of the currently checked environment
106
+ :return: None
107
+ """
108
+ try:
109
+ if validation_exit_on_fail:
110
+ code, out, err = await executor("pip list")
111
+ exit_if(code != 0, f"Failed executing 'pip list' at env '{env_name}'",
112
+ err_func=lambda msg: None # TODO remove
113
+ )
114
+ split_lines = (line.split(' ') for line in out[2:])
115
+ version_tuples = [(s[0], s[-1].strip()) for s in split_lines]
116
+ filtered_tuples = [t for t in version_tuples if VERSION_REGEX.match(t[1])]
117
+ currently_installed: Dict[str, Union[str, Dependency]] = {
118
+ s[0]: Dependency(s[0], "==", Version.from_str(s[-1]))
119
+ for s in filtered_tuples}
120
+ currently_installed.update(**{t[0]: t[1] for t in version_tuples if not VERSION_REGEX.match(t[1])})
121
+ not_installed_properly: List[Tuple[Dependency, str]] = []
122
+ for req in required_dependencies:
123
+ if req.name not in currently_installed:
124
+ not_installed_properly.append((req, "dependency not found"))
125
+ else:
126
+ v = currently_installed[req.name]
127
+ if isinstance(v, str):
128
+ not_installed_properly.append(
129
+ (req, "Version format of dependency is not currently supported by quickpub"))
130
+ elif isinstance(v, Dependency):
131
+ if not req.is_satisfied_by(v.ver):
132
+ not_installed_properly.append((req, "Invalid version installed"))
133
+
134
+ exit_if(bool(not_installed_properly),
135
+ f"On env '{env_name}' the following dependencies have problems: {(not_installed_properly)}",
136
+ err_func=lambda msg: None # TODO remove
137
+ )
138
+ finally:
139
+ if pbar is not None:
140
+ pbar.update(1)
141
+
142
+
143
+ is_config_run_success: List[bool] = []
144
+
145
+
146
+ async def run_config(
147
+ env_name: str,
148
+ async_executor: AsyncLayeredCommand,
149
+ runner: QualityAssuranceRunner,
150
+ config_id: int,
151
+ *,
152
+ is_system_interpreter: bool,
153
+ validation_exit_on_fail: bool,
154
+ src_folder_path: str,
155
+ pbar: Optional[SupportsProgress] = None
156
+ ) -> None:
157
+ try:
158
+ await runner.run(
159
+ src_folder_path,
160
+ async_executor,
161
+ use_system_interpreter=is_system_interpreter,
162
+ env_name=env_name
163
+ )
164
+ except ExitEarlyError as e:
165
+ raise e
166
+ except Exception as e:
167
+ if validation_exit_on_fail:
168
+ raise RuntimeError(e) from e
169
+ return
170
+ finally:
171
+ if pbar is not None:
172
+ pbar.update(1)
173
+ is_config_run_success[config_id] = True
174
+
175
+
176
+ async def qa(
177
+ python_provider: PythonProvider,
178
+ quality_assurance_strategies: List[QualityAssuranceRunner],
179
+ package_name: str,
180
+ src_folder_path: str,
181
+ dependencies: list,
182
+ log: Optional[Callable[[Any], None]] = None,
183
+ pbar: Optional[SupportsProgress] = None
184
+ ) -> bool:
185
+ is_config_run_success.clear()
186
+ from .strategies import DefaultPythonProvider
187
+ is_system_interpreter = isinstance(python_provider, DefaultPythonProvider)
188
+
189
+ class MyAsyncWorkerPool(AsyncWorkerPool):
190
+ @classmethod
191
+ def log(
192
+ self,
193
+ level: Literal["INFO", "WARNING", "ERROR"],
194
+ message: str,
195
+ order: Optional[List[str]] = AsyncWorkerPool.DEFAULT_ORDER_IF_KEY_EXISTS,
196
+ **kwargs
197
+ ) -> None:
198
+ kwargs["level"] = level
199
+ kwargs["message"] = message
200
+ kwargs["timestamp"] = datetime.now().isoformat()
201
+ ordered_kwargs = kwargs
202
+ if order:
203
+ ordered_kwargs = {key: kwargs[key] for key in order if key in kwargs}
204
+ ordered_kwargs.update(kwargs)
205
+
206
+ if log:
207
+ log(ordered_kwargs)
208
+
209
+ pool = MyAsyncWorkerPool(ASYNC_POOL_NAME, num_workers=5)
210
+ total = 0
211
+ i = 0
212
+ with AsyncLayeredCommand() as base:
213
+ async for env_name, async_executor in python_provider:
214
+ with async_executor:
215
+ async_executor.prev = base
216
+ await pool.submit(
217
+ validate_dependencies,
218
+ args=[
219
+ python_provider.exit_on_fail,
220
+ dependencies,
221
+ async_executor,
222
+ env_name,
223
+ pbar
224
+ ],
225
+ name=f"Validate dependencies for env '{env_name}'",
226
+ )
227
+ total += 1
228
+ await pool.submit(
229
+ global_import_sanity_check,
230
+ args=[
231
+ package_name,
232
+ async_executor,
233
+ is_system_interpreter,
234
+ env_name,
235
+ pbar
236
+ ],
237
+ name=f"Global Import Sanity Check for env '{env_name}'",
238
+ )
239
+ total += 1
240
+ for runner in quality_assurance_strategies:
241
+ await pool.submit(
242
+ run_config,
243
+ args=[env_name, async_executor, runner, i],
244
+ kwargs=dict(src_folder_path=src_folder_path,
245
+ is_system_interpreter=is_system_interpreter,
246
+ validation_exit_on_fail=python_provider.exit_on_fail,
247
+ pbar=pbar),
248
+ name=f"Run config for '{env_name}' + '{runner.__class__.__qualname__}'",
249
+ )
250
+ total += 1
251
+ i += 1
252
+ if pbar is not None:
253
+ pbar.total = total
254
+ for _ in range(i):
255
+ is_config_run_success.append(False)
256
+ await pool.start()
257
+ await pool.join()
258
+
259
+ return all(is_config_run_success)
260
+
261
+
262
+ __all__ = [
263
+ 'qa',
264
+ "SupportsProgress"
265
+ ]
@@ -1,11 +1,12 @@
1
1
  from abc import abstractmethod
2
2
  from typing import Type
3
3
 
4
+ from ..enforcers import ExitEarlyError
4
5
  from .quickpub_strategy import QuickpubStrategy
5
6
 
6
7
 
7
8
  class ConstraintEnforcer(QuickpubStrategy):
8
- EXCEPTION_TYPE: Type[BaseException] = SystemExit
9
+ EXCEPTION_TYPE: Type[Exception] = ExitEarlyError
9
10
 
10
11
  @abstractmethod
11
12
  def enforce(self, **kwargs) -> None: ...
@@ -22,15 +22,16 @@ class PypiRemoteVersionEnforcer(ConstraintEnforcer):
22
22
  executor: RetryExecutor[Response] = RetryExecutor(ConstantBackOffStrategy(1))
23
23
  response = executor.execute(wrapper, 5)
24
24
  if response is None:
25
- raise SystemExit(self._HTTP_FAILED_MESSAGE)
25
+ raise self.EXCEPTION_TYPE(self._HTTP_FAILED_MESSAGE)
26
26
  html = response.content.decode()
27
27
  i = html.index(f" {name} ")
28
28
  try:
29
29
  remote_version = Version.from_str(html[i:i + 50].splitlines()[0].strip().split()[1])
30
30
  except Exception as e:
31
- raise SystemExit(f"{self.__class__.__name__} encountered an unexpected error while parsing.") from e
31
+ raise self.EXCEPTION_TYPE(f"{self.__class__.__name__} encountered an unexpected error while parsing.",
32
+ e) from e
32
33
  if not version > remote_version:
33
- raise SystemExit(
34
+ raise self.EXCEPTION_TYPE(
34
35
  f"Specified version is '{version}' but (remotely available) latest existing is '{remote_version}'")
35
36
 
36
37
 
@@ -15,7 +15,7 @@ class PypircEnforcer(ConstraintEnforcer):
15
15
 
16
16
  def enforce(self, **kwargs) -> None:
17
17
  if not file_exists(self.path):
18
- raise SystemExit(f"Couldn't find '{self.path}'")
18
+ raise self.EXCEPTION_TYPE(f"Couldn't find '{self.path}'")
19
19
  if self.should_enforce_expected_format:
20
20
  with open(self.path, "r") as f:
21
21
  text = f.read()
@@ -0,0 +1,35 @@
1
+ from typing import Tuple, Optional, Set, List
2
+ from danielutils import AsyncLayeredCommand
3
+
4
+ from ....enforcers import ExitEarlyError
5
+ from ...python_provider import PythonProvider
6
+
7
+
8
+ class CondaPythonProvider(PythonProvider):
9
+ def get_python_executable_name(self) -> str:
10
+ return "python"
11
+
12
+ def __init__(self, env_names: List[str]) -> None:
13
+ PythonProvider.__init__(self, requested_envs=env_names, explicit_versions=[], exit_on_fail=True)
14
+ self._cached_available_envs: Optional[Set[str]] = None
15
+
16
+ @classmethod
17
+ async def _get_available_envs_impl(cls) -> Set[str]:
18
+ with AsyncLayeredCommand() as base:
19
+ code, out, err = await base("conda env list")
20
+ return set([line.split(' ')[0] for line in out[2:] if len(line.split(' ')) > 1])
21
+
22
+ async def __anext__(self) -> Tuple[str, AsyncLayeredCommand]:
23
+ if self.aiter_index >= len(self.requested_envs):
24
+ raise StopAsyncIteration
25
+ available_envs = await self.get_available_envs()
26
+ self.aiter_index += 1
27
+ name = self.requested_envs[self.aiter_index - 1]
28
+ if name not in available_envs:
29
+ raise ExitEarlyError(f"Can't find env '{name}' in list of conda environments, try 'conda env list'")
30
+ return name, AsyncLayeredCommand(f"conda activate {name}")
31
+
32
+
33
+ __all__ = [
34
+ 'CondaPythonProvider',
35
+ ]
@@ -1,11 +1,17 @@
1
1
  import sys
2
- from typing import Set, Tuple, Iterator
2
+ from typing import Set, Tuple, Iterator, AsyncIterator, Iterable
3
3
 
4
4
  from danielutils import LayeredCommand
5
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
5
6
 
6
7
  from ...python_provider import PythonProvider
7
8
 
8
9
 
10
+ async def cast_aiter(itr: Iterable) -> AsyncIterator:
11
+ for x in itr:
12
+ yield x
13
+
14
+
9
15
  class DefaultPythonProvider(PythonProvider):
10
16
  def get_python_executable_name(self) -> str:
11
17
  return sys.executable
@@ -13,8 +19,11 @@ class DefaultPythonProvider(PythonProvider):
13
19
  def __init__(self) -> None:
14
20
  PythonProvider.__init__(self, requested_envs=["system"], explicit_versions=[], exit_on_fail=True)
15
21
 
16
- def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]:
17
- return iter([("system", LayeredCommand())])
22
+ async def __anext__(self):
23
+ if self.aiter_index == 0:
24
+ self.aiter_index += 1
25
+ return "system", AsyncLayeredCommand()
26
+ raise StopAsyncIteration
18
27
 
19
28
  @classmethod
20
29
  def _get_available_envs_impl(cls) -> Set[str]:
@@ -3,6 +3,7 @@ from typing import Optional, List
3
3
 
4
4
  from danielutils import LayeredCommand
5
5
 
6
+ from ....enforcers import ExitEarlyError
6
7
  from ...quality_assurance_runner import QualityAssuranceRunner
7
8
 
8
9
 
@@ -34,14 +35,20 @@ class MypyRunner(QualityAssuranceRunner):
34
35
  return 0.0
35
36
 
36
37
  if rating_line.endswith("No module named mypy"):
37
- raise SystemExit("Mypy is not installed.")
38
+ raise ExitEarlyError("Mypy is not installed.")
39
+
40
+ if rating_line.startswith("mypy: error: Cannot find config file"):
41
+ raise ExitEarlyError(rating_line)
38
42
 
39
43
  if rating_line.startswith("Success"):
40
44
  return 0.0
41
45
 
42
- exit_if(not (m := self.RATING_PATTERN.match(rating_line)),
43
- f"Failed running MyPy, got exit code {ret}. try running manually using: {self._build_command('TARGET')}",
44
- verbose=verbose)
46
+ exit_if(
47
+ not (m := self.RATING_PATTERN.match(rating_line)),
48
+ f"Failed running MyPy, got exit code {ret}. try running manually using: {self._build_command('TARGET')}",
49
+ verbose=verbose,
50
+ err_func=lambda msg: None # TODO remove
51
+ )
45
52
  num_failed = float(m.group(1)) # type :ignore
46
53
  # active_files = float(m.group(2)) # type :ignore
47
54
  # total_files = float(m.group(3)) # type :ignore
@@ -3,6 +3,7 @@ from typing import Optional, List
3
3
 
4
4
  from danielutils import LayeredCommand
5
5
 
6
+ from ....enforcers import ExitEarlyError
6
7
  from ...quality_assurance_runner import QualityAssuranceRunner
7
8
 
8
9
 
@@ -31,8 +32,12 @@ class PylintRunner(QualityAssuranceRunner):
31
32
  return 1
32
33
  if len(lines) == 1:
33
34
  if lines[0].endswith("No module named pylint"):
34
- raise SystemExit("No module named pylint found")
35
- raise SystemExit("Got an unexpected error!")
35
+ raise ExitEarlyError("No module named pylint found")
36
+
37
+ if lines[0].startswith("The config file") and lines[0].endswith("doesn't exist!"):
38
+ raise ExitEarlyError(lines[0])
39
+
40
+ raise ExitEarlyError(f"Got an unexpected error: {lines[0]}")
36
41
  index = -2
37
42
  if lines[-1] == '\x1b[0m':
38
43
  index += -1
@@ -4,7 +4,8 @@ from typing import List, Union
4
4
 
5
5
  from danielutils import LayeredCommand
6
6
 
7
- from quickpub import Bound
7
+ from ....enforcers import ExitEarlyError
8
+ from ....structures import Bound
8
9
  from ...quality_assurance_runner import QualityAssuranceRunner
9
10
 
10
11
 
@@ -85,7 +86,7 @@ class PytestRunner(QualityAssuranceRunner):
85
86
  if "no tests ran" in rating_line:
86
87
  return self.no_tests_score
87
88
  if not (m := self.PYTEST_REGEX.match(rating_line)):
88
- raise SystemExit(1)
89
+ raise ExitEarlyError(f"Can't calculate score for pytest on the following line: {rating_line}")
89
90
 
90
91
  dct = m.groupdict()
91
92
  failed = int(dct["failed"] or "0")
@@ -1,8 +1,10 @@
1
1
  import re
2
2
  import os
3
+ from pathlib import Path
3
4
  from typing import Optional, List
4
5
  from danielutils import get_current_working_directory, set_current_working_directory, LayeredCommand, warning
5
6
 
7
+ from ....enforcers import ExitEarlyError
6
8
  from ...quality_assurance_runner import QualityAssuranceRunner
7
9
 
8
10
 
@@ -24,26 +26,23 @@ class UnittestRunner(QualityAssuranceRunner):
24
26
  def _install_dependencies(self, base: LayeredCommand) -> None:
25
27
  return None
26
28
 
27
- def _pre_command(self):
28
- self._cwd = get_current_working_directory()
29
- if self.target is None:
30
- self.target = ""
31
- warning("This is not supposed to happen. See quickpub's UnitestRunner._pre_command")
32
- set_current_working_directory(os.path.join(self._cwd, self.target))
29
+ def _pre_command(self) -> None:
30
+ pass
33
31
 
34
32
  def _post_command(self):
35
- set_current_working_directory(self._cwd)
33
+ pass
34
+ # set_current_working_directory(self._cwd)
36
35
 
37
36
  def __init__(self, target: Optional[str] = "./tests", bound: str = ">=0.8", no_tests_score: float = 0) -> None:
38
37
  QualityAssuranceRunner.__init__(self, name="unittest", bound=bound, target=target)
39
- self._cwd = ""
40
38
  self.no_tests_score = no_tests_score
41
39
 
42
40
  def _build_command(self, src: str, *args, use_system_interpreter: bool = False) -> str:
43
41
  command: str = self.get_executable()
44
42
  rel = _removesuffix(os.path.relpath(src, self.target), src.lstrip("./\\"))
45
43
  command += f" discover -s {rel}"
46
- return command # f"cd {self.target}; {command}" # f"; cd {self.target}"
44
+ normalized_target_path = Path(os.path.join(os.getcwd(), self.target)).resolve()
45
+ return f"cd {normalized_target_path} & {command} & cd {Path(os.getcwd()).resolve()}" # This is for concurrency reasons
47
46
 
48
47
  def _calculate_score(self, ret: int, lines: List[str], *, verbose: bool = False) -> float:
49
48
  num_tests_ran_line = lines[-3]
@@ -61,8 +60,8 @@ class UnittestRunner(QualityAssuranceRunner):
61
60
  return 1 - ((num_failed + num_errors) / num_tests)
62
61
 
63
62
  except Exception as e:
64
- raise SystemExit(f"Failed running Unittest, got exit code {ret}. "
65
- f"try running manually using: {self._build_command('TARGET')}") from e
63
+ raise ExitEarlyError(f"Failed running Unittest, got exit code {ret}. "
64
+ f"try running manually using: {self._build_command('TARGET')}") from e
66
65
 
67
66
 
68
67
  __all__ = [
@@ -0,0 +1,55 @@
1
+ import asyncio
2
+
3
+ from .quickpub_strategy import QuickpubStrategy
4
+ from abc import abstractmethod
5
+ from typing import Tuple, Set, List, AsyncIterator, Iterator, Iterable
6
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
7
+
8
+
9
+ class PythonProvider(AsyncIterator, QuickpubStrategy):
10
+ def __init__(
11
+ self,
12
+ auto_install_dependencies: bool = True,
13
+ *,
14
+ requested_envs: List[str],
15
+ explicit_versions: List[str],
16
+ exit_on_fail: bool = False
17
+ ) -> None:
18
+ self.auto_install_dependencies = auto_install_dependencies
19
+ self.requested_envs = requested_envs
20
+ self.explicit_versions = explicit_versions
21
+ self.exit_on_fail = exit_on_fail
22
+ self.aiter_index = 0
23
+
24
+ def __aiter__(self) -> AsyncIterator[Tuple[str, AsyncLayeredCommand]]:
25
+ self.aiter_index = 0
26
+ return self
27
+
28
+ @abstractmethod
29
+ async def __anext__(self) -> Tuple[str, AsyncLayeredCommand]: ...
30
+
31
+ @classmethod
32
+ async def get_available_envs(cls) -> Set[str]:
33
+ KEY = "__available_envs__"
34
+ if (res := getattr(cls, KEY, None)) is not None:
35
+ return res
36
+
37
+ setattr(cls, KEY, res := await cls._get_available_envs_impl())
38
+ return res
39
+
40
+ @classmethod
41
+ @abstractmethod
42
+ async def _get_available_envs_impl(cls) -> Set[str]:
43
+ ...
44
+
45
+ def __len__(self) -> int:
46
+ return len(self.requested_envs)
47
+
48
+ @abstractmethod
49
+ def get_python_executable_name(self) -> str:
50
+ ...
51
+
52
+
53
+ __all__ = [
54
+ 'PythonProvider'
55
+ ]
@@ -2,6 +2,7 @@ import sys
2
2
  from abc import abstractmethod
3
3
  from typing import Union, List, Optional, cast, Dict, Tuple
4
4
  from danielutils import LayeredCommand, get_os, OSType, file_exists
5
+ from danielutils.async_.async_layered_command import AsyncLayeredCommand
5
6
 
6
7
  from quickpub import Bound
7
8
 
@@ -124,8 +125,8 @@ class QualityAssuranceRunner(Configurable, HasOptionalExecutable):
124
125
  """
125
126
  pass
126
127
 
127
- def run(self, target: str, executor: LayeredCommand, *, verbose: bool = True, # type: ignore
128
- use_system_interpreter: bool = False, print_func, env_name: str) -> None:
128
+ async def run(self, target: str, executor: AsyncLayeredCommand, *, verbose: bool = True, # type: ignore
129
+ use_system_interpreter: bool = False, env_name: str) -> None:
129
130
  """
130
131
  Runs the QA process on the specified target.
131
132
 
@@ -133,31 +134,34 @@ class QualityAssuranceRunner(Configurable, HasOptionalExecutable):
133
134
  :param executor: The executor object to run the command.
134
135
  :param verbose: Whether to output verbose logs, default is True.
135
136
  :param use_system_interpreter: Whether to use the system interpreter, default is False.
136
- :param print_func: The function to use for printing output.
137
137
  :param env_name: The name of the environment in which the QA runner is executed.
138
138
  """
139
139
  from quickpub.proxy import os_system # pylint: disable=import-error
140
140
  from quickpub.enforcers import exit_if # pylint: disable=import-error
141
141
  # =====================================
142
142
  # IMPORTANT: need to explicitly override it here
143
- executor._executor = os_system # pylint: disable=protected-access
143
+ # executor._executor = os_system # pylint: disable=protected-access #TODO re-fix this for the tests because now this is not working with the async variant
144
144
  # =====================================
145
145
  command = self._build_command(target, use_system_interpreter)
146
146
  self._pre_command()
147
147
  try:
148
- ret, out, err = executor(command, command_raise_on_fail=False)
148
+ ret, out, err = await executor(command, command_raise_on_fail=False)
149
149
  if ret in SPEICLA_EXIT_CODES:
150
150
  title, explanation = SPEICLA_EXIT_CODES[ret]
151
151
  unsigned_integer_ret = ret + 2 ** 32
152
152
  raise RuntimeError(
153
153
  title + "\n\t" + explanation.format(command=command, ret=ret, hex=hex(unsigned_integer_ret)))
154
- score = self._calculate_score(ret, "".join(out + err).splitlines(), verbose=verbose)
155
- exit_if(not self.bound.compare_against(score),
156
- f"On env '{env_name}' runner '{self.__class__.__name__}' failed to pass its defined bound. Got a score of {score} but expected {self.bound}",
157
- verbose=verbose, err_func=print_func)
154
+ score = self._calculate_score(ret, out + err, verbose=verbose)
155
+ exit_if(
156
+ not self.bound.compare_against(score),
157
+ f"On env '{env_name}' runner '{self.__class__.__name__}' failed to pass its defined bound. Got a score of {score} but expected {self.bound}",
158
+ verbose=verbose,
159
+ err_func=lambda msg: None # TODO remove
160
+ )
158
161
  except Exception as e:
159
162
  raise RuntimeError(
160
- f"On env {env_name}, failed to run {self.__class__.__name__}. Try running manually:\n{executor._build_command(command)}") from e
163
+ f"On env {env_name}, failed to run {self.__class__.__name__}. Try running manually:\n{executor._build_command(command)}",
164
+ e) from e
161
165
  finally:
162
166
  self._post_command()
163
167
 
@@ -2,9 +2,11 @@ from typing import Type
2
2
 
3
3
  from danielutils.university.oop.strategy import Strategy
4
4
 
5
+ from ..enforcers import ExitEarlyError
6
+
5
7
 
6
8
  class QuickpubStrategy(Strategy):
7
- EXCEPTION_TYPE: Type[Exception] = SystemExit
9
+ EXCEPTION_TYPE: Type[Exception] = ExitEarlyError
8
10
 
9
11
 
10
12
  __all__ = [
@@ -2,12 +2,13 @@ from typing import Optional, Union, List
2
2
 
3
3
  from danielutils import get_python_version
4
4
 
5
+ from .enforcers import ExitEarlyError
5
6
  from .structures import Version, Dependency
6
7
 
7
8
 
8
9
  def validate_version(version: Optional[Union[str, Version]] = None) -> Version:
9
- if version is None:
10
- return Version(0, 0, 1)
10
+ if not bool(version):
11
+ raise ExitEarlyError(f"Must supply a version number. got '{version}'")
11
12
  return version if isinstance(version, Version) else Version.from_str(version) # type: ignore
12
13
 
13
14
 
@@ -45,5 +46,6 @@ __all__ = [
45
46
  "validate_version",
46
47
  "validate_python_version",
47
48
  "validate_keywords",
48
- "validate_dependencies"
49
+ "validate_dependencies",
50
+ "validate_source"
49
51
  ]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: quickpub
3
- Version: 2.0.4
3
+ Version: 3.0.0
4
4
  Summary: A python package to quickly configure and publish a new package
5
5
  Author-email: danielnachumdev <danielnachumdev@gmail.com>
6
6
  License: MIT License
@@ -33,8 +33,12 @@ Classifier: Operating System :: Microsoft :: Windows
33
33
  Requires-Python: >=3.8.0
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
+ Requires-Dist: danielutils>=1.0.0
37
+ Requires-Dist: requests
38
+ Requires-Dist: fire
39
+ Dynamic: license-file
36
40
 
37
- # quickpub V2.0.2
41
+ # quickpub V3.0.0
38
42
  **Tested python versions**: `3.8.0`, `3.9.0`, `3.10.13`,
39
43
 
40
44
  Example usage of how this package was published
@@ -3,6 +3,8 @@ MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
5
  setup.py
6
+ ./LICENSE
7
+ ./README.md
6
8
  quickpub/__init__.py
7
9
  quickpub/__main__.py
8
10
  quickpub/classifiers.py
@@ -1,17 +0,0 @@
1
- import sys
2
- from typing import Union, Callable
3
-
4
- from danielutils import error
5
-
6
-
7
- def exit_if(predicate: Union[bool, Callable[[], bool]], msg: str, *, verbose: bool = True,
8
- err_func: Callable[[str], None] = error) -> None:
9
- if (isinstance(predicate, bool) and predicate) or (callable(predicate) and predicate()):
10
- if verbose:
11
- err_func(msg)
12
- sys.exit(1)
13
-
14
-
15
- __all__ = [
16
- "exit_if"
17
- ]
@@ -1,180 +0,0 @@
1
- import re
2
- import sys
3
- from functools import wraps
4
- from typing import Optional, ContextManager, List, Callable, Tuple, Dict, Union
5
- from danielutils import AttrContext, LayeredCommand, AsciiProgressBar, ColoredText, ProgressBarPool, TemporaryFile
6
-
7
- from .strategies import PythonProvider, QualityAssuranceRunner # pylint: disable=relative-beyond-top-level
8
- from .structures import Dependency, Version, Bound # pylint: disable=relative-beyond-top-level
9
- from .enforcers import exit_if # pylint: disable=relative-beyond-top-level
10
-
11
- try:
12
- from danielutils import MultiContext # type:ignore
13
- except ImportError:
14
- class MultiContext(ContextManager): # type: ignore # pylint: disable=missing-class-docstring
15
- def __init__(self, *contexts: ContextManager):
16
- self.contexts = contexts
17
-
18
- def __enter__(self):
19
- for context in self.contexts:
20
- context.__enter__()
21
- return self
22
-
23
- def __exit__(self, exc_type, exc_val, exc_tb):
24
- for context in self.contexts:
25
- context.__exit__(exc_type, exc_val, exc_tb)
26
-
27
- def __getitem__(self, index):
28
- return self.contexts[index]
29
-
30
-
31
- def global_import_sanity_check(package_name: str, executor: LayeredCommand, is_system_interpreter: bool,
32
- env_name: str, err_print_func) -> None:
33
- """
34
- Will check that importing from the package works as a sanity check.
35
- :param package_name: Name of the package
36
- :param executor: the previously ued LayeredCommand executor
37
- :param is_system_interpreter: whether or not the system interpreter is used
38
- :param env_name: The name of the currently tested environment
39
- :param err_print_func: the function to print our errors
40
- :return: None
41
- """
42
- p = sys.executable if is_system_interpreter else "python"
43
- file_name = "./__sanity_check_main.py"
44
- with TemporaryFile(file_name) as f:
45
- f.writelines([f"from {package_name} import *"])
46
- code, _, _ = executor(f"{p} {file_name}")
47
- exit_if(code != 0,
48
- f"Env '{env_name}' failed sanity check. "
49
- f"Try manually running the following script 'from {package_name} import *'",
50
- verbose=True, err_func=err_print_func)
51
-
52
-
53
- VERSION_REGEX: re.Pattern = re.compile(r"^\d+\.\d+\.\d+$")
54
-
55
-
56
- def validate_dependencies(python_manager: PythonProvider, required_dependencies: List[Dependency],
57
- executor: LayeredCommand,
58
- env_name: str, err_print_func: Callable) -> None:
59
- """
60
- will check if all the dependencies of the package are installed on current env.
61
- :param python_manager: the manager to use
62
- :param required_dependencies: the dependencies to check
63
- :param executor: the current LayeredCommand executor
64
- :param env_name: name of the currently checked environment
65
- :param err_print_func: function to print errors
66
- :return: None
67
- """
68
- if python_manager.exit_on_fail:
69
- code, out, err = executor("pip list")
70
- exit_if(code != 0, f"Failed executing 'pip list' at env '{env_name}'", err_func=err_print_func)
71
- split_lines = (line.split(' ') for line in out[2:])
72
- version_tuples = [(s[0], s[-1].strip()) for s in split_lines]
73
- filtered_tuples = [t for t in version_tuples if VERSION_REGEX.match(t[1])]
74
- currently_installed: Dict[str, Union[str, Dependency]] = {s[0]: Dependency(s[0], "==", Version.from_str(s[-1]))
75
- for s in filtered_tuples}
76
- currently_installed.update(**{t[0]: t[1] for t in version_tuples if not VERSION_REGEX.match(t[1])})
77
- not_installed_properly: List[Tuple[Dependency, str]] = []
78
- for req in required_dependencies:
79
- if req.name not in currently_installed:
80
- not_installed_properly.append((req, "dependency not found"))
81
- else:
82
- v = currently_installed[req.name]
83
- if isinstance(v, str):
84
- not_installed_properly.append(
85
- (req, "Verion format of dependecy is not currently supported by quickpub"))
86
- elif isinstance(v, Dependency):
87
- if not req.is_satisfied_by(v.ver):
88
- not_installed_properly.append((req, "Invalid version installed"))
89
-
90
- exit_if(bool(not_installed_properly),
91
- f"On env '{env_name}' the following dependencies have problems: {(not_installed_properly)}",
92
- err_func=err_print_func)
93
-
94
-
95
- def create_progress_bar_pool(python_version_manager: PythonProvider,
96
- quality_assurance_strategies: List[QualityAssuranceRunner]) -> ProgressBarPool:
97
- return ProgressBarPool(
98
- AsciiProgressBar,
99
- 2,
100
- individual_options=[
101
- dict(
102
- iterator=python_version_manager,
103
- desc="Envs",
104
- total=len(python_version_manager.requested_envs)
105
- ),
106
- dict(
107
- iterator=quality_assurance_strategies or [],
108
- desc="Runners",
109
- total=len(quality_assurance_strategies or [])
110
- ),
111
- ]
112
- )
113
-
114
-
115
- def create_pool_print_error(pool: ProgressBarPool):
116
- @wraps(pool.write)
117
- def func(*args, **kwargs):
118
- msg = "".join([ColoredText.red("ERROR"), ": ", *args])
119
- pool.write(msg, **kwargs)
120
-
121
- return func
122
-
123
-
124
- def qa(
125
- python_provider: PythonProvider,
126
- quality_assurance_strategies: List[QualityAssuranceRunner],
127
- package_name: str,
128
- src_folder_path: str,
129
- dependencies: list
130
- ) -> bool:
131
- from .strategies import DefaultPythonProvider
132
- result = True
133
- is_system_interpreter = isinstance(python_provider, DefaultPythonProvider)
134
- pool = create_progress_bar_pool(python_provider, quality_assurance_strategies)
135
- pool_err = create_pool_print_error(pool)
136
- with LayeredCommand() as base:
137
- for env_name, executor in pool[0]:
138
- pool[0].desc = f"Env '{env_name}'"
139
- pool[0].update(0, refresh=True)
140
- with executor:
141
- executor._prev_instance = base
142
- try:
143
- validate_dependencies(python_provider, dependencies, executor, env_name, pool_err)
144
- except SystemExit:
145
- result = False
146
- continue
147
- try:
148
- global_import_sanity_check(package_name, executor, is_system_interpreter, env_name, pool_err)
149
- except SystemExit:
150
- result = False
151
- continue
152
- for runner in pool[1]:
153
- pool[1].desc = f"Runner '{runner.__class__.__name__}'"
154
- pool[1].update(0, refresh=True)
155
- try:
156
- runner.run(
157
- src_folder_path,
158
- executor,
159
- use_system_interpreter=is_system_interpreter,
160
- print_func=pool_err,
161
- env_name=env_name
162
- )
163
- except SystemExit:
164
- result = False
165
- continue
166
- except Exception as e:
167
- result = False
168
- manual_command = executor._build_command(runner._build_command(src_folder_path))
169
- pool_err(
170
- f"Failed running '{runner.__class__.__name__}' on env '{env_name}'. "
171
- f"Try manually: '{manual_command}'.")
172
- pool.write(f"\tCaused by '{e.__cause__ or e}'")
173
- if python_provider.exit_on_fail:
174
- raise RuntimeError() from e
175
- return result
176
-
177
-
178
- __all__ = [
179
- 'qa'
180
- ]
@@ -1,32 +0,0 @@
1
- from typing import Tuple, Optional, Set, Iterator, List
2
- from danielutils import LayeredCommand, warning
3
-
4
- from ...python_provider import PythonProvider
5
-
6
-
7
- class CondaPythonProvider(PythonProvider):
8
- def get_python_executable_name(self) -> str:
9
- return "python"
10
-
11
- def __init__(self, env_names: List[str]) -> None:
12
- PythonProvider.__init__(self, requested_envs=env_names, explicit_versions=[], exit_on_fail=True)
13
- self._cached_available_envs: Optional[Set[str]] = None
14
-
15
- @classmethod
16
- def _get_available_envs_impl(cls) -> Set[str]:
17
- with LayeredCommand() as base:
18
- code, out, err = base("conda env list")
19
- return set([line.split(' ')[0] for line in out[2:] if len(line.split(' ')) > 1])
20
-
21
- def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]:
22
- available_envs = self.get_available_envs()
23
- for name in self.requested_envs:
24
- if name not in available_envs:
25
- warning(f"Couldn't find env '{name}'")
26
- continue
27
- yield name, LayeredCommand(f"conda activate {name}")
28
-
29
-
30
- __all__ = [
31
- 'CondaPythonProvider',
32
- ]
@@ -1,42 +0,0 @@
1
- from .quickpub_strategy import QuickpubStrategy
2
- from .quality_assurance_runner import QualityAssuranceRunner
3
- from abc import abstractmethod
4
- from typing import Tuple, Set, Iterator, List
5
- from danielutils import LayeredCommand
6
-
7
-
8
- class PythonProvider(QuickpubStrategy):
9
- def __init__(self, auto_install_dependencies: bool = True, *, requested_envs: List[str],
10
- explicit_versions: List[str],
11
- exit_on_fail: bool = False) -> None:
12
- self.auto_install_dependencies = auto_install_dependencies
13
- self.requested_envs = requested_envs
14
- self.explicit_versions = explicit_versions
15
- self.exit_on_fail = exit_on_fail
16
-
17
- @abstractmethod
18
- def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]: ...
19
-
20
- @classmethod
21
- def get_available_envs(cls) -> Set[str]:
22
- KEY = "__available_envs__"
23
- if (res := getattr(cls, KEY, None)) is not None:
24
- return res
25
-
26
- setattr(cls, KEY, res := cls._get_available_envs_impl())
27
- return res
28
-
29
- @classmethod
30
- @abstractmethod
31
- def _get_available_envs_impl(cls) -> Set[str]: ...
32
-
33
- def __len__(self) -> int:
34
- return len(self.requested_envs)
35
-
36
- @abstractmethod
37
- def get_python_executable_name(self) -> str: ...
38
-
39
-
40
- __all__ = [
41
- 'PythonProvider'
42
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes