quickpub 0.5.0__tar.gz → 0.8.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 (42) hide show
  1. quickpub-0.8.0/LICENSE +21 -0
  2. quickpub-0.8.0/MANIFEST.in +1 -0
  3. {quickpub-0.5.0 → quickpub-0.8.0}/PKG-INFO +33 -2
  4. quickpub-0.8.0/README.md +31 -0
  5. {quickpub-0.5.0 → quickpub-0.8.0}/pyproject.toml +6 -4
  6. quickpub-0.8.0/quickpub/__init__.py +3 -0
  7. quickpub-0.8.0/quickpub/__main__.py +102 -0
  8. quickpub-0.8.0/quickpub/custom_types.py +12 -0
  9. quickpub-0.8.0/quickpub/enforcers.py +58 -0
  10. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub/files.py +18 -5
  11. quickpub-0.5.0/quickpub/publish.py → quickpub-0.8.0/quickpub/functions.py +34 -14
  12. quickpub-0.8.0/quickpub/proxy.py +26 -0
  13. quickpub-0.8.0/quickpub/py.typed +0 -0
  14. quickpub-0.8.0/quickpub/runnables/__init__.py +2 -0
  15. quickpub-0.8.0/quickpub/runnables/common_check.py +57 -0
  16. quickpub-0.8.0/quickpub/runnables/configurable.py +20 -0
  17. quickpub-0.8.0/quickpub/runnables/has_optional_executable.py +27 -0
  18. quickpub-0.8.0/quickpub/runnables/implementations/__init__.py +4 -0
  19. quickpub-0.8.0/quickpub/runnables/implementations/mypy.py +29 -0
  20. quickpub-0.8.0/quickpub/runnables/implementations/pylint.py +30 -0
  21. quickpub-0.8.0/quickpub/runnables/implementations/pytest.py +1 -0
  22. quickpub-0.8.0/quickpub/runnables/implementations/unittest.py +50 -0
  23. quickpub-0.8.0/quickpub/runnables/runnable.py +11 -0
  24. quickpub-0.8.0/quickpub/structures/__init__.py +3 -0
  25. quickpub-0.8.0/quickpub/structures/additional_configuration.py +13 -0
  26. quickpub-0.8.0/quickpub/structures/bound.py +30 -0
  27. quickpub-0.8.0/quickpub/structures/version.py +27 -0
  28. quickpub-0.8.0/quickpub/validators.py +46 -0
  29. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub.egg-info/PKG-INFO +33 -2
  30. quickpub-0.8.0/quickpub.egg-info/SOURCES.txt +34 -0
  31. quickpub-0.5.0/README.md +0 -1
  32. quickpub-0.5.0/quickpub/__init__.py +0 -6
  33. quickpub-0.5.0/quickpub/__main__.py +0 -67
  34. quickpub-0.5.0/quickpub/proxy.py +0 -11
  35. quickpub-0.5.0/quickpub/structures.py +0 -28
  36. quickpub-0.5.0/quickpub.egg-info/SOURCES.txt +0 -15
  37. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub/classifiers.py +0 -0
  38. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub.egg-info/dependency_links.txt +0 -0
  39. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub.egg-info/requires.txt +0 -0
  40. {quickpub-0.5.0 → quickpub-0.8.0}/quickpub.egg-info/top_level.txt +0 -0
  41. {quickpub-0.5.0 → quickpub-0.8.0}/setup.cfg +0 -0
  42. {quickpub-0.5.0 → quickpub-0.8.0}/setup.py +0 -0
quickpub-0.8.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 danielnachumdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include quickpub *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quickpub
3
- Version: 0.5.0
3
+ Version: 0.8.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
@@ -30,7 +30,38 @@ Classifier: Development Status :: 3 - Alpha
30
30
  Classifier: Intended Audience :: Developers
31
31
  Classifier: Programming Language :: Python :: 3
32
32
  Classifier: Operating System :: Microsoft :: Windows
33
- Requires-Python: >=3.10.13
33
+ Requires-Python: >=3.9.19
34
34
  Description-Content-Type: text/markdown
35
+ License-File: LICENSE
35
36
 
36
37
  # quickpub
38
+
39
+ Example usage of how this package was published
40
+ ```python
41
+ from quickpub import publish, AdditionalConfiguration, MypyRunner, PylintRunner, UnittestRunner
42
+
43
+
44
+ def main() -> None:
45
+ publish(
46
+ name="quickpub",
47
+ version="0.8.0",
48
+ author="danielnachumdev",
49
+ author_email="danielnachumdev@gmail.com",
50
+ description="A python package to quickly configure and publish a new package",
51
+ homepage="https://github.com/danielnachumdev/quickpub",
52
+ dependencies=["twine", "danielutils"],
53
+ min_python="3.9.19",
54
+ config=AdditionalConfiguration(
55
+ runners=[
56
+ MypyRunner(),
57
+ PylintRunner(),
58
+ UnittestRunner(),
59
+ ]
60
+ )
61
+ )
62
+
63
+
64
+ if __name__ == '__main__':
65
+ main()
66
+
67
+ ```
@@ -0,0 +1,31 @@
1
+ # quickpub
2
+
3
+ Example usage of how this package was published
4
+ ```python
5
+ from quickpub import publish, AdditionalConfiguration, MypyRunner, PylintRunner, UnittestRunner
6
+
7
+
8
+ def main() -> None:
9
+ publish(
10
+ name="quickpub",
11
+ version="0.8.0",
12
+ author="danielnachumdev",
13
+ author_email="danielnachumdev@gmail.com",
14
+ description="A python package to quickly configure and publish a new package",
15
+ homepage="https://github.com/danielnachumdev/quickpub",
16
+ dependencies=["twine", "danielutils"],
17
+ min_python="3.9.19",
18
+ config=AdditionalConfiguration(
19
+ runners=[
20
+ MypyRunner(),
21
+ PylintRunner(),
22
+ UnittestRunner(),
23
+ ]
24
+ )
25
+ )
26
+
27
+
28
+ if __name__ == '__main__':
29
+ main()
30
+
31
+ ```
@@ -4,16 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quickpub"
7
- version = "0.5.0"
7
+ version = "0.8.0"
8
8
  authors = [
9
9
  { name = "danielnachumdev", email = "danielnachumdev@gmail.com" },
10
10
  ]
11
11
  dependencies = ['twine', 'danielutils']
12
12
  keywords = []
13
- license = { "file" = "LISENCE" }
13
+ license = { "file" = "./LICENSE" }
14
14
  description = "A python package to quickly configure and publish a new package"
15
- readme = "README.md"
16
- requires-python = ">=3.10.13"
15
+ readme = {file = "./README.md", content-type = "text/markdown"}
16
+ requires-python = ">=3.9.19"
17
17
  classifiers = [
18
18
  "Development Status :: 3 - Alpha",
19
19
  "Intended Audience :: Developers",
@@ -23,6 +23,8 @@ classifiers = [
23
23
 
24
24
  [tool.setuptools]
25
25
  packages = ["quickpub"]
26
+ [tool.setuptools.package-data]
27
+ "quickpub" = ["py.typed"]
26
28
 
27
29
  [project.urls]
28
30
  "Homepage" = "https://github.com/danielnachumdev/quickpub"
@@ -0,0 +1,3 @@
1
+ from .structures import *
2
+ from .runnables import *
3
+ from .__main__ import publish
@@ -0,0 +1,102 @@
1
+ from typing import Optional, Union
2
+ from danielutils import warning, file_exists
3
+ from .validators import validate_version, validate_python_version, validate_keywords, validate_dependencies, \
4
+ validate_source
5
+ from .functions import build, upload, commit, metrics
6
+ from .structures import Version, AdditionalConfiguration
7
+ from .files import create_toml, create_setup, create_manifest
8
+ from .classifiers import *
9
+ from .enforcers import enforce_local_correct_version, enforce_pypirc_exists, exit_if, enforce_remote_correct_version
10
+ from .custom_types import Path
11
+
12
+
13
+ def publish(
14
+ *,
15
+ name: str,
16
+ author: str,
17
+ author_email: str,
18
+ description: str,
19
+ homepage: str,
20
+ src: Optional[Path] = None,
21
+ version: Optional[Union[Version, str]] = None,
22
+ readme: Path = "./README.md",
23
+ license: Path = "./LICENSE",
24
+
25
+ min_python: Optional[Union[Version, str]] = None,
26
+
27
+ keywords: Optional[list[str]] = None,
28
+ dependencies: Optional[list[str]] = None,
29
+ config: Optional[AdditionalConfiguration] = None
30
+ ) -> None:
31
+ """The main function of this package. will do all the heavy lifting in order for you to publish your package.
32
+
33
+ Args:
34
+ name (str): The name of the package
35
+ author (str): The name of the author
36
+ author_email (str): The email of the author
37
+ description (str): A short description for the package
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>
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".
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
+ keywords (Optional[list[str]], optional): A list of keywords to describe areas of interests of this package. Defaults to None.
45
+ dependencies (Optional[list[str]], optional): A list of the dependencies for this package. Defaults to None.
46
+ config (Optional[Config], optional): reserved for future use. Defaults to None.
47
+ """
48
+ enforce_pypirc_exists()
49
+ src = validate_source(name, src)
50
+ if src != f"./{name}":
51
+ warning(
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}")
56
+ version = validate_version(version)
57
+ enforce_local_correct_version(name, version)
58
+ min_python = validate_python_version(min_python) # type:ignore
59
+ keywords = validate_keywords(keywords)
60
+ dependencies = validate_dependencies(dependencies)
61
+ enforce_remote_correct_version(name, version)
62
+
63
+ if config is not None:
64
+ if config.runners is not None:
65
+ for runner in config.runners:
66
+ runner.run(src)
67
+
68
+ create_setup()
69
+ create_toml(
70
+ name=name,
71
+ src=src,
72
+ readme=readme,
73
+ license=license,
74
+ version=version,
75
+ author=author,
76
+ author_email=author_email,
77
+ description=description,
78
+ homepage=homepage,
79
+ keywords=keywords,
80
+ dependencies=dependencies,
81
+ classifiers=[
82
+ DevelopmentStatusClassifier.Alpha,
83
+ IntendedAudienceClassifier.Developers,
84
+ ProgrammingLanguageClassifier.Python3,
85
+ OperatingSystemClassifier.MicrosoftWindows
86
+ ],
87
+ min_python=min_python
88
+ )
89
+ create_manifest(name=name)
90
+
91
+ build()
92
+ upload(
93
+ name=name,
94
+ version=version
95
+ )
96
+ commit(
97
+ version=version
98
+ )
99
+ metrics()
100
+
101
+ # if __name__ == '__main__':
102
+ # publish()
@@ -0,0 +1,12 @@
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
+ ]
@@ -0,0 +1,58 @@
1
+ import sys
2
+ from typing import Union, Callable
3
+
4
+ import requests
5
+ # from bs4 import BeautifulSoup
6
+ from danielutils import directory_exists, get_files, error, file_exists
7
+ from .structures import Version
8
+ from .proxy import get
9
+
10
+
11
+ def exit_if(predicate: Union[bool, Callable[[], bool]], msg: str) -> None:
12
+ if (isinstance(predicate, bool) and predicate) or (callable(predicate) and predicate()):
13
+ error(msg)
14
+ sys.exit(1)
15
+
16
+
17
+ def enforce_local_correct_version(name: str, version: Version) -> None:
18
+ if directory_exists("./dist"):
19
+ max_version = Version(0, 0, 0)
20
+ for d in get_files("./dist"):
21
+ d = d.removeprefix(f"{name}-").removesuffix(".tar.gz")
22
+ v = Version.from_str(d)
23
+ max_version = max(max_version, v)
24
+ exit_if(
25
+ version <= max_version,
26
+ f"Specified version is '{version}' but (locally available) latest existing is '{max_version}'"
27
+ )
28
+
29
+
30
+ def enforce_remote_correct_version(name: str, version: Version) -> None:
31
+ pass
32
+ # url = f"https://pypi.org/project/{name}/#history"
33
+ # headers = {
34
+ # "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
35
+ # }
36
+ # response = requests.get(url, headers=headers)
37
+ # if response.status_code != 200:
38
+ # return
39
+ # soup = BeautifulSoup(response.text, "html.parser")
40
+ # divs = soup.find_all("div", class_="release")
41
+ # versions = []
42
+ # for div in divs:
43
+ # ver = div.find("p", class_="release__version").text.strip()
44
+ # versions.append(ver)
45
+ # pass
46
+
47
+
48
+ def enforce_pypirc_exists() -> None:
49
+ exit_if(
50
+ not file_exists("./.pypirc"),
51
+ "No .pypirc file found"
52
+ )
53
+
54
+
55
+ __all__ = [
56
+ "enforce_local_correct_version",
57
+ "enforce_pypirc_exists"
58
+ ]
@@ -1,12 +1,15 @@
1
- from typing import Optional
2
-
1
+ from .custom_types import Path
3
2
  from .classifiers import Classifier
4
3
  from .structures import Version
4
+ from danielutils import get_files
5
5
 
6
6
 
7
7
  def create_toml(
8
8
  *,
9
9
  name: str,
10
+ src: Path,
11
+ readme: Path,
12
+ license: Path,
10
13
  version: Version,
11
14
  author: str,
12
15
  author_email: str,
@@ -20,6 +23,13 @@ def create_toml(
20
23
  classifiers_string = ",\n\t".join([f"\"{str(c)}\"" for c in classifiers])
21
24
  if len(classifiers_string) > 0:
22
25
  classifiers_string = f"\n\t{classifiers_string}\n"
26
+ py_typed = ""
27
+ for file in get_files(src):
28
+ if file == "py.typed":
29
+ py_typed = f"""[tool.setuptools.package-data]
30
+ "{name}" = ["py.typed"]"""
31
+ break
32
+
23
33
  s = f"""[build-system]
24
34
  requires = ["setuptools>=61.0"]
25
35
  build-backend = "setuptools.build_meta"
@@ -32,14 +42,15 @@ authors = [
32
42
  ]
33
43
  dependencies = {dependencies}
34
44
  keywords = {keywords}
35
- license = {{ "file" = "LISENCE" }}
45
+ license = {{ "file" = "{license}" }}
36
46
  description = "{description}"
37
- readme = "README.md"
47
+ readme = {{file = "{readme}", content-type = "text/markdown"}}
38
48
  requires-python = ">={min_python}"
39
49
  classifiers = [{classifiers_string}]
40
50
 
41
51
  [tool.setuptools]
42
52
  packages = ["{name}"]
53
+ {py_typed}
43
54
 
44
55
  [project.urls]
45
56
  "Homepage" = "{homepage}"
@@ -52,7 +63,9 @@ packages = ["{name}"]
52
63
  def create_setup() -> None:
53
64
  with open("./setup.py", "w", encoding="utf8") as f:
54
65
  f.write("from setuptools import setup\n\nsetup()\n")
55
-
66
+ def create_manifest(*,name:str)->None:
67
+ with open("./MANIFEST.in", "w", encoding="utf8") as f:
68
+ f.write(f"recursive-include {name} *.py")
56
69
 
57
70
  __all__ = [
58
71
  "create_setup",
@@ -1,7 +1,12 @@
1
- from danielutils import info, error
1
+ import sys
2
+ from typing import Optional, Literal
3
+ from danielutils import info
2
4
 
3
- from .proxy import cm
5
+ from .enforcers import exit_if
4
6
  from .structures import Version
7
+ import quickpub.proxy
8
+
9
+
5
10
 
6
11
 
7
12
  def prev_main():
@@ -147,10 +152,11 @@ def build(
147
152
  ) -> None:
148
153
  if verbose:
149
154
  info("Creating new distribution...")
150
- ret, stdout, stderr = cm("python", "setup.py", "sdist")
151
- if ret != 0:
152
- error(stderr.decode(encoding="utf8"))
153
- exit(1)
155
+ ret, stdout, stderr = quickpub.proxy.cm("python", "setup.py", "sdist")
156
+ exit_if(
157
+ ret != 0,
158
+ stderr.decode(encoding="utf8")
159
+ )
154
160
 
155
161
 
156
162
  def upload(
@@ -161,10 +167,12 @@ def upload(
161
167
  ) -> None:
162
168
  if verbose:
163
169
  info("Uploading")
164
- ret, stdout, stderr = cm("wt.exe", "twine", "upload", "--config-file", ".pypirc", f"dist/{name}-{version}.tar.gz")
165
- if ret != 0:
166
- error(stderr.decode(encoding="utf8"))
167
- exit(1)
170
+ ret, stdout, stderr = quickpub.proxy.cm("twine", "upload", "--config-file", ".pypirc",
171
+ f"dist/{name}-{version}.tar.gz")
172
+ exit_if(
173
+ ret != 0,
174
+ f"Failed uploading the package to pypi. Try running the following command manually:\n\ttwine upload --config-file .pypirc dist/{name}-{version}.tar.gz"
175
+ )
168
176
 
169
177
 
170
178
  def commit(
@@ -175,16 +183,28 @@ def commit(
175
183
  if verbose:
176
184
  info("Git")
177
185
  info("\tStaging")
178
- cm("git add .")
186
+ ret, stdout, stderr = quickpub.proxy.cm("git add .")
187
+ exit_if(
188
+ ret != 0,
189
+ stderr.decode(encoding="utf8")
190
+ )
179
191
  if verbose:
180
192
  info("\tCommitting")
181
- cm(f"git commit -m \"updated to version {version}\"")
193
+ ret, stdout, stderr = quickpub.proxy.cm(f"git commit -m \"updated to version {version}\"")
194
+ exit_if(
195
+ ret != 0,
196
+ stderr.decode(encoding="utf8")
197
+ )
182
198
  if verbose:
183
199
  info("\tPushing")
184
- cm("git push")
200
+ ret, stdout, stderr = quickpub.proxy.cm("git push")
201
+ exit_if(
202
+ ret != 0,
203
+ stderr.decode(encoding="utf8")
204
+ )
185
205
 
186
206
 
187
- def metrics():
207
+ def metrics(testing_client: Optional[Literal["pytest", "unitest"]] = None):
188
208
  pass
189
209
 
190
210
 
@@ -0,0 +1,26 @@
1
+ import danielutils
2
+ import requests
3
+
4
+
5
+ # need it like this for the testing
6
+ def cm(*args, **kwargs) -> tuple[int, bytes, bytes]:
7
+ return danielutils.cm(*args, **kwargs)
8
+
9
+
10
+ def get(*args, **kwargs):
11
+ return requests.get(*args, **kwargs)
12
+
13
+
14
+ def add_verbose_keyword(func):
15
+ def wrapper(*args, verbose: bool = False, **kwargs):
16
+ if verbose:
17
+ return func(*args, **kwargs)
18
+
19
+ return wrapper
20
+
21
+
22
+ __all__ = [
23
+ "cm",
24
+ "get",
25
+ 'add_verbose_keyword'
26
+ ]
File without changes
@@ -0,0 +1,2 @@
1
+ from .implementations import *
2
+ from .runnable import Runnable
@@ -0,0 +1,57 @@
1
+ import os
2
+ from abc import abstractmethod
3
+ from typing import Optional, Union
4
+
5
+ from danielutils import file_exists, cm, info, get_current_working_directory, set_current_working_directory
6
+
7
+ from .has_optional_executable import HasOptionalExecutable
8
+ from .runnable import Runnable
9
+ from .configurable import Configurable
10
+ from ..structures import Bound
11
+
12
+
13
+ class CommonCheck(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) -> 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
+ def _build_command(self, target: str) -> str:
24
+ command: str = self.get_executable()
25
+ if self.has_config:
26
+ command += f" --rcfile {self.config_path}"
27
+ command += f" {target}"
28
+ return command
29
+
30
+ @abstractmethod
31
+ def _pre_command(self):
32
+ ...
33
+
34
+ @abstractmethod
35
+ def _post_command(self):
36
+ ...
37
+
38
+ def run(self, target: str, *_) -> None:
39
+ command = self._build_command(target)
40
+ info(f"Running {self.name}")
41
+ self._pre_command()
42
+ try:
43
+ ret, out, err = cm(command)
44
+ score = self._calculate_score(ret, b"".join([out, err]).decode("utf-8").splitlines())
45
+ from ..enforcers import exit_if
46
+ exit_if(not self.bound.compare_against(score), f"{self.name} failed to pass it's defined bound")
47
+ finally:
48
+ self._post_command()
49
+
50
+
51
+ @abstractmethod
52
+ def _calculate_score(self, ret: int, command_output: list[str]) -> float: ...
53
+
54
+
55
+ __all__ = [
56
+ "CommonCheck"
57
+ ]
@@ -0,0 +1,20 @@
1
+ from abc import ABC
2
+ from typing import Optional
3
+ from danielutils import file_exists
4
+
5
+
6
+ class Configurable(ABC):
7
+ @property
8
+ def has_config(self) -> bool:
9
+ return self.config_path is not None
10
+
11
+ def __init__(self, config_path: Optional[str] = None):
12
+ self.config_path = config_path
13
+ if self.has_config:
14
+ if not file_exists(self.config_path):
15
+ raise FileNotFoundError(f"Can't find config file {self.config_path}")
16
+
17
+
18
+ __all__ = [
19
+ "Configurable",
20
+ ]
@@ -0,0 +1,27 @@
1
+ from typing import Any, Protocol, Optional
2
+ from danielutils import get_os, OSType, file_exists
3
+
4
+
5
+ class HasOptionalExecutable():
6
+ PYTHON: str = "python" if get_os() == OSType.WINDOWS else "python3"
7
+
8
+ @property
9
+ def use_executable(self) -> bool:
10
+ return self.executable_path is not None
11
+
12
+ def __init__(self, name, executable_path: Optional[str] = None) -> None:
13
+ self.name = name
14
+ self.executable_path = executable_path
15
+ if self.use_executable:
16
+ if not file_exists(self.executable_path):
17
+ raise FileNotFoundError(f"Executable not found {self.executable_path}")
18
+
19
+ def get_executable(self) -> str:
20
+ if self.use_executable:
21
+ return self.executable_path
22
+ return f"{self.PYTHON} -m {self.name}"
23
+
24
+
25
+ __all__ = [
26
+ 'HasOptionalExecutable',
27
+ ]
@@ -0,0 +1,4 @@
1
+ from .mypy import *
2
+ from .pylint import *
3
+ from .pytest import *
4
+ from .unittest import *
@@ -0,0 +1,29 @@
1
+ import re
2
+ from typing import Optional
3
+ from ..common_check import CommonCheck
4
+
5
+
6
+ class MypyRunner(CommonCheck):
7
+ def _pre_command(self):
8
+ pass
9
+
10
+ def _post_command(self):
11
+ pass
12
+
13
+ RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
14
+
15
+ def __init__(self, configuration_path: Optional[str] = None, executable_path: Optional[str] = None) -> None:
16
+ CommonCheck.__init__(self, "mypy","<15", configuration_path, executable_path)
17
+
18
+ def _calculate_score(self, ret, lines: list[str]) -> float:
19
+ from ...enforcers import exit_if
20
+ rating_line = lines[-1]
21
+ exit_if(not (m := self.RATING_PATTERN.match(rating_line)),
22
+ f"Failed running MyPy, got exit code {ret}. try running manually using:\n\t{self._build_command('TARGE')}")
23
+ rating_string = m.group(1)
24
+ return float(rating_string)
25
+
26
+
27
+ __all__ = [
28
+ 'MypyRunner',
29
+ ]
@@ -0,0 +1,30 @@
1
+ import re
2
+ from typing import Optional
3
+ from ..common_check import CommonCheck
4
+
5
+
6
+ class PylintRunner(CommonCheck):
7
+ def _pre_command(self):
8
+ pass
9
+
10
+ def _post_command(self):
11
+ pass
12
+
13
+ def __init__(self, configuration_path: Optional[str] = None, executable_path: Optional[str] = None) -> None:
14
+ CommonCheck.__init__(self, "pylint",">=0.8", configuration_path, executable_path)
15
+
16
+ RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
17
+
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
+ ]
@@ -0,0 +1,50 @@
1
+ import re
2
+ import os
3
+ from typing import Optional
4
+ from danielutils import get_current_working_directory, set_current_working_directory
5
+ from ..common_check import CommonCheck
6
+
7
+
8
+ class UnittestRunner(CommonCheck):
9
+ def _pre_command(self):
10
+ self._cwd = get_current_working_directory()
11
+ set_current_working_directory(os.path.join(self._cwd, self.target))
12
+
13
+ def _post_command(self):
14
+ set_current_working_directory(self._cwd)
15
+
16
+ RATING_PATTERN: re.Pattern = re.compile(r".*?([\d\.\/]+)")
17
+
18
+ def __init__(self, target: Optional[str] = "./tests") -> None:
19
+ CommonCheck.__init__(self, "unittest", ">=0.7", target)
20
+ self._cwd = ""
21
+
22
+ def _build_command(self, src: str, *args) -> str:
23
+ command: str = self.get_executable()
24
+ rel = os.path.relpath(src, self.target).removesuffix(src.lstrip("./\\"))
25
+ command += f" discover -s {rel}"
26
+ return command # f"cd {self.target}; {command}" # f"; cd {self.target}"
27
+
28
+ def _calculate_score(self, ret: int, lines: list[str]) -> float:
29
+ from ...enforcers import exit_if
30
+ num_tests_line = lines[-3]
31
+ num_failed_line = lines[-1] if lines[-1] != "OK" else "0"
32
+ try:
33
+ m = self.RATING_PATTERN.match(num_tests_line)
34
+ if not m:
35
+ raise AssertionError
36
+ num_tests = m.group(1)
37
+ m = self.RATING_PATTERN.match(num_failed_line)
38
+ if not m:
39
+ raise AssertionError
40
+ num_failed = m.group(1)
41
+
42
+ return 1.0 - (float(num_failed) / float(num_tests))
43
+ except:
44
+ exit_if(True,
45
+ f"Failed running Unittest, got exit code {ret}. try running manually using:\n\t{self._build_command('TARGET')}")
46
+
47
+
48
+ __all__ = [
49
+ 'UnittestRunner',
50
+ ]
@@ -0,0 +1,11 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Runnable(ABC):
5
+ @abstractmethod
6
+ def run(self, *args, **kwargs) -> float: ...
7
+
8
+
9
+ __all__ = [
10
+ "Runnable"
11
+ ]
@@ -0,0 +1,3 @@
1
+ from .bound import *
2
+ from .version import *
3
+ from .additional_configuration import *
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+ from ..runnables import Runnable
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class AdditionalConfiguration:
8
+ runners: Optional[list[Runnable]]
9
+
10
+
11
+ __all__ = [
12
+ 'AdditionalConfiguration',
13
+ ]
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Literal
3
+
4
+
5
+ @dataclass
6
+ class Bound:
7
+ operator: Literal["<", "<=", ">", ">="]
8
+ value: float
9
+
10
+ def compare_against(self, score: float) -> bool:
11
+ return {
12
+ ">": score > self.value,
13
+ ">=": score >= self.value,
14
+ "<": score < self.value,
15
+ "<=": score <= self.value,
16
+ }[self.operator]
17
+
18
+ @staticmethod
19
+ def from_string(s: str) -> 'Bound':
20
+ # the order of iteration matters, weak inequality operators should be first.
21
+ for op in [">=", "<=", ">", "<"]:
22
+ splits = s.split(op)
23
+ if len(splits) == 2:
24
+ return Bound(op, float(splits[-1])) # type:ignore
25
+ raise ValueError("Invalid 'Bound' format")
26
+
27
+
28
+ __all__ = [
29
+ 'Bound'
30
+ ]
@@ -0,0 +1,27 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(order=True)
5
+ class Version:
6
+ @staticmethod
7
+ def from_str(version_str: str) -> "Version":
8
+ return Version(*list(map(int, version_str.split("."))))
9
+
10
+ major: int = 0
11
+ minor: int = 0
12
+ patch: int = 0
13
+
14
+ def __init__(self, major: int = 0, minor: int = 0, patch: int = 0):
15
+ if not all(map(lambda x: isinstance(x, int) and x >= 0, [major, minor, patch])):
16
+ raise ValueError("Version supports positive integers only")
17
+ self.major = major
18
+ self.minor = minor
19
+ self.patch = patch
20
+
21
+ def __str__(self) -> str:
22
+ return f"{self.major}.{self.minor}.{self.patch}"
23
+
24
+
25
+ __all__ = [
26
+ 'Version'
27
+ ]
@@ -0,0 +1,46 @@
1
+ from typing import Optional, Union
2
+
3
+ from danielutils import get_python_version
4
+
5
+ from .custom_types import Path
6
+ from .structures import Version
7
+
8
+
9
+ def validate_version(version: Optional[Union[str, Version]]) -> Version:
10
+ if version is None:
11
+ version = Version(0, 0, 1)
12
+ else:
13
+ version: Version = version if isinstance(version, Version) else Version.from_str(version) # type: ignore
14
+ return version
15
+
16
+
17
+ def validate_python_version(min_python: Optional[Version]) -> Version:
18
+ if min_python is not None:
19
+ return min_python
20
+ return Version(*get_python_version())
21
+
22
+
23
+ def validate_keywords(keywords: Optional[list[str]]) -> list[str]:
24
+ if keywords is None:
25
+ return []
26
+ return keywords
27
+
28
+
29
+ def validate_dependencies(dependencies: Optional[list[str]]) -> list[str]:
30
+ if dependencies is None:
31
+ return []
32
+ return dependencies
33
+
34
+
35
+ def validate_source(name: str, src: Optional[Path] = None) -> Path:
36
+ if src is not None:
37
+ return src
38
+ return f"./{name}"
39
+
40
+
41
+ __all__ = [
42
+ "validate_version",
43
+ "validate_python_version",
44
+ "validate_keywords",
45
+ "validate_dependencies"
46
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quickpub
3
- Version: 0.5.0
3
+ Version: 0.8.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
@@ -30,7 +30,38 @@ Classifier: Development Status :: 3 - Alpha
30
30
  Classifier: Intended Audience :: Developers
31
31
  Classifier: Programming Language :: Python :: 3
32
32
  Classifier: Operating System :: Microsoft :: Windows
33
- Requires-Python: >=3.10.13
33
+ Requires-Python: >=3.9.19
34
34
  Description-Content-Type: text/markdown
35
+ License-File: LICENSE
35
36
 
36
37
  # quickpub
38
+
39
+ Example usage of how this package was published
40
+ ```python
41
+ from quickpub import publish, AdditionalConfiguration, MypyRunner, PylintRunner, UnittestRunner
42
+
43
+
44
+ def main() -> None:
45
+ publish(
46
+ name="quickpub",
47
+ version="0.8.0",
48
+ author="danielnachumdev",
49
+ author_email="danielnachumdev@gmail.com",
50
+ description="A python package to quickly configure and publish a new package",
51
+ homepage="https://github.com/danielnachumdev/quickpub",
52
+ dependencies=["twine", "danielutils"],
53
+ min_python="3.9.19",
54
+ config=AdditionalConfiguration(
55
+ runners=[
56
+ MypyRunner(),
57
+ PylintRunner(),
58
+ UnittestRunner(),
59
+ ]
60
+ )
61
+ )
62
+
63
+
64
+ if __name__ == '__main__':
65
+ main()
66
+
67
+ ```
@@ -0,0 +1,34 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ quickpub/__init__.py
7
+ quickpub/__main__.py
8
+ quickpub/classifiers.py
9
+ quickpub/custom_types.py
10
+ quickpub/enforcers.py
11
+ quickpub/files.py
12
+ quickpub/functions.py
13
+ quickpub/proxy.py
14
+ quickpub/py.typed
15
+ quickpub/validators.py
16
+ quickpub.egg-info/PKG-INFO
17
+ quickpub.egg-info/SOURCES.txt
18
+ quickpub.egg-info/dependency_links.txt
19
+ quickpub.egg-info/requires.txt
20
+ quickpub.egg-info/top_level.txt
21
+ quickpub/runnables/__init__.py
22
+ quickpub/runnables/common_check.py
23
+ quickpub/runnables/configurable.py
24
+ quickpub/runnables/has_optional_executable.py
25
+ quickpub/runnables/runnable.py
26
+ quickpub/runnables/implementations/__init__.py
27
+ quickpub/runnables/implementations/mypy.py
28
+ quickpub/runnables/implementations/pylint.py
29
+ quickpub/runnables/implementations/pytest.py
30
+ quickpub/runnables/implementations/unittest.py
31
+ quickpub/structures/__init__.py
32
+ quickpub/structures/additional_configuration.py
33
+ quickpub/structures/bound.py
34
+ quickpub/structures/version.py
quickpub-0.5.0/README.md DELETED
@@ -1 +0,0 @@
1
- # quickpub
@@ -1,6 +0,0 @@
1
- from .__main__ import publish
2
- from .classifiers import *
3
- from .files import *
4
- from .publish import *
5
- from .structures import *
6
- from .proxy import *
@@ -1,67 +0,0 @@
1
- from typing import Optional, Union
2
- from danielutils import get_python_version
3
- from .publish import build, upload, commit, metrics
4
- from .structures import Version, Config
5
- from .files import create_toml, create_setup
6
- from .classifiers import *
7
-
8
-
9
- def publish(
10
- *,
11
- name: str,
12
- version: Optional[Union[Version, str]] = None,
13
- author: str,
14
- author_email: str,
15
- description: str,
16
- homepage: str,
17
-
18
- min_python: Optional[Union[Version, str]] = None,
19
-
20
- keywords: Optional[list[str]] = None,
21
- dependencies: Optional[list[str]] = None,
22
- config: Optional[Config] = None
23
- ) -> None:
24
- if version is None:
25
- version: Version = None # type: ignore
26
- else:
27
- version: Version = version if isinstance(version, Version) else Version.from_str(version) # type: ignore
28
-
29
- if min_python is None:
30
- min_python = Version(*get_python_version())
31
- if keywords is None:
32
- keywords = []
33
-
34
- if dependencies is None:
35
- dependencies = []
36
-
37
- create_setup()
38
- create_toml(
39
- name=name,
40
- version=version,
41
- author=author,
42
- author_email=author_email,
43
- description=description,
44
- homepage=homepage,
45
- keywords=keywords,
46
- dependencies=dependencies,
47
- classifiers=[
48
- DevelopmentStatusClassifier.Alpha,
49
- IntendedAudienceClassifier.Developers,
50
- ProgrammingLanguageClassifier.Python3,
51
- OperatingSystemClassifier.MicrosoftWindows
52
- ],
53
- min_python=min_python
54
- )
55
-
56
- build()
57
- upload(
58
- name=name,
59
- version=version
60
- )
61
- commit(
62
- version=version
63
- )
64
- metrics()
65
-
66
- # if __name__ == '__main__':
67
- # publish()
@@ -1,11 +0,0 @@
1
- import danielutils
2
-
3
-
4
- # need it like this for the testing
5
- def cm(*args, **kwargs)->tuple[int,bytes,bytes]:
6
- return danielutils.cm(*args, **kwargs)
7
-
8
-
9
- __all__ = [
10
- "cm"
11
- ]
@@ -1,28 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass
5
- class Version:
6
- @staticmethod
7
- def from_str(version_str: str) -> "Version":
8
- return Version(*list(map(int, version_str.split("."))))
9
-
10
- major: int = 0
11
- minor: int = 0
12
- patch: int = 0
13
-
14
- def __str__(self) -> str:
15
- return f"{self.major}.{self.minor}.{self.patch}"
16
-
17
-
18
- @dataclass
19
- class Config:
20
- pylint: bool = False
21
- mypy: bool = False
22
-
23
-
24
-
25
- __all__ = [
26
- "Version",
27
- "Config",
28
- ]
@@ -1,15 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- setup.py
4
- quickpub/__init__.py
5
- quickpub/__main__.py
6
- quickpub/classifiers.py
7
- quickpub/files.py
8
- quickpub/proxy.py
9
- quickpub/publish.py
10
- quickpub/structures.py
11
- quickpub.egg-info/PKG-INFO
12
- quickpub.egg-info/SOURCES.txt
13
- quickpub.egg-info/dependency_links.txt
14
- quickpub.egg-info/requires.txt
15
- quickpub.egg-info/top_level.txt
File without changes
File without changes