quickpub 0.8.3__tar.gz → 1.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 (46) hide show
  1. {quickpub-0.8.3/quickpub.egg-info → quickpub-1.0.0}/PKG-INFO +1 -1
  2. {quickpub-0.8.3 → quickpub-1.0.0}/pyproject.toml +1 -1
  3. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/__init__.py +1 -0
  4. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/__main__.py +25 -22
  5. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/enforcers.py +4 -5
  6. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/files.py +6 -7
  7. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/functions.py +0 -7
  8. quickpub-1.0.0/quickpub/managers/__init__.py +2 -0
  9. quickpub-1.0.0/quickpub/managers/implementations/__init__.py +2 -0
  10. quickpub-1.0.0/quickpub/managers/implementations/conda.py +37 -0
  11. quickpub-1.0.0/quickpub/managers/implementations/system_interpreter.py +25 -0
  12. quickpub-1.0.0/quickpub/managers/python_manager.py +30 -0
  13. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/proxy.py +8 -11
  14. quickpub-1.0.0/quickpub/qa.py +159 -0
  15. quickpub-1.0.0/quickpub/runnables/base_runner.py +65 -0
  16. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/has_optional_executable.py +9 -4
  17. quickpub-1.0.0/quickpub/runnables/implementations/mypy.py +45 -0
  18. quickpub-1.0.0/quickpub/runnables/implementations/pylint.py +43 -0
  19. quickpub-1.0.0/quickpub/runnables/implementations/pytest.py +2 -0
  20. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/implementations/unittest.py +13 -6
  21. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/structures/additional_configuration.py +3 -1
  22. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/structures/bound.py +6 -0
  23. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/structures/version.py +5 -1
  24. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/validators.py +4 -7
  25. {quickpub-0.8.3 → quickpub-1.0.0/quickpub.egg-info}/PKG-INFO +1 -1
  26. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub.egg-info/SOURCES.txt +7 -2
  27. quickpub-0.8.3/quickpub/custom_types.py +0 -12
  28. quickpub-0.8.3/quickpub/runnables/common_check.py +0 -53
  29. quickpub-0.8.3/quickpub/runnables/implementations/mypy.py +0 -31
  30. quickpub-0.8.3/quickpub/runnables/implementations/pylint.py +0 -30
  31. quickpub-0.8.3/quickpub/runnables/implementations/pytest.py +0 -1
  32. {quickpub-0.8.3 → quickpub-1.0.0}/LICENSE +0 -0
  33. {quickpub-0.8.3 → quickpub-1.0.0}/MANIFEST.in +0 -0
  34. {quickpub-0.8.3 → quickpub-1.0.0}/README.md +0 -0
  35. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/classifiers.py +0 -0
  36. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/py.typed +0 -0
  37. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/__init__.py +0 -0
  38. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/configurable.py +0 -0
  39. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/implementations/__init__.py +0 -0
  40. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/runnables/runnable.py +0 -0
  41. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub/structures/__init__.py +0 -0
  42. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub.egg-info/dependency_links.txt +0 -0
  43. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub.egg-info/requires.txt +0 -0
  44. {quickpub-0.8.3 → quickpub-1.0.0}/quickpub.egg-info/top_level.txt +0 -0
  45. {quickpub-0.8.3 → quickpub-1.0.0}/setup.cfg +0 -0
  46. {quickpub-0.8.3 → quickpub-1.0.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quickpub
3
- Version: 0.8.3
3
+ Version: 1.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quickpub"
7
- version = "0.8.3"
7
+ version = "1.0.0"
8
8
  authors = [
9
9
  { name = "danielnachumdev", email = "danielnachumdev@gmail.com" },
10
10
  ]
@@ -1,3 +1,4 @@
1
1
  from .structures import *
2
2
  from .runnables import *
3
+ from .managers import *
3
4
  from .__main__ import publish
@@ -1,13 +1,13 @@
1
1
  from typing import Optional, Union, List
2
- from danielutils import warning, file_exists
2
+ from danielutils import warning, file_exists, error
3
3
  from .validators import validate_version, validate_python_version, validate_keywords, validate_dependencies, \
4
4
  validate_source
5
- from .functions import build, upload, commit, metrics
5
+ from .functions import build, upload, commit
6
6
  from .structures import Version, AdditionalConfiguration
7
7
  from .files import create_toml, create_setup, create_manifest
8
8
  from .classifiers import *
9
9
  from .enforcers import enforce_local_correct_version, enforce_pypirc_exists, exit_if, enforce_remote_correct_version
10
- from .custom_types import Path
10
+ from .qa import qa
11
11
 
12
12
 
13
13
  def publish(
@@ -17,10 +17,10 @@ def publish(
17
17
  author_email: str,
18
18
  description: str,
19
19
  homepage: str,
20
- src: Optional[Path] = None,
20
+ explicit_src_folder_path: Optional[str] = None,
21
21
  version: Optional[Union[Version, str]] = None,
22
- readme: Path = "./README.md",
23
- license: Path = "./LICENSE",
22
+ readme_file_path: str = "./README.md",
23
+ license_file_path: str = "./LICENSE",
24
24
 
25
25
  min_python: Optional[Union[Version, str]] = None,
26
26
 
@@ -36,23 +36,22 @@ def publish(
36
36
  author_email (str): The email of the author
37
37
  description (str): A short description for the package
38
38
  homepage (str): The homepage for the package. URL to the github repo is a good option.
39
- src (Optional[Path], optional): The path to the source code of the package. if None defaults to CWD/<name>
39
+ explicit_src_folder_path (Optional[Path], optional): The path to the source code of the package. if None defaults to CWD/<name>
40
40
  version (Optional[Union[Version, str]], optional): The version to create the new distribution. if None defaults to 0.0.1
41
- readme (Path, optional): The path to the readme file. Defaults to "./README.md".
42
- license (Path, optional): The path to the license file . Defaults to "./LICENSE".
41
+ readme_file_path (Path, optional): The path to the readme file. Defaults to "./README.md".
42
+ license_file_path (Path, optional): The path to the license file . Defaults to "./LICENSE".
43
43
  min_python (Optional[Union[Version, str]], optional): The minimum version of python required for this package to run. Defaults to the version of python running this script.
44
44
  keywords (Optional[list[str]], optional): A list of keywords to describe areas of interests of this package. Defaults to None.
45
45
  dependencies (Optional[list[str]], optional): A list of the dependencies for this package. Defaults to None.
46
46
  config (Optional[Config], optional): reserved for future use. Defaults to None.
47
47
  """
48
48
  enforce_pypirc_exists()
49
- src = validate_source(name, src)
50
- if src != f"./{name}":
49
+ explicit_src_folder_path = validate_source(name, explicit_src_folder_path)
50
+ if explicit_src_folder_path != f"./{name}":
51
51
  warning(
52
52
  "The source folder's name is different from the package's name. this may not be currently supported correctly")
53
- exit_if(not file_exists(readme), f"Could not find readme file at {readme}")
54
- exit_if(not file_exists(license),
55
- f"Could not find license file at {license}")
53
+ exit_if(not file_exists(readme_file_path), f"Could not find readme file at {readme_file_path}")
54
+ exit_if(not file_exists(license_file_path), f"Could not find license file at {license_file_path}")
56
55
  version = validate_version(version)
57
56
  enforce_local_correct_version(name, version)
58
57
  min_python = validate_python_version(min_python) # type:ignore
@@ -60,17 +59,22 @@ def publish(
60
59
  dependencies = validate_dependencies(dependencies)
61
60
  enforce_remote_correct_version(name, version)
62
61
 
63
- if config is not None:
64
- if config.runners is not None:
65
- for runner in config.runners:
66
- runner.run(src)
62
+ try:
63
+ if not qa(name, config, explicit_src_folder_path, dependencies):
64
+ error(
65
+ f"quickpub.publish exited early as '{name}' did not pass quality assurance step, see above logs to pass this step.")
66
+ raise SystemExit(1)
67
+ except SystemExit as e:
68
+ raise e
69
+ except Exception as e:
70
+ raise RuntimeError("Quality assurance stage has failed") from e
67
71
 
68
72
  create_setup()
69
73
  create_toml(
70
74
  name=name,
71
- src=src,
72
- readme=readme,
73
- license=license,
75
+ src_folder_path=explicit_src_folder_path,
76
+ readme_file_path=readme_file_path,
77
+ license_file_path=license_file_path,
74
78
  version=version,
75
79
  author=author,
76
80
  author_email=author_email,
@@ -96,7 +100,6 @@ def publish(
96
100
  commit(
97
101
  version=version
98
102
  )
99
- metrics()
100
103
 
101
104
  # if __name__ == '__main__':
102
105
  # publish()
@@ -1,16 +1,15 @@
1
1
  import sys
2
2
  from typing import Union, Callable
3
3
 
4
- import requests
5
- # from bs4 import BeautifulSoup
6
4
  from danielutils import directory_exists, get_files, error, file_exists, get_python_version
7
5
  from .structures import Version
8
- from .proxy import get
9
6
 
10
7
 
11
- def exit_if(predicate: Union[bool, Callable[[], bool]], msg: str) -> None:
8
+ def exit_if(predicate: Union[bool, Callable[[], bool]], msg: str, *, verbose: bool = True,
9
+ err_func: Callable[[str], None] = error) -> None:
12
10
  if (isinstance(predicate, bool) and predicate) or (callable(predicate) and predicate()):
13
- error(msg)
11
+ if verbose:
12
+ err_func(msg)
14
13
  sys.exit(1)
15
14
 
16
15
 
@@ -1,6 +1,5 @@
1
1
  from typing import List
2
2
  from danielutils import get_files
3
- from .custom_types import Path
4
3
  from .classifiers import Classifier
5
4
  from .structures import Version
6
5
 
@@ -8,9 +7,9 @@ from .structures import Version
8
7
  def create_toml(
9
8
  *,
10
9
  name: str,
11
- src: Path,
12
- readme: Path,
13
- license: Path,
10
+ src_folder_path: str,
11
+ readme_file_path: str,
12
+ license_file_path: str,
14
13
  version: Version,
15
14
  author: str,
16
15
  author_email: str,
@@ -25,7 +24,7 @@ def create_toml(
25
24
  if len(classifiers_string) > 0:
26
25
  classifiers_string = f"\n\t{classifiers_string}\n"
27
26
  py_typed = ""
28
- for file in get_files(src):
27
+ for file in get_files(src_folder_path):
29
28
  if file == "py.typed":
30
29
  py_typed = f"""[tool.setuptools.package-data]
31
30
  "{name}" = ["py.typed"]"""
@@ -43,9 +42,9 @@ authors = [
43
42
  ]
44
43
  dependencies = {dependencies}
45
44
  keywords = {keywords}
46
- license = {{ "file" = "{license}" }}
45
+ license = {{ "file" = "{license_file_path}" }}
47
46
  description = "{description}"
48
- readme = {{file = "{readme}", content-type = "text/markdown"}}
47
+ readme = {{file = "{readme_file_path}", content-type = "text/markdown"}}
49
48
  requires-python = ">={min_python}"
50
49
  classifiers = [{classifiers_string}]
51
50
 
@@ -7,8 +7,6 @@ from .structures import Version
7
7
  import quickpub.proxy
8
8
 
9
9
 
10
-
11
-
12
10
  def prev_main():
13
11
  import re
14
12
  from danielutils import cmrt, cm, read_file, get_files, directory_exists, create_directory # type:ignore
@@ -204,13 +202,8 @@ def commit(
204
202
  )
205
203
 
206
204
 
207
- def metrics(testing_client: Optional[Literal["pytest", "unitest"]] = None):
208
- pass
209
-
210
-
211
205
  __all__ = [
212
206
  "build",
213
207
  "upload",
214
208
  "commit",
215
- "metrics"
216
209
  ]
@@ -0,0 +1,2 @@
1
+ from .python_manager import *
2
+ from .implementations import *
@@ -0,0 +1,2 @@
1
+ from .conda import *
2
+ from .system_interpreter import *
@@ -0,0 +1,37 @@
1
+ from typing import Tuple, Optional, Set, Iterator
2
+ from danielutils import LayeredCommand, warning
3
+
4
+ from ..python_manager import PythonManager
5
+
6
+
7
+ class CondaPythonManager(PythonManager):
8
+ def get_python_executable_name(self) -> str:
9
+ return "python"
10
+
11
+ def __init__(self, env_names: list[str]) -> None:
12
+ PythonManager.__init__(self, requested_envs=env_names, explicit_versions=[])
13
+ self._cached_available_envs: Optional[Set[str]] = None
14
+
15
+ def get_available_envs(self) -> Set[str]:
16
+ if self._cached_available_envs is not None:
17
+ return self._cached_available_envs
18
+
19
+ with LayeredCommand(instance_flush_stdout=False, instance_flush_stderr=False) as base:
20
+ code, out, err = base("conda env list")
21
+ res = set([line.split(' ')[0] for line in out[2:] if len(line.split(' ')) > 1])
22
+
23
+ self._cached_available_envs = res
24
+ return res
25
+
26
+ def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]:
27
+ available_envs = self.get_available_envs()
28
+ for name in self.requested_envs:
29
+ if name not in available_envs:
30
+ warning(f"Couldn't find env '{name}'")
31
+ continue
32
+ yield name, LayeredCommand(f"conda activate {name}")
33
+
34
+
35
+ __all__ = [
36
+ 'CondaPythonManager',
37
+ ]
@@ -0,0 +1,25 @@
1
+ import sys
2
+ from typing import Set, Tuple, Iterator
3
+
4
+ from danielutils import LayeredCommand
5
+
6
+ from .. import PythonManager
7
+
8
+
9
+ class SystemInterpreter(PythonManager):
10
+ def get_python_executable_name(self) -> str:
11
+ return sys.executable
12
+
13
+ def __init__(self) -> None:
14
+ PythonManager.__init__(self, requested_envs=["system"], explicit_versions=[], exit_on_fail=True)
15
+
16
+ def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]:
17
+ return iter([("system", LayeredCommand(""))])
18
+
19
+ def get_available_envs(self) -> Set[str]:
20
+ return set("system")
21
+
22
+
23
+ __all__ = [
24
+ "SystemInterpreter",
25
+ ]
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Tuple, Set, Iterator
3
+ from danielutils import LayeredCommand
4
+
5
+
6
+ class PythonManager(ABC):
7
+ def __init__(self, auto_install_dependencies: bool = True, *, requested_envs: list[str],
8
+ explicit_versions: list[str],
9
+ exit_on_fail: bool = False) -> None:
10
+ self.auto_install_dependencies = auto_install_dependencies
11
+ self.requested_envs = requested_envs
12
+ self.explicit_versions = explicit_versions
13
+ self.exit_on_fail = exit_on_fail
14
+
15
+ @abstractmethod
16
+ def __iter__(self) -> Iterator[Tuple[str, LayeredCommand]]: ...
17
+
18
+ @abstractmethod
19
+ def get_available_envs(self) -> Set[str]: ...
20
+
21
+ def __len__(self) -> int:
22
+ return len(self.requested_envs)
23
+
24
+ @abstractmethod
25
+ def get_python_executable_name(self) -> str: ...
26
+
27
+
28
+ __all__ = [
29
+ 'PythonManager'
30
+ ]
@@ -1,6 +1,7 @@
1
+ import os
2
+ from typing import Tuple
1
3
  import danielutils
2
4
  import requests
3
- from typing import Tuple
4
5
 
5
6
 
6
7
  # need it like this for the testing
@@ -8,20 +9,16 @@ def cm(*args, **kwargs) -> Tuple[int, bytes, bytes]:
8
9
  return danielutils.cm(*args, **kwargs)
9
10
 
10
11
 
11
- def get(*args, **kwargs):
12
- return requests.get(*args, **kwargs)
12
+ def os_system(command) -> int:
13
+ return os.system(command)
13
14
 
14
15
 
15
- def add_verbose_keyword(func):
16
- def wrapper(*args, verbose: bool = False, **kwargs):
17
- if verbose:
18
- return func(*args, **kwargs)
19
-
20
- return wrapper
16
+ def get(*args, **kwargs):
17
+ return requests.get(*args, **kwargs)
21
18
 
22
19
 
23
20
  __all__ = [
24
21
  "cm",
25
- "get",
26
- 'add_verbose_keyword'
22
+ 'os_system',
23
+ "get"
27
24
  ]
@@ -0,0 +1,159 @@
1
+ import sys
2
+ from functools import wraps
3
+ from typing import Optional, ContextManager, List, Callable
4
+ from danielutils import AttrContext, LayeredCommand, AsciiProgressBar, ColoredText, ProgressBarPool, TemporaryFile
5
+
6
+ from .managers import PythonManager # pylint: disable=relative-beyond-top-level
7
+ from .structures import AdditionalConfiguration # pylint: disable=relative-beyond-top-level
8
+ from .enforcers import exit_if # pylint: disable=relative-beyond-top-level
9
+
10
+ try:
11
+ from danielutils import MultiContext # type:ignore
12
+ except ImportError:
13
+ class MultiContext(ContextManager): # pylint: disable=missing-class-docstring #type:ignore
14
+ def __init__(self, *contexts: ContextManager):
15
+ self.contexts = contexts
16
+
17
+ def __enter__(self):
18
+ for context in self.contexts:
19
+ context.__enter__()
20
+ return self
21
+
22
+ def __exit__(self, exc_type, exc_val, exc_tb):
23
+ for context in self.contexts:
24
+ context.__exit__(exc_type, exc_val, exc_tb)
25
+
26
+ def __getitem__(self, index):
27
+ return self.contexts[index]
28
+
29
+
30
+ def global_import_sanity_check(package_name: str, executor: LayeredCommand, is_system_interpreter: bool,
31
+ env_name: str, err_print_func) -> None:
32
+ """
33
+ Will check that importing from the package works as a sanity check.
34
+ :param package_name: Name of the package
35
+ :param executor: the previously ued LayeredCommand executor
36
+ :param is_system_interpreter: whether or not the system interpreter is used
37
+ :param env_name: The name of the currently tested environment
38
+ :param err_print_func: the function to print our errors
39
+ :return: None
40
+ """
41
+ p = sys.executable if is_system_interpreter else "python"
42
+ file_name = "./__sanity_check_main.py"
43
+ with TemporaryFile(file_name) as f:
44
+ f.write([f"from {package_name} import *"])
45
+ code, _, _ = executor(f"{p} {file_name}")
46
+ exit_if(code != 0,
47
+ f"Env '{env_name}' failed sanity check. "
48
+ f"Try manually running the following script 'from {package_name} import *'",
49
+ verbose=True, err_func=err_print_func)
50
+
51
+
52
+ def validate_dependencies(python_manager: PythonManager, dependencies: List[str], executor: LayeredCommand,
53
+ env_name: str, err_print_func: Callable) -> None:
54
+ """
55
+ will check if all the dependencies of the package are installed on current env.
56
+ :param python_manager: the manager to use
57
+ :param dependencies: the dependencies to check
58
+ :param executor: the current LayeredCommand executor
59
+ :param env_name: name of the currently checked environment
60
+ :param err_print_func: function to print errors
61
+ :return: None
62
+ """
63
+ if python_manager.exit_on_fail:
64
+ code, out, err = executor("pip list")
65
+ exit_if(code != 0, f"Failed executing 'pip list' at env '{env_name}'", err_func=err_print_func)
66
+ installed = [line.split(' ')[0] for line in out[2:]]
67
+ not_installed = []
68
+ for dep in dependencies:
69
+ if dep not in installed:
70
+ not_installed.append(dep)
71
+ exit_if(not (len(not_installed) == 0),
72
+ f"On env '{env_name}' the following dependencies are not installed: {not_installed}",
73
+ err_func=err_print_func)
74
+
75
+
76
+ def create_progress_bar_pool(config, python_manager) -> ProgressBarPool:
77
+ return ProgressBarPool(
78
+ AsciiProgressBar,
79
+ 2,
80
+ individual_options=[
81
+ dict(iterable=python_manager, desc="Envs", total=len(python_manager.requested_envs)),
82
+ dict(iterable=config.runners or [], desc="Runners", total=len(config.runners or [])),
83
+ ]
84
+ )
85
+
86
+
87
+ def create_pool_print_error(pool: ProgressBarPool):
88
+ @wraps(pool.write)
89
+ def func(*args, **kwargs):
90
+ msg = "".join([ColoredText.red("ERROR"), ": ", *args])
91
+ pool.write(msg, **kwargs)
92
+
93
+ return func
94
+
95
+
96
+ def qa(package_name: str, config: Optional[AdditionalConfiguration], src_folder_path: Optional[str],
97
+ dependencies: list) -> bool:
98
+ if config is None:
99
+ return True
100
+ result = True
101
+ python_manager = config.python_manager
102
+ is_system_interpreter: bool = False
103
+ if python_manager is None:
104
+ from .managers import SystemInterpreter
105
+ python_manager = SystemInterpreter()
106
+ is_system_interpreter = True
107
+ pool = create_progress_bar_pool(config, python_manager)
108
+ pool_err = create_pool_print_error(pool)
109
+ with MultiContext(
110
+ AttrContext(LayeredCommand, 'class_flush_stdout', False),
111
+ AttrContext(LayeredCommand, 'class_flush_stderr', False),
112
+ AttrContext(LayeredCommand, 'class_raise_on_fail', False),
113
+ base := LayeredCommand()
114
+ ):
115
+ for env_name, executor in pool[0]:
116
+ pool[0].desc = f"Env '{env_name}'"
117
+ pool[0].update(0, refresh=True)
118
+ with executor:
119
+ executor._prev_instance = base
120
+ try:
121
+ validate_dependencies(python_manager, dependencies, executor, env_name, pool_err)
122
+ except SystemExit:
123
+ result = False
124
+ continue
125
+ try:
126
+ global_import_sanity_check(package_name, executor, is_system_interpreter, env_name, pool_err)
127
+ except SystemExit:
128
+ result = False
129
+ continue
130
+ for runner in pool[1]:
131
+ pool[1].desc = f"Runner '{runner.__class__.__name__}'"
132
+ pool[1].update(0, refresh=True)
133
+ try:
134
+ runner.run(
135
+ src_folder_path,
136
+ executor,
137
+ use_system_interpreter=is_system_interpreter,
138
+ raise_on_fail=python_manager.exit_on_fail,
139
+ print_func=pool_err,
140
+ env_name=env_name
141
+ )
142
+ except SystemExit:
143
+ result = False
144
+ continue
145
+ except Exception as e:
146
+ result = False
147
+ manual_command = executor._build_command(runner._build_command(src_folder_path))
148
+ pool_err(
149
+ f"Failed running '{runner.__class__.__name__}' on env '{env_name}'. "
150
+ f"Try manually: '{manual_command}'.")
151
+ pool.write(f"\tCaused by '{e.__cause__ or e}'")
152
+ if python_manager.exit_on_fail:
153
+ raise e
154
+ return result
155
+
156
+
157
+ __all__ = [
158
+ 'qa'
159
+ ]
@@ -0,0 +1,65 @@
1
+ from abc import abstractmethod
2
+ from typing import Optional, Union, List
3
+
4
+ from danielutils import LayeredCommand
5
+
6
+ from .has_optional_executable import HasOptionalExecutable
7
+ from .runnable import Runnable
8
+ from .configurable import Configurable
9
+ from ..structures import Bound
10
+ from ..proxy import os_system
11
+
12
+
13
+ class BaseRunner(Runnable, Configurable, HasOptionalExecutable):
14
+
15
+ def __init__(self, *, name: str, bound: Union[str, Bound], target: Optional[str] = None,
16
+ configuration_path: Optional[str] = None,
17
+ executable_path: Optional[str] = None, auto_install: bool = False) -> None:
18
+ Configurable.__init__(self, configuration_path)
19
+ HasOptionalExecutable.__init__(self, name, executable_path)
20
+ self.bound: Bound = bound if isinstance(bound, Bound) else Bound.from_string(bound)
21
+ self.target = target
22
+
23
+ @abstractmethod
24
+ def _build_command(self, target: str, use_system_interpreter: bool = False) -> str:
25
+ ...
26
+
27
+ @abstractmethod
28
+ def _install_dependencies(self, base: LayeredCommand) -> None:
29
+ ...
30
+
31
+ def _pre_command(self) -> None:
32
+ pass
33
+
34
+ def _post_command(self) -> None:
35
+ pass
36
+
37
+ def run(self, target: str, executor: LayeredCommand, *_, verbose: bool = True, # type: ignore
38
+ use_system_interpreter: bool = False, raise_on_fail: bool = False, print_func, env_name: str) -> None:
39
+ # =====================================
40
+ # IMPORTANT: need to explicitly override it here
41
+ executor._executor = os_system
42
+ # =====================================
43
+ command = self._build_command(target, use_system_interpreter)
44
+ self._pre_command()
45
+ try:
46
+ ret, out, err = executor(command, command_raise_on_fail=raise_on_fail)
47
+ score = self._calculate_score(ret, "".join(out + err).splitlines(), verbose=verbose)
48
+ from ..enforcers import exit_if
49
+ exit_if(not self.bound.compare_against(score),
50
+ f"On env '{env_name}' runner '{self.__class__.__name__}' failed to pass it's defined bound. Got a score of {score} but expected {self.bound}",
51
+ verbose=verbose, err_func=print_func)
52
+ except Exception as e:
53
+ raise RuntimeError(
54
+ f"On env {env_name}, failed to run {self.__class__.__name__}. Try running manually:\n{executor._build_command(command)}") from e
55
+ finally:
56
+ self._post_command()
57
+
58
+ @abstractmethod
59
+ def _calculate_score(self, ret: int, command_output: List[str], *, verbose: bool = False) -> float:
60
+ ...
61
+
62
+
63
+ __all__ = [
64
+ "BaseRunner"
65
+ ]
@@ -1,4 +1,5 @@
1
- from typing import Any, Protocol, Optional
1
+ import sys
2
+ from typing import Any, Protocol, Optional, cast
2
3
  from danielutils import get_os, OSType, file_exists
3
4
 
4
5
 
@@ -16,10 +17,14 @@ class HasOptionalExecutable():
16
17
  if not file_exists(self.executable_path):
17
18
  raise FileNotFoundError(f"Executable not found {self.executable_path}")
18
19
 
19
- def get_executable(self) -> str:
20
+ def get_executable(self, use_system_interpreter: bool = False) -> str:
20
21
  if self.use_executable:
21
- return self.executable_path
22
- return f"{self.PYTHON} -m {self.name}"
22
+ return cast(str, self.executable_path)
23
+
24
+ p = self.PYTHON
25
+ if use_system_interpreter:
26
+ p = sys.executable
27
+ return f"{p} -m {self.name}"
23
28
 
24
29
 
25
30
  __all__ = [
@@ -0,0 +1,45 @@
1
+ import re
2
+ from typing import Optional, List
3
+
4
+ from danielutils import LayeredCommand
5
+
6
+ from ..base_runner import BaseRunner
7
+
8
+
9
+ class MypyRunner(BaseRunner):
10
+ def _install_dependencies(self, base: LayeredCommand) -> None:
11
+ with base:
12
+ base("pip install pylint")
13
+
14
+ def _build_command(self, target: str, use_system_interpreter: bool = False) -> str:
15
+ command: str = self.get_executable(use_system_interpreter)
16
+ if self.has_config:
17
+ command += f" --config-file {self.config_path}"
18
+ command += f" {target}"
19
+ return command
20
+
21
+ RATING_PATTERN: re.Pattern = re.compile(
22
+ "Found (\d+(?:\.\d+)?) errors in (\d+(?:\.\d+)?) files \(checked (\d+(?:\.\d+)?) source files\)")
23
+
24
+ def __init__(self, bound: str = "<15", configuration_path: Optional[str] = None,
25
+ executable_path: Optional[str] = None) -> None:
26
+ BaseRunner.__init__(self, name="mypy", bound=bound, configuration_path=configuration_path,
27
+ executable_path=executable_path)
28
+
29
+ def _calculate_score(self, ret, lines: List[str], verbose: bool = False) -> float:
30
+ from ...enforcers import exit_if
31
+ rating_line = lines[-1]
32
+ if rating_line.startswith("Success"):
33
+ return 0.0
34
+ exit_if(not (m := self.RATING_PATTERN.match(rating_line)),
35
+ f"Failed running MyPy, got exit code {ret}. try running manually using:\n\t{self._build_command('TARGET')}",
36
+ verbose=verbose)
37
+ num_failed = float(m.group(1))
38
+ active_files = float(m.group(2))
39
+ total_files = float(m.group(3))
40
+ return num_failed
41
+
42
+
43
+ __all__ = [
44
+ 'MypyRunner',
45
+ ]
@@ -0,0 +1,43 @@
1
+ import re
2
+ from typing import Optional, List
3
+
4
+ from danielutils import LayeredCommand
5
+
6
+ from ..base_runner import BaseRunner
7
+
8
+
9
+ class PylintRunner(BaseRunner):
10
+ def _install_dependencies(self, base: LayeredCommand) -> None:
11
+ with base:
12
+ base("pip install pylint")
13
+
14
+ RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
15
+
16
+ def __init__(self, bound: str = ">=0.8", configuration_path: Optional[str] = None,
17
+ executable_path: Optional[str] = None) -> None:
18
+ BaseRunner.__init__(self, name="pylint", bound=bound, configuration_path=configuration_path,
19
+ executable_path=executable_path)
20
+
21
+ def _build_command(self, target: str, use_system_interpreter: bool = False) -> str:
22
+ command: str = self.get_executable()
23
+ if self.has_config:
24
+ command += f" --rcfile {self.config_path}"
25
+ command += f" {target}"
26
+ return command
27
+
28
+ def _calculate_score(self, ret: int, lines: List[str], verbose: bool = False) -> float:
29
+ from ...enforcers import exit_if
30
+ if len(lines) == 1 and lines[0].endswith("No module named pylint"):
31
+ raise RuntimeError(f"No module named pylint found")
32
+ rating_line = lines[-2]
33
+ m = self.RATING_PATTERN.match(rating_line)
34
+ msg = f"Failed running Pylint, got exit code {ret}. Try running manually using:\n\t{self._build_command('TARGET')}"
35
+ exit_if(not m, msg)
36
+ rating_string = m.group(1) # type:ignore
37
+ numerator, denominator = rating_string.split("/")
38
+ return float(numerator) / float(denominator)
39
+
40
+
41
+ __all__ = [
42
+ "PylintRunner",
43
+ ]
@@ -0,0 +1,2 @@
1
+ from ..base_runner import BaseRunner
2
+ # TODO
@@ -1,13 +1,20 @@
1
1
  import re
2
2
  import os
3
3
  from typing import Optional, List
4
- from danielutils import get_current_working_directory, set_current_working_directory
5
- from ..common_check import CommonCheck
4
+ from danielutils import get_current_working_directory, set_current_working_directory, LayeredCommand, warning
6
5
 
6
+ from ..base_runner import BaseRunner
7
+
8
+
9
+ class UnittestRunner(BaseRunner):
10
+ def _install_dependencies(self, base: LayeredCommand) -> None:
11
+ return None
7
12
 
8
- class UnittestRunner(CommonCheck):
9
13
  def _pre_command(self):
10
14
  self._cwd = get_current_working_directory()
15
+ if self.target is None:
16
+ self.target = ""
17
+ warning("This is not supposed to happen. See quickpub's UnitestRunner._pre_command")
11
18
  set_current_working_directory(os.path.join(self._cwd, self.target))
12
19
 
13
20
  def _post_command(self):
@@ -16,16 +23,16 @@ class UnittestRunner(CommonCheck):
16
23
  RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
17
24
 
18
25
  def __init__(self, target: Optional[str] = "./tests", bound: str = ">=0.8") -> None:
19
- CommonCheck.__init__(self, "unittest", bound, target)
26
+ BaseRunner.__init__(self, name="unittest", bound=bound, target=target)
20
27
  self._cwd = ""
21
28
 
22
- def _build_command(self, src: str, *args) -> str:
29
+ def _build_command(self, src: str, *args, use_system_interpreter: bool = False) -> str:
23
30
  command: str = self.get_executable()
24
31
  rel = os.path.relpath(src, self.target).removesuffix(src.lstrip("./\\"))
25
32
  command += f" discover -s {rel}"
26
33
  return command # f"cd {self.target}; {command}" # f"; cd {self.target}"
27
34
 
28
- def _calculate_score(self, ret: int, lines: List[str]) -> float:
35
+ def _calculate_score(self, ret: int, lines: List[str], *, verbose: bool = False) -> float:
29
36
  from ...enforcers import exit_if
30
37
  num_tests_line = lines[-3]
31
38
  num_failed_line = lines[-1] if lines[-1] != "OK" else "0"
@@ -1,11 +1,13 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Optional, List
3
3
  from ..runnables import Runnable
4
+ from ..managers import PythonManager
4
5
 
5
6
 
6
7
  @dataclass(frozen=True)
7
8
  class AdditionalConfiguration:
8
- runners: Optional[List[Runnable]]
9
+ python_manager: Optional[PythonManager] = None
10
+ runners: Optional[List[Runnable]] = None
9
11
 
10
12
 
11
13
  __all__ = [
@@ -24,6 +24,12 @@ class Bound:
24
24
  return Bound(op, float(splits[-1])) # type:ignore
25
25
  raise ValueError("Invalid 'Bound' format")
26
26
 
27
+ def __str__(self) -> str:
28
+ return f"{self.operator}{self.value}"
29
+
30
+ def __repr__(self) -> str:
31
+ return f"{self.__class__.__name__}(operator='{self.operator}', value='{self.value}')"
32
+
27
33
 
28
34
  __all__ = [
29
35
  'Bound'
@@ -5,7 +5,11 @@ from dataclasses import dataclass
5
5
  class Version:
6
6
  @staticmethod
7
7
  def from_str(version_str: str) -> "Version":
8
- return Version(*list(map(int, version_str.split("."))))
8
+ try:
9
+ return Version(*list(map(int, version_str.split("."))))
10
+ except Exception as e:
11
+ raise ValueError(f"Failed converting '{version_str}' to instance of 'Version' in 'Version.from_str") from e
12
+
9
13
 
10
14
  major: int = 0
11
15
  minor: int = 0
@@ -1,16 +1,13 @@
1
1
  from typing import Optional, Union, List
2
2
 
3
3
  from danielutils import get_python_version
4
- from .custom_types import Path
5
4
  from .structures import Version
6
5
 
7
6
 
8
- def validate_version(version: Optional[Union[str, Version]]) -> Version:
7
+ def validate_version(version: Optional[Union[str, Version]] = None) -> Version:
9
8
  if version is None:
10
- version = Version(0, 0, 1)
11
- else:
12
- version: Version = version if isinstance(version, Version) else Version.from_str(version) # type: ignore
13
- return version
9
+ return Version(0, 0, 1)
10
+ return version if isinstance(version, Version) else Version.from_str(version) # type: ignore
14
11
 
15
12
 
16
13
  def validate_python_version(min_python: Optional[Version]) -> Version:
@@ -31,7 +28,7 @@ def validate_dependencies(dependencies: Optional[List[str]]) -> List[str]:
31
28
  return dependencies
32
29
 
33
30
 
34
- def validate_source(name: str, src: Optional[Path] = None) -> Path:
31
+ def validate_source(name: str, src: Optional[str] = None) -> str:
35
32
  if src is not None:
36
33
  return src
37
34
  return f"./{name}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quickpub
3
- Version: 0.8.3
3
+ Version: 1.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
@@ -6,20 +6,25 @@ setup.py
6
6
  quickpub/__init__.py
7
7
  quickpub/__main__.py
8
8
  quickpub/classifiers.py
9
- quickpub/custom_types.py
10
9
  quickpub/enforcers.py
11
10
  quickpub/files.py
12
11
  quickpub/functions.py
13
12
  quickpub/proxy.py
14
13
  quickpub/py.typed
14
+ quickpub/qa.py
15
15
  quickpub/validators.py
16
16
  quickpub.egg-info/PKG-INFO
17
17
  quickpub.egg-info/SOURCES.txt
18
18
  quickpub.egg-info/dependency_links.txt
19
19
  quickpub.egg-info/requires.txt
20
20
  quickpub.egg-info/top_level.txt
21
+ quickpub/managers/__init__.py
22
+ quickpub/managers/python_manager.py
23
+ quickpub/managers/implementations/__init__.py
24
+ quickpub/managers/implementations/conda.py
25
+ quickpub/managers/implementations/system_interpreter.py
21
26
  quickpub/runnables/__init__.py
22
- quickpub/runnables/common_check.py
27
+ quickpub/runnables/base_runner.py
23
28
  quickpub/runnables/configurable.py
24
29
  quickpub/runnables/has_optional_executable.py
25
30
  quickpub/runnables/runnable.py
@@ -1,12 +0,0 @@
1
- from typing import TypeVar
2
-
3
- try:
4
- from typing import TypeAlias
5
- except:
6
- from typing_extensions import TypeAlias
7
-
8
- Path: TypeAlias = TypeVar("Path", bound=str)
9
-
10
- __all__ = [
11
- "Path"
12
- ]
@@ -1,53 +0,0 @@
1
- from abc import abstractmethod
2
- from typing import Optional, Union, List
3
-
4
- from danielutils import cm, info
5
-
6
- from .has_optional_executable import HasOptionalExecutable
7
- from .runnable import Runnable
8
- from .configurable import Configurable
9
- from ..structures import Bound
10
-
11
-
12
- class CommonCheck(Runnable, Configurable, HasOptionalExecutable):
13
-
14
- def __init__(self, name: str, bound: Union[str, Bound], target: Optional[str] = None,
15
- configuration_path: Optional[str] = None,
16
- executable_path: Optional[str] = None) -> None:
17
- Configurable.__init__(self, configuration_path)
18
- HasOptionalExecutable.__init__(self, name, executable_path)
19
- self.bound: Bound = bound if isinstance(bound, Bound) else Bound.from_string(bound)
20
- self.target = target
21
-
22
- @abstractmethod
23
- def _build_command(self, target: str) -> str:
24
- ...
25
-
26
- def _pre_command(self):
27
- pass
28
-
29
- def _post_command(self):
30
- pass
31
-
32
- def run(self, target: str, *_) -> None:
33
- command = self._build_command(target)
34
- info(f"Running {self.name}")
35
- self._pre_command()
36
- try:
37
- ret, out, err = cm(command)
38
- score = self._calculate_score(ret, b"".join([out, err]).decode("utf-8").splitlines())
39
- from ..enforcers import exit_if
40
- exit_if(not self.bound.compare_against(score), f"{self.name} failed to pass it's defined bound")
41
- except Exception as e:
42
- raise RuntimeError(f"Failed to run {self.name}, try running manually:\n{self._build_command('TARGET')}") from e
43
- finally:
44
- self._post_command()
45
-
46
-
47
- @abstractmethod
48
- def _calculate_score(self, ret: int, command_output: List[str]) -> float: ...
49
-
50
-
51
- __all__ = [
52
- "CommonCheck"
53
- ]
@@ -1,31 +0,0 @@
1
- import re
2
- from typing import Optional, List
3
- from ..common_check import CommonCheck
4
-
5
-
6
- class MypyRunner(CommonCheck):
7
- def _build_command(self, target: str) -> str:
8
- command: str = self.get_executable()
9
- if self.has_config:
10
- command += f" --config-file {self.config_path}"
11
- command += f" {target}"
12
- return command
13
-
14
- RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
15
-
16
- def __init__(self, bound: str = "<15", configuration_path: Optional[str] = None,
17
- executable_path: Optional[str] = None) -> None:
18
- CommonCheck.__init__(self, "mypy", bound, configuration_path, executable_path)
19
-
20
- def _calculate_score(self, ret, lines: List[str]) -> float:
21
- from ...enforcers import exit_if
22
- rating_line = lines[-1]
23
- exit_if(not (m := self.RATING_PATTERN.match(rating_line)),
24
- f"Failed running MyPy, got exit code {ret}. try running manually using:\n\t{self._build_command('TARGE')}")
25
- rating_string = m.group(1)
26
- return float(rating_string)
27
-
28
-
29
- __all__ = [
30
- 'MypyRunner',
31
- ]
@@ -1,30 +0,0 @@
1
- import re
2
- from typing import Optional, List
3
- from ..common_check import CommonCheck
4
-
5
-
6
- class PylintRunner(CommonCheck):
7
- RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
8
- def __init__(self, bound: str = ">=0.8", configuration_path: Optional[str] = None,
9
- executable_path: Optional[str] = None) -> None:
10
- CommonCheck.__init__(self, "pylint", bound, configuration_path, executable_path)
11
-
12
- def _build_command(self, target: str) -> str:
13
- command: str = self.get_executable()
14
- if self.has_config:
15
- command += f" --rcfile {self.config_path}"
16
- command += f" {target}"
17
- return command
18
- def _calculate_score(self, ret: int, lines: List[str]) -> float:
19
- from ...enforcers import exit_if
20
- rating_line = lines[-2]
21
- exit_if(not (m := self.RATING_PATTERN.match(rating_line)),
22
- f"Failed running Pylint, got exit code {ret}. try running manually using:\n\t{self._build_command('TARGET')}")
23
- rating_string = m.group(1) # type:ignore
24
- numerator, denominator = rating_string.split("/")
25
- return float(numerator) / float(denominator)
26
-
27
-
28
- __all__ = [
29
- "PylintRunner",
30
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes