nmk-python 0.14.1__py3-none-any.whl → 0.15.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nmk_python/__init__.py +11 -13
- nmk_python/badges.yml +6 -6
- nmk_python/build.py +101 -5
- nmk_python/build.yml +21 -9
- nmk_python/files.py +36 -2
- nmk_python/files.yml +4 -1
- nmk_python/plugin.yml +3 -4
- nmk_python/project.py +84 -0
- nmk_python/project.yml +31 -0
- nmk_python/{flake.py → ruff.py} +21 -8
- nmk_python/ruff.yml +64 -0
- nmk_python/templates/build.toml.jinja +35 -0
- nmk_python/templates/pytest.toml.jinja +20 -0
- nmk_python/templates/python-settings.json.jinja +3 -10
- nmk_python/templates/ruff.toml.jinja +19 -0
- nmk_python/tests.py +17 -1
- nmk_python/tests.yml +5 -11
- nmk_python/version.py +57 -6
- nmk_python/version.yml +6 -0
- {nmk_python-0.14.1.dist-info → nmk_python-0.15.1.dist-info}/METADATA +9 -10
- nmk_python-0.15.1.dist-info/RECORD +27 -0
- {nmk_python-0.14.1.dist-info → nmk_python-0.15.1.dist-info}/WHEEL +1 -1
- nmk_python/flake.yml +0 -56
- nmk_python/format.py +0 -17
- nmk_python/format.yml +0 -41
- nmk_python/setup.py +0 -63
- nmk_python/setup.yml +0 -37
- nmk_python/templates/build.cfg +0 -20
- nmk_python/templates/flake.cfg +0 -6
- nmk_python/templates/isort.cfg +0 -3
- nmk_python/templates/pytest.cfg +0 -20
- nmk_python-0.14.1.dist-info/RECORD +0 -30
- {nmk_python-0.14.1.dist-info → nmk_python-0.15.1.dist-info}/LICENSE +0 -0
- {nmk_python-0.14.1.dist-info → nmk_python-0.15.1.dist-info}/top_level.txt +0 -0
nmk_python/__init__.py
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Python support for nmk
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version
|
|
3
6
|
|
|
4
7
|
from nmk_base.version import VersionResolver
|
|
5
|
-
from pkg_resources import DistributionNotFound, get_distribution
|
|
6
8
|
|
|
7
9
|
__title__ = "nmk-python"
|
|
8
10
|
try:
|
|
9
|
-
__version__ =
|
|
10
|
-
except
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
with (Path(__file__).parent.parent.parent / "setup.cfg").open("r") as f:
|
|
14
|
-
c = ConfigParser()
|
|
15
|
-
c.read_file(f.readlines())
|
|
16
|
-
__version__ = c.get("metadata", "version")
|
|
17
|
-
except Exception:
|
|
18
|
-
__version__ = "unknown"
|
|
11
|
+
__version__ = version(__title__)
|
|
12
|
+
except Exception: # pragma: no cover
|
|
13
|
+
__version__ = "unknown"
|
|
19
14
|
|
|
20
15
|
|
|
21
16
|
class NmkPythonVersionResolver(VersionResolver):
|
|
17
|
+
"""Plugin version resolver"""
|
|
18
|
+
|
|
22
19
|
def get_version(self) -> str:
|
|
20
|
+
"""Returns nmk-python plugin version"""
|
|
23
21
|
return __version__
|
nmk_python/badges.yml
CHANGED
|
@@ -7,13 +7,13 @@ config:
|
|
|
7
7
|
- alt: PyPI
|
|
8
8
|
img: https://img.shields.io/pypi/v/${pythonPackage}
|
|
9
9
|
url: https://pypi.org/project/${pythonPackage}/
|
|
10
|
-
- alt: "Code style:
|
|
11
|
-
img: https://img.shields.io/badge/code%20style-
|
|
12
|
-
url: https://
|
|
10
|
+
- alt: "Code style: ruff"
|
|
11
|
+
img: https://img.shields.io/badge/code%20style-ruff-000000.svg
|
|
12
|
+
url: https://astral.sh/ruff
|
|
13
13
|
if: ${pythonFoundSrcFiles}
|
|
14
|
-
- alt: "
|
|
15
|
-
img: https://img.shields.io/badge/
|
|
16
|
-
url: https://
|
|
14
|
+
- alt: "Ruff analysis result"
|
|
15
|
+
img: https://img.shields.io/badge/ruff-0-green
|
|
16
|
+
url: https://astral.sh/ruff
|
|
17
17
|
if: ${pythonFoundSrcFiles}
|
|
18
18
|
- alt: "Code coverage"
|
|
19
19
|
img: https://img.shields.io/codecov/c/github/${githubUser}/${githubRepo}
|
nmk_python/build.py
CHANGED
|
@@ -1,29 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python package build module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
1
6
|
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List
|
|
2
9
|
|
|
3
10
|
from nmk.model.builder import NmkTaskBuilder
|
|
4
11
|
from nmk.model.keys import NmkRootConfig
|
|
5
12
|
from nmk.model.resolver import NmkStrConfigResolver
|
|
6
13
|
from nmk.utils import is_windows, run_with_logs
|
|
7
14
|
from nmk_base.venvbuilder import VenvUpdateBuilder
|
|
15
|
+
from tomlkit import loads
|
|
16
|
+
from tomlkit.toml_file import TOMLFile
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
class PackageBuilder(NmkTaskBuilder):
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
"""
|
|
21
|
+
Python package builder
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def build(self, project_file: str, version_file: str, source_dirs: List[str], artifacts_dir: str, build_dir: str, extra_resources: Dict[str, str]):
|
|
25
|
+
"""
|
|
26
|
+
Delegate to python build module, from a temporary build folder
|
|
27
|
+
|
|
28
|
+
:param project_file: path to python project file
|
|
29
|
+
:param version_file: path to generated version file
|
|
30
|
+
:param source_dirs: list of source folders for this wheel
|
|
31
|
+
:param artifacts_dir: output folder for built wheel
|
|
32
|
+
:param build_dir: temporary build folder
|
|
33
|
+
:param extra_resources: dictionary of extra resources mapping (original path -> target path)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Prepare build folder
|
|
37
|
+
build_path = Path(build_dir)
|
|
38
|
+
if build_path.is_dir():
|
|
39
|
+
shutil.rmtree(build_path)
|
|
40
|
+
build_path.mkdir(exist_ok=True, parents=True)
|
|
41
|
+
|
|
42
|
+
# Copy source folders and various project files
|
|
43
|
+
project_root = Path(self.model.config[NmkRootConfig.PROJECT_DIR].value)
|
|
44
|
+
for source_dir in map(Path, source_dirs):
|
|
45
|
+
shutil.copytree(source_dir, build_path / source_dir.relative_to(project_root))
|
|
46
|
+
for candidate in filter(lambda p: p.is_file(), map(Path, [project_file, version_file, project_root / "README.md", project_root / "LICENSE"])):
|
|
47
|
+
shutil.copyfile(candidate, build_path / candidate.name)
|
|
48
|
+
|
|
49
|
+
# Update project file with version
|
|
50
|
+
build_project = build_path / Path(project_file).name
|
|
51
|
+
build_version = build_path / Path(version_file).name
|
|
52
|
+
with build_project.open() as f:
|
|
53
|
+
project_doc = loads(f.read())
|
|
54
|
+
dyn_table = project_doc.get("tool").get("setuptools").get("dynamic")
|
|
55
|
+
dyn_table["version"] = {"file": build_version.name}
|
|
56
|
+
project_output = TOMLFile(build_project)
|
|
57
|
+
project_output.write(project_doc)
|
|
58
|
+
|
|
59
|
+
# Handle extra resources
|
|
60
|
+
for src, dst in extra_resources.items():
|
|
61
|
+
src_path, dst_path = Path(src), Path(dst)
|
|
62
|
+
if not src_path.is_absolute(): # pragma: no branch
|
|
63
|
+
src_path = project_root / src_path
|
|
64
|
+
if not dst_path.is_absolute(): # pragma: no branch
|
|
65
|
+
dst_path = project_root / dst_path
|
|
66
|
+
assert src_path.exists(), f"Required extra resource path not found: {src_path}"
|
|
67
|
+
dst_path = build_path / dst_path.relative_to(project_root)
|
|
68
|
+
dst_path.mkdir(exist_ok=True, parents=True)
|
|
69
|
+
if src_path.is_file():
|
|
70
|
+
# Single file copy
|
|
71
|
+
self.logger.debug(f"Copy extra resource file: {src_path} --> {dst_path}")
|
|
72
|
+
shutil.copyfile(src_path, dst_path / src_path.name)
|
|
73
|
+
else:
|
|
74
|
+
# Directory copy
|
|
75
|
+
self.logger.debug(f"Copy extra resource tree: {src_path} --> {dst_path}")
|
|
76
|
+
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
|
77
|
+
|
|
78
|
+
# Prepare artifacts folder
|
|
79
|
+
artifacts_path = Path(artifacts_dir)
|
|
80
|
+
artifacts_path.mkdir(exist_ok=True, parents=True)
|
|
81
|
+
for wheel in artifacts_path.glob("*.whl"):
|
|
82
|
+
wheel.unlink()
|
|
83
|
+
|
|
84
|
+
# Delegate to build module
|
|
13
85
|
run_with_logs(
|
|
14
|
-
[sys.executable, "-
|
|
86
|
+
[sys.executable, "-m", "build", "--wheel", "--skip-dependency-check", "--no-isolation"],
|
|
15
87
|
self.logger,
|
|
16
|
-
cwd=
|
|
88
|
+
cwd=build_path,
|
|
17
89
|
)
|
|
18
90
|
|
|
91
|
+
# Copy wheel to artifacts folder
|
|
92
|
+
target_wheel = self.main_output
|
|
93
|
+
built_wheel = build_path / "dist" / target_wheel.name
|
|
94
|
+
assert built_wheel.is_file(), f"Expected built wheel not found: {built_wheel}"
|
|
95
|
+
shutil.copyfile(built_wheel, target_wheel)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PythonModuleResolver(NmkStrConfigResolver):
|
|
99
|
+
"""
|
|
100
|
+
Python module name resolver
|
|
101
|
+
"""
|
|
19
102
|
|
|
20
|
-
class PythonPackageForWheel(NmkStrConfigResolver):
|
|
21
103
|
def get_value(self, name: str) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Return module name from package (i.e. wheel) name
|
|
106
|
+
"""
|
|
22
107
|
return self.model.config["pythonPackage"].value.replace("-", "_")
|
|
23
108
|
|
|
24
109
|
|
|
25
110
|
class Installer(VenvUpdateBuilder):
|
|
111
|
+
"""
|
|
112
|
+
Install built wheel in venv
|
|
113
|
+
"""
|
|
114
|
+
|
|
26
115
|
def build(self, name: str, pip_args: str):
|
|
116
|
+
"""
|
|
117
|
+
Install wheel in venv
|
|
118
|
+
|
|
119
|
+
:param name: wheel name to be installed
|
|
120
|
+
:param pip_args: pip command line arguments
|
|
121
|
+
"""
|
|
122
|
+
|
|
27
123
|
# On Windows, refuse to install nmk package while running nmk (wont' work)
|
|
28
124
|
if is_windows() and name == "nmk":
|
|
29
125
|
self.logger.warning("Can't install nmk while running nmk!")
|
nmk_python/build.yml
CHANGED
|
@@ -2,8 +2,8 @@ config:
|
|
|
2
2
|
|
|
3
3
|
# Python package name (to be configured at project level)
|
|
4
4
|
pythonPackage: ${projectName}
|
|
5
|
-
|
|
6
|
-
__resolver__: nmk_python.build.
|
|
5
|
+
pythonModule:
|
|
6
|
+
__resolver__: nmk_python.build.PythonModuleResolver
|
|
7
7
|
|
|
8
8
|
# Python artifacts directory
|
|
9
9
|
pythonArtifacts: ${outputDir}/artifacts
|
|
@@ -11,20 +11,27 @@ config:
|
|
|
11
11
|
# Python build directory
|
|
12
12
|
pythonBuildDir: ${outputDir}/python
|
|
13
13
|
|
|
14
|
+
# Target platform for python project
|
|
15
|
+
pythonPackagePlatform: any
|
|
16
|
+
|
|
14
17
|
# Python source distribution + wheel artifacts
|
|
15
|
-
pythonWheel: ${pythonArtifacts}/${
|
|
18
|
+
pythonWheel: ${pythonArtifacts}/${pythonModule}-${pythonVersion}-py3-none-${pythonPackagePlatform}.whl
|
|
16
19
|
|
|
17
|
-
# Add
|
|
18
|
-
|
|
19
|
-
- ${BASEDIR}/templates/build.
|
|
20
|
+
# Add project file fragment for build config
|
|
21
|
+
pythonProjectFileFragments:
|
|
22
|
+
- ${BASEDIR}/templates/build.toml.jinja
|
|
20
23
|
|
|
21
24
|
# Python package dependencies (also contributed to venv)
|
|
22
25
|
pythonPackageRequirements: []
|
|
23
26
|
venvPkgDeps:
|
|
24
27
|
- setuptools
|
|
25
28
|
- wheel
|
|
29
|
+
- build
|
|
26
30
|
- ${pythonPackageRequirements}
|
|
27
31
|
|
|
32
|
+
# Extra (external) resources to be bundled in built python wheel
|
|
33
|
+
pythonExtraResources: {}
|
|
34
|
+
|
|
28
35
|
# Python files to be ignored
|
|
29
36
|
gitIgnoredFiles:
|
|
30
37
|
- .eggs/
|
|
@@ -48,7 +55,8 @@ tasks:
|
|
|
48
55
|
description: Build python package
|
|
49
56
|
emoji: ":snake:.:hammer:"
|
|
50
57
|
deps:
|
|
51
|
-
- py.
|
|
58
|
+
- py.project
|
|
59
|
+
- py.format
|
|
52
60
|
- py.analyze
|
|
53
61
|
appendToDeps: build
|
|
54
62
|
if: ${pythonSrcFiles} # Only if python source files are found
|
|
@@ -56,11 +64,15 @@ tasks:
|
|
|
56
64
|
- ${pythonWheel}
|
|
57
65
|
input:
|
|
58
66
|
- ${pythonSrcFiles}
|
|
59
|
-
- ${
|
|
67
|
+
- ${pythonProjectFile}
|
|
60
68
|
builder: nmk_python.build.PackageBuilder
|
|
61
69
|
params:
|
|
62
|
-
|
|
70
|
+
project_file: ${pythonProjectFile}
|
|
71
|
+
version_file: ${pythonVersionStamp}
|
|
72
|
+
source_dirs: ${pythonSrcFolders}
|
|
73
|
+
artifacts_dir: ${pythonArtifacts}
|
|
63
74
|
build_dir: ${pythonBuildDir}
|
|
75
|
+
extra_resources: ${pythonExtraResources}
|
|
64
76
|
|
|
65
77
|
# Python module install
|
|
66
78
|
py.install:
|
nmk_python/files.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python files resolvers
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
from pathlib import Path
|
|
2
6
|
from typing import List
|
|
3
7
|
|
|
@@ -5,19 +9,44 @@ from nmk.model.resolver import NmkListConfigResolver
|
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class FilesFinder(NmkListConfigResolver):
|
|
12
|
+
"""
|
|
13
|
+
Shared logic for files resolution
|
|
14
|
+
"""
|
|
15
|
+
|
|
8
16
|
def find_in_folders(self) -> List[str]: # pragma: no cover
|
|
17
|
+
"""
|
|
18
|
+
Folders to be browsed (to be overridden)
|
|
19
|
+
|
|
20
|
+
:return: List of folders to be browsed for python files
|
|
21
|
+
"""
|
|
9
22
|
pass
|
|
10
23
|
|
|
11
24
|
def get_value(self, name: str) -> List[Path]:
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
"""
|
|
26
|
+
Browse for source files in specified folders
|
|
27
|
+
|
|
28
|
+
:param name: Config item name
|
|
29
|
+
:return: List of found source files
|
|
30
|
+
"""
|
|
31
|
+
# Iterate on source paths, and find all source files
|
|
32
|
+
return [src_file for src_path in map(Path, self.find_in_folders()) for src_file in filter(lambda f: f.is_file(), src_path.rglob("*"))]
|
|
14
33
|
|
|
15
34
|
|
|
16
35
|
class PythonFilesFinder(FilesFinder):
|
|
36
|
+
"""Regular python files resolver"""
|
|
37
|
+
|
|
17
38
|
def find_in_folders(self) -> List[str]:
|
|
39
|
+
"""
|
|
40
|
+
Python source folders
|
|
41
|
+
"""
|
|
18
42
|
return self.model.config["pythonSrcFolders"].value
|
|
19
43
|
|
|
20
44
|
def get_value(self, name: str) -> List[Path]:
|
|
45
|
+
"""
|
|
46
|
+
Browse for python files in specified folders
|
|
47
|
+
|
|
48
|
+
Also and make sure they don't overlap with generated and test source code
|
|
49
|
+
"""
|
|
21
50
|
# All found files
|
|
22
51
|
all_files = set(super().get_value(name))
|
|
23
52
|
|
|
@@ -29,5 +58,10 @@ class PythonFilesFinder(FilesFinder):
|
|
|
29
58
|
|
|
30
59
|
|
|
31
60
|
class PythonTestFilesFinder(FilesFinder):
|
|
61
|
+
"""Test python files resolver"""
|
|
62
|
+
|
|
32
63
|
def find_in_folders(self) -> List[str]:
|
|
64
|
+
"""
|
|
65
|
+
Python tests source folders
|
|
66
|
+
"""
|
|
33
67
|
return [self.model.config["pythonTestSources"].value]
|
nmk_python/files.yml
CHANGED
|
@@ -3,8 +3,11 @@ config:
|
|
|
3
3
|
pythonSrcFolders:
|
|
4
4
|
- ${sourceDir}
|
|
5
5
|
|
|
6
|
+
# Package holding test code
|
|
7
|
+
pythonTestPackage: tests
|
|
8
|
+
|
|
6
9
|
# Root folder for test sources
|
|
7
|
-
pythonTestSources: ${sourceDir}
|
|
10
|
+
pythonTestSources: ${sourceDir}/${pythonTestPackage}
|
|
8
11
|
|
|
9
12
|
# Found Python test source files
|
|
10
13
|
pythonTestSrcFiles:
|
nmk_python/plugin.yml
CHANGED
|
@@ -3,10 +3,9 @@ refs:
|
|
|
3
3
|
- pip://nmk-base!plugin.yml
|
|
4
4
|
- files.yml # Look for python source files
|
|
5
5
|
- version.yml # Python version contribution
|
|
6
|
-
-
|
|
7
|
-
- format.yml # Python code format (with black/isort)
|
|
8
|
-
- flake.yml # Python code analysis (with flake8)
|
|
6
|
+
- project.yml # Python project file generation
|
|
9
7
|
- build.yml # Python module build/install
|
|
8
|
+
- ruff.yml # Python code format/analysis (with ruff)
|
|
10
9
|
- tests.yml # Python automated tests
|
|
11
10
|
- badges.yml # Python badges
|
|
12
11
|
- doc.yml # Python doc
|
|
@@ -22,7 +21,7 @@ config:
|
|
|
22
21
|
|
|
23
22
|
# Plugin help links dict
|
|
24
23
|
nmkPluginsDocs:
|
|
25
|
-
nmk-python: https://
|
|
24
|
+
nmk-python: https://nmk-python.readthedocs.io/
|
|
26
25
|
|
|
27
26
|
# Contribution to VSCode settings/launch/extensions (if plugin is used)
|
|
28
27
|
vscodeSettingsFiles:
|
nmk_python/project.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python project file builder
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, List
|
|
7
|
+
|
|
8
|
+
from nmk_base.common import TemplateBuilder
|
|
9
|
+
from tomlkit import TOMLDocument, comment, loads
|
|
10
|
+
from tomlkit.toml_file import TOMLFile
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PythonProjectBuilder(TemplateBuilder):
|
|
14
|
+
"""
|
|
15
|
+
Python project file builder
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Handle relative path for all contributions
|
|
19
|
+
def _check_paths(self, value: Any):
|
|
20
|
+
if isinstance(value, str):
|
|
21
|
+
return self.relative_path(value)
|
|
22
|
+
if isinstance(value, list):
|
|
23
|
+
return list(map(self._check_paths, value))
|
|
24
|
+
if isinstance(value, dict):
|
|
25
|
+
return {k: self._check_paths(v) for k, v in value.items()}
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
def _contribute(self, main: dict, update: dict):
|
|
29
|
+
"""
|
|
30
|
+
Merge items from **update** dictionary into **main** one.
|
|
31
|
+
Merge logic for existing items is:
|
|
32
|
+
|
|
33
|
+
* for lists: new items are appended after existing ones
|
|
34
|
+
* for dictionaries: existing dictionary is updated with new one content
|
|
35
|
+
* for other types: existing values are replaces with new ones
|
|
36
|
+
|
|
37
|
+
:param main: Settings dictionary to be updated
|
|
38
|
+
:param update: Update dictionary to be merged into the existing one
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
for k, v in update.items():
|
|
42
|
+
# Already exists in target model?
|
|
43
|
+
if k in main:
|
|
44
|
+
# List: extend
|
|
45
|
+
if isinstance(v, list):
|
|
46
|
+
main[k].extend(self._check_paths(v))
|
|
47
|
+
# Map: recursive contribution
|
|
48
|
+
elif isinstance(v, dict):
|
|
49
|
+
self._contribute(main[k], v)
|
|
50
|
+
# Otherwise: replace
|
|
51
|
+
else:
|
|
52
|
+
main[k] = self._check_paths(v)
|
|
53
|
+
else:
|
|
54
|
+
# New key
|
|
55
|
+
main[k] = self._check_paths(v)
|
|
56
|
+
|
|
57
|
+
def build(self, fragment_files: List[str], items: dict):
|
|
58
|
+
"""
|
|
59
|
+
Generates python project file from fragments and items
|
|
60
|
+
|
|
61
|
+
:param fragment_files: List of fragment files to be merged
|
|
62
|
+
:param items: Dict of project items to be merged
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# Merge project fragments to generate final project
|
|
66
|
+
project = {}
|
|
67
|
+
for f_path in map(Path, fragment_files):
|
|
68
|
+
try:
|
|
69
|
+
# Update document with rendered template
|
|
70
|
+
fragment_doc = loads(self.render_template(f_path, {}))
|
|
71
|
+
except Exception as e:
|
|
72
|
+
# Propagate error with file name
|
|
73
|
+
raise ValueError(f"While loading project file template ({f_path}): {e}") from e
|
|
74
|
+
self._contribute(project, fragment_doc.unwrap())
|
|
75
|
+
|
|
76
|
+
# Iterate on items contributed through yml project files (only ones contributing maps)
|
|
77
|
+
self._contribute(project, {k: v for k, v in items.items() if isinstance(v, dict)})
|
|
78
|
+
|
|
79
|
+
# Finally write config to output file
|
|
80
|
+
doc = TOMLDocument()
|
|
81
|
+
doc.add(comment("Please don't edit: generated by nmk-python plugin"))
|
|
82
|
+
doc.update(project)
|
|
83
|
+
project_output = TOMLFile(self.main_output)
|
|
84
|
+
project_output.write(doc)
|
nmk_python/project.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
config:
|
|
2
|
+
# Generated project file
|
|
3
|
+
pythonProjectFile: ${PROJECTDIR}/pyproject.toml
|
|
4
|
+
|
|
5
|
+
# Paths to pyproject.toml fragments to be merged
|
|
6
|
+
pythonProjectFileFragments: []
|
|
7
|
+
|
|
8
|
+
# Contributed project items from yml files
|
|
9
|
+
pythonProjectFileItems: {}
|
|
10
|
+
|
|
11
|
+
# Require VSCode specific extension for toml support
|
|
12
|
+
vscodeExtensionsNames:
|
|
13
|
+
- tamasfe.even-better-toml
|
|
14
|
+
|
|
15
|
+
tasks:
|
|
16
|
+
py.project:
|
|
17
|
+
description: Generate python project file
|
|
18
|
+
emoji: ":snake:.:toolbox:"
|
|
19
|
+
input:
|
|
20
|
+
- ${pythonProjectFileFragments}
|
|
21
|
+
- ${pythonVersionStamp}
|
|
22
|
+
output:
|
|
23
|
+
- ${pythonProjectFile}
|
|
24
|
+
deps:
|
|
25
|
+
- py.version
|
|
26
|
+
appendToDeps: setup
|
|
27
|
+
if: ${pythonSrcFiles} # Only if python source files are found
|
|
28
|
+
builder: nmk_python.project.PythonProjectBuilder
|
|
29
|
+
params:
|
|
30
|
+
fragment_files: ${pythonProjectFileFragments}
|
|
31
|
+
items: ${pythonProjectFileItems}
|
nmk_python/{flake.py → ruff.py}
RENAMED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Ruff tool handling
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
2
6
|
from pathlib import Path
|
|
3
7
|
from typing import List
|
|
4
8
|
|
|
@@ -7,8 +11,19 @@ from nmk.model.keys import NmkRootConfig
|
|
|
7
11
|
from nmk.utils import run_with_logs
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
class
|
|
11
|
-
|
|
14
|
+
class RuffBuilder(NmkTaskBuilder):
|
|
15
|
+
"""
|
|
16
|
+
Ruff tool builder
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def build(self, src_folders: List[str], command: str):
|
|
20
|
+
"""
|
|
21
|
+
Invoke ruff
|
|
22
|
+
|
|
23
|
+
:param src_folders: source folders to be analyzed
|
|
24
|
+
:param command: ruff command to be executed
|
|
25
|
+
"""
|
|
26
|
+
|
|
12
27
|
# Project folder
|
|
13
28
|
project_folder = Path(self.model.config[NmkRootConfig.PROJECT_DIR].value)
|
|
14
29
|
|
|
@@ -16,14 +31,12 @@ class FlakeBuilder(NmkTaskBuilder):
|
|
|
16
31
|
relative_src_folders = []
|
|
17
32
|
for f in src_folders:
|
|
18
33
|
p = Path(f)
|
|
19
|
-
|
|
34
|
+
with contextlib.suppress(ValueError):
|
|
20
35
|
p = p.relative_to(project_folder)
|
|
21
|
-
except ValueError: # pragma: no cover
|
|
22
|
-
pass
|
|
23
36
|
relative_src_folders.append(p)
|
|
24
37
|
|
|
25
|
-
# Delegate to
|
|
26
|
-
run_with_logs([
|
|
38
|
+
# Delegate to ruff
|
|
39
|
+
run_with_logs(["ruff", command] + relative_src_folders, self.logger, cwd=project_folder)
|
|
27
40
|
|
|
28
41
|
# Touch output file
|
|
29
42
|
self.main_output.touch()
|
nmk_python/ruff.yml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
config:
|
|
2
|
+
|
|
3
|
+
# Just needs ruff
|
|
4
|
+
venvPkgDeps:
|
|
5
|
+
- ruff
|
|
6
|
+
|
|
7
|
+
# Max line length
|
|
8
|
+
pythonLineLength: 160
|
|
9
|
+
|
|
10
|
+
# Add project file fragment for ruff config
|
|
11
|
+
pythonProjectFileFragments:
|
|
12
|
+
- ${BASEDIR}/templates/ruff.toml.jinja
|
|
13
|
+
|
|
14
|
+
# Stamp files for ruff execution
|
|
15
|
+
pythonRuffFormatStamp: ${outputDir}/.ruff-format
|
|
16
|
+
pythonRuffCheckStamp: ${outputDir}/.ruff-check
|
|
17
|
+
|
|
18
|
+
# Ignored rules
|
|
19
|
+
pythonIgnoredRules:
|
|
20
|
+
- E203 # space before ":"; sometimes formatting with black introduces this kind of space in slice expressions
|
|
21
|
+
- E501 # line length; we're working with lines wider than the recommended 79 length
|
|
22
|
+
|
|
23
|
+
# Require VSCode specific extension for ruff analysis
|
|
24
|
+
vscodeExtensionsNames:
|
|
25
|
+
- charliermarsh.ruff
|
|
26
|
+
|
|
27
|
+
tasks:
|
|
28
|
+
|
|
29
|
+
# Python source code format with ruff
|
|
30
|
+
py.format:
|
|
31
|
+
description: Format python code
|
|
32
|
+
emoji: ":snake:.:lipstick:"
|
|
33
|
+
deps:
|
|
34
|
+
- out
|
|
35
|
+
- py.project
|
|
36
|
+
appendToDeps: build
|
|
37
|
+
if: ${pythonSrcFiles} # Only if python source files are found
|
|
38
|
+
output:
|
|
39
|
+
- ${pythonRuffFormatStamp}
|
|
40
|
+
input:
|
|
41
|
+
- ${pythonProjectFile}
|
|
42
|
+
- ${pythonSrcFiles}
|
|
43
|
+
builder: nmk_python.ruff.RuffBuilder
|
|
44
|
+
params:
|
|
45
|
+
src_folders: ${pythonSrcFolders}
|
|
46
|
+
command: format
|
|
47
|
+
|
|
48
|
+
# Python source code analysis with ruff
|
|
49
|
+
py.analyze:
|
|
50
|
+
description: Analyze python code
|
|
51
|
+
emoji: ":snake:.:eye: "
|
|
52
|
+
deps:
|
|
53
|
+
- py.format
|
|
54
|
+
appendToDeps: build
|
|
55
|
+
if: ${pythonSrcFiles} # Only if python source files are found
|
|
56
|
+
output:
|
|
57
|
+
- ${pythonRuffCheckStamp}
|
|
58
|
+
input:
|
|
59
|
+
- ${pythonProjectFile}
|
|
60
|
+
- ${pythonSrcFiles}
|
|
61
|
+
builder: nmk_python.ruff.RuffBuilder
|
|
62
|
+
params:
|
|
63
|
+
src_folders: ${pythonSrcFolders}
|
|
64
|
+
command: check
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "{{ pythonPackage }}"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
authors = [
|
|
9
|
+
{name="{{ projectAuthor }}"},
|
|
10
|
+
]
|
|
11
|
+
maintainers = [
|
|
12
|
+
{name="{{ projectAuthor }}"},
|
|
13
|
+
]
|
|
14
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
15
|
+
requires-python = ">={{ pythonMinVersion }}"
|
|
16
|
+
classifiers = [{% for ver in pythonSupportedVersions %}
|
|
17
|
+
"Programming Language :: Python :: {{ ver }}",{% endfor %}
|
|
18
|
+
]
|
|
19
|
+
dependencies = [{% for req in pythonPackageRequirements %}
|
|
20
|
+
"{{ req }}",{% endfor %}
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = [{% for path in pythonSrcFolders %}"{{path}}",{% endfor %}]
|
|
25
|
+
exclude = ["{{pythonTestPackage}}"]
|
|
26
|
+
namespaces = false
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.package-data]
|
|
29
|
+
"*" = ["*"]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.dynamic.version]
|
|
32
|
+
file = "{{ pythonVersionStamp }}"
|
|
33
|
+
|
|
34
|
+
[tool.distutils.bdist_wheel]
|
|
35
|
+
plat-name = "{{ pythonPackagePlatform }}"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[tool.pytest.ini_options]
|
|
2
|
+
testpaths = ["{{pythonTestSources}}"]
|
|
3
|
+
junit_family = "xunit2"
|
|
4
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
|
5
|
+
addopts = [
|
|
6
|
+
"--capture=sys",
|
|
7
|
+
"--junit-xml={{pythonTestOutput}}/report.xml{% for folder in pythonSrcFolders %}",
|
|
8
|
+
"--cov={{folder}}{% endfor %}",
|
|
9
|
+
"--cov-report=html:{{pythonCoverageHtmlReport}}",
|
|
10
|
+
"--cov-report=xml:{{pythonCoverageXmlReport}}",
|
|
11
|
+
"--cov-report=term",
|
|
12
|
+
"--cov-config={{pythonProjectFile}}"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.coverage.run]
|
|
16
|
+
data_file = "{{pythonCoverageFile}}"
|
|
17
|
+
concurrency = ["thread"]
|
|
18
|
+
disable_warnings = ["no-data-collected"]
|
|
19
|
+
branch = true
|
|
20
|
+
omit = [ "{{pythonTestSources}}/*" ]
|
|
@@ -3,17 +3,10 @@
|
|
|
3
3
|
"editor.codeActionsOnSave": {
|
|
4
4
|
"source.organizeImports": "always"
|
|
5
5
|
},
|
|
6
|
-
"editor.defaultFormatter": "
|
|
6
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
7
7
|
"editor.formatOnSave": true
|
|
8
8
|
},
|
|
9
|
-
"
|
|
10
|
-
"-l",
|
|
11
|
-
"{{ pythonLineLength }}"
|
|
12
|
-
],
|
|
13
|
-
"flake8.args": [
|
|
14
|
-
"--ignore={% for rule in pythonIgnoredRules %}{% if loop.index > 1 %},{% endif %}{{rule}}{% endfor %}"
|
|
15
|
-
],
|
|
16
|
-
"python.defaultInterpreterPath": "venv/bin/python",
|
|
9
|
+
"python.defaultInterpreterPath": "{{ venvName }}/bin/python",
|
|
17
10
|
"python.autoComplete.extraPaths": [{% for folder in pythonSrcFolders %}{% if loop.index > 1 %},{% endif %}
|
|
18
11
|
"{{folder}}"{% endfor %}
|
|
19
12
|
],
|
|
@@ -26,7 +19,7 @@
|
|
|
26
19
|
"pyvenv",
|
|
27
20
|
"sdist",
|
|
28
21
|
"venv",
|
|
29
|
-
"
|
|
22
|
+
"pyproject"
|
|
30
23
|
],
|
|
31
24
|
"python.analysis.extraPaths": [{% for folder in pythonSrcFolders %}{% if loop.index > 1 %},{% endif %}
|
|
32
25
|
"{{folder}}"{% endfor %}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[tool.ruff]
|
|
2
|
+
line-length = {{ pythonLineLength }}
|
|
3
|
+
|
|
4
|
+
[tool.ruff.lint]
|
|
5
|
+
select = [
|
|
6
|
+
# pycodestyle
|
|
7
|
+
"E",
|
|
8
|
+
# Pyflakes
|
|
9
|
+
"F",
|
|
10
|
+
# pyupgrade
|
|
11
|
+
"UP",
|
|
12
|
+
# flake8-bugbear
|
|
13
|
+
"B",
|
|
14
|
+
# flake8-simplify
|
|
15
|
+
"SIM",
|
|
16
|
+
# isort
|
|
17
|
+
"I",
|
|
18
|
+
]
|
|
19
|
+
ignore = [{% for rule in pythonIgnoredRules %}{% if loop.index > 1 %},{% endif %}"{{rule}}"{% endfor %}]
|
nmk_python/tests.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python tests builder
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
import shutil
|
|
2
6
|
import subprocess
|
|
3
7
|
import sys
|
|
@@ -8,7 +12,17 @@ from nmk.model.keys import NmkRootConfig
|
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
class PytestBuilder(NmkTaskBuilder):
|
|
15
|
+
"""
|
|
16
|
+
Python tests builder
|
|
17
|
+
"""
|
|
18
|
+
|
|
11
19
|
def build(self, pytest_args: Dict[str, str]):
|
|
20
|
+
"""
|
|
21
|
+
Invoke pytest with specified options
|
|
22
|
+
|
|
23
|
+
:param pytest_args: extra pytest command line args
|
|
24
|
+
"""
|
|
25
|
+
|
|
12
26
|
# Clean outputs
|
|
13
27
|
for p in self.outputs:
|
|
14
28
|
if p.is_dir():
|
|
@@ -28,4 +42,6 @@ class PytestBuilder(NmkTaskBuilder):
|
|
|
28
42
|
args.append(f"--{opt_k}={opt_v}")
|
|
29
43
|
|
|
30
44
|
# Invoke pytest
|
|
31
|
-
|
|
45
|
+
all_args = [sys.executable, "-m", "pytest"] + args
|
|
46
|
+
self.logger.debug(f"Running subprocess: {' '.join(all_args)}")
|
|
47
|
+
subprocess.run(all_args, check=True, cwd=self.model.config[NmkRootConfig.PROJECT_DIR].value)
|
nmk_python/tests.yml
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
config:
|
|
2
|
-
# Tests related
|
|
3
|
-
|
|
4
|
-
- ${BASEDIR}/templates/pytest.
|
|
5
|
-
|
|
6
|
-
# Extra config to get an extendable list of source paths to be ignored for coverage
|
|
7
|
-
pythonSetupItems:
|
|
8
|
-
run:
|
|
9
|
-
omit:
|
|
10
|
-
- ${pythonTestSources}/*
|
|
2
|
+
# Tests related project config
|
|
3
|
+
pythonProjectFileFragments:
|
|
4
|
+
- ${BASEDIR}/templates/pytest.toml.jinja
|
|
11
5
|
|
|
12
6
|
# Tests related venv dependencies
|
|
13
7
|
venvPkgDeps:
|
|
@@ -23,7 +17,7 @@ config:
|
|
|
23
17
|
# Python test output folder
|
|
24
18
|
pythonTestOutput: ${outputDir}/tests
|
|
25
19
|
|
|
26
|
-
# Pytest extra args (not generated in
|
|
20
|
+
# Pytest extra args (not generated in project file)
|
|
27
21
|
pytestExtraArgs:
|
|
28
22
|
numprocesses: auto
|
|
29
23
|
cov-fail-under: 100
|
|
@@ -50,7 +44,7 @@ tasks:
|
|
|
50
44
|
description: Run python tests
|
|
51
45
|
emoji: ":snake:.:crossed_fingers:"
|
|
52
46
|
deps:
|
|
53
|
-
- py.
|
|
47
|
+
- py.install
|
|
54
48
|
appendToDeps: tests
|
|
55
49
|
output:
|
|
56
50
|
- ${pythonCoverageHtmlReport}
|
nmk_python/version.py
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python version handling
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import platform
|
|
1
6
|
import re
|
|
7
|
+
from typing import List
|
|
2
8
|
|
|
3
9
|
from nmk.model.builder import NmkTaskBuilder
|
|
4
|
-
from nmk.model.resolver import NmkStrConfigResolver
|
|
10
|
+
from nmk.model.resolver import NmkListConfigResolver, NmkStrConfigResolver
|
|
5
11
|
|
|
6
12
|
# Git version pattern
|
|
7
|
-
|
|
13
|
+
_GIT_VERSION_PATTERN = re.compile("([^-]+)(?:-([0-9]+))?(?:-(.+))?")
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
class PythonVersionResolver(NmkStrConfigResolver):
|
|
17
|
+
"""
|
|
18
|
+
Python version resolver
|
|
19
|
+
"""
|
|
20
|
+
|
|
11
21
|
def get_value(self, name: str) -> str:
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
"""
|
|
23
|
+
Turn the git version in the Python way
|
|
24
|
+
"""
|
|
14
25
|
git_version = self.model.config["gitVersion"].value
|
|
15
|
-
m =
|
|
26
|
+
m = _GIT_VERSION_PATTERN.match(git_version)
|
|
16
27
|
if m is not None:
|
|
17
28
|
# Build python version from git version segments
|
|
18
29
|
out = m.group(1)
|
|
@@ -30,8 +41,48 @@ class PythonVersionResolver(NmkStrConfigResolver):
|
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
class PythonVersionRefresh(NmkTaskBuilder):
|
|
44
|
+
"""
|
|
45
|
+
Python version builder
|
|
46
|
+
"""
|
|
47
|
+
|
|
33
48
|
def build(self, version: str):
|
|
34
|
-
|
|
49
|
+
"""
|
|
50
|
+
Simple python version dump
|
|
51
|
+
"""
|
|
35
52
|
self.logger.info(self.task.emoji, self.task.description)
|
|
36
53
|
with self.main_output.open("w") as f:
|
|
37
54
|
f.write(version)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PythonSupportedVersionsResolver(NmkListConfigResolver):
|
|
58
|
+
"""
|
|
59
|
+
Supported python versions range resolver
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def get_value(self, name: str) -> List[str]:
|
|
63
|
+
"""
|
|
64
|
+
Returns supported python versions range
|
|
65
|
+
|
|
66
|
+
:return: list of all python versions between min and max supported versions
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def _split_version(v: str) -> List[int]:
|
|
70
|
+
return list(map(int, v.split(".")))
|
|
71
|
+
|
|
72
|
+
# Get min/max values, and verify consistency
|
|
73
|
+
min_ver, max_ver = self.model.config["pythonMinVersion"].value, self.model.config["pythonMaxVersion"].value
|
|
74
|
+
min_split, max_split = _split_version(min_ver), _split_version(max_ver)
|
|
75
|
+
prefix = "Inconsistency in python min/max supported versions: "
|
|
76
|
+
assert len(min_split) == len(max_split), prefix + "not the same segments count"
|
|
77
|
+
assert len(min_split) == 2, prefix + "can only deal with X.Y versions (2 segments expected)"
|
|
78
|
+
assert min_split[0] == max_split[0], prefix + "can't deal with different major versions"
|
|
79
|
+
assert max_split[1] > min_split[1], prefix + "max isn't greater than min"
|
|
80
|
+
|
|
81
|
+
# Also verifies current runtime is in range
|
|
82
|
+
p_ver = platform.python_version()
|
|
83
|
+
cur_split = _split_version(p_ver)
|
|
84
|
+
assert cur_split[0] == max_split[0], prefix + f"current python major version ({p_ver}) doesn't match with supported versions range"
|
|
85
|
+
assert min_split[1] <= cur_split[1] <= max_split[1], prefix + f"current python version ({p_ver}) is out of supported versions range"
|
|
86
|
+
|
|
87
|
+
# Iterate and return versions range
|
|
88
|
+
return [f"{min_split[0]}.{sub}" for sub in range(min_split[1], max_split[1] + 1)]
|
nmk_python/version.yml
CHANGED
|
@@ -6,6 +6,12 @@ config:
|
|
|
6
6
|
# Python version stamp file
|
|
7
7
|
pythonVersionStamp: ${outputDir}/.pythonversion
|
|
8
8
|
|
|
9
|
+
# Python supported versions range (min-max)
|
|
10
|
+
pythonMinVersion: "3.8"
|
|
11
|
+
pythonMaxVersion: "3.12"
|
|
12
|
+
pythonSupportedVersions:
|
|
13
|
+
__resolver__: nmk_python.version.PythonSupportedVersionsResolver
|
|
14
|
+
|
|
9
15
|
tasks:
|
|
10
16
|
# To refresh python version stamp file
|
|
11
17
|
py.version:
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nmk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.1
|
|
4
4
|
Summary: Python plugin for nmk build system
|
|
5
|
-
Home-page: https://github.com/dynod/nmk-python
|
|
6
5
|
Author: The dynod project
|
|
7
6
|
Maintainer: The dynod project
|
|
8
|
-
License: Mozilla Public License Version 2.0
|
|
9
7
|
Classifier: Programming Language :: Python :: 3.8
|
|
10
8
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.8
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
15
14
|
License-File: LICENSE
|
|
16
15
|
Requires-Dist: nmk-base
|
|
17
16
|
Requires-Dist: Jinja2
|
|
17
|
+
Requires-Dist: tomlkit
|
|
18
18
|
|
|
19
19
|
# nmk-python
|
|
20
20
|
Python plugin for nmk build system
|
|
@@ -25,16 +25,15 @@ Python plugin for nmk build system
|
|
|
25
25
|
[](https://github.com/dynod/nmk/issues?q=is%3Aopen+is%3Aissue+label%3Aplugin%3Apython)
|
|
26
26
|
[](https://www.python.org/)
|
|
27
27
|
[](https://pypi.org/project/nmk-python/)
|
|
28
|
-
[](https://astral.sh/ruff)
|
|
29
|
+
[](https://astral.sh/ruff)
|
|
30
30
|
[](https://app.codecov.io/gh/dynod/nmk-python)
|
|
31
|
+
[](https://nmk-python.readthedocs.io/)
|
|
31
32
|
<!-- NMK-BADGES-END -->
|
|
32
33
|
|
|
33
34
|
This plugin adds support for Python development in an **`nmk`** project:
|
|
34
|
-
*
|
|
35
|
-
* code format with [
|
|
36
|
-
* import sorting with [isort](https://github.com/PyCQA/isort)
|
|
37
|
-
* code analysis with [flake8](https://flake8.pycqa.org/)
|
|
35
|
+
* project file generation
|
|
36
|
+
* code format and analysis with [ruff](https://astral.sh/ruff)
|
|
38
37
|
* python wheel build
|
|
39
38
|
* test with [pytest](https://pytest.org)
|
|
40
39
|
* VSCode settings generation (if [**`nmk-vscode`**](https://github.com/dynod/nmk-vscode) plugin is also used)
|
|
@@ -54,7 +53,7 @@ refs:
|
|
|
54
53
|
|
|
55
54
|
## Documentation
|
|
56
55
|
|
|
57
|
-
This plugin documentation is available [here](https://
|
|
56
|
+
This plugin documentation is available [here](https://nmk-python.readthedocs.io/)
|
|
58
57
|
|
|
59
58
|
## Issues
|
|
60
59
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
nmk_python/__init__.py,sha256=EouCWXbAeObby1vOCx0Vx_WaGC2fEEFDMCen3JZt4TU,446
|
|
2
|
+
nmk_python/badges.yml,sha256=RAn_0A7NdHb0leF-MdXsVPtS6kOyfsUGb61GBXqDc-8,911
|
|
3
|
+
nmk_python/build.py,sha256=8PgpaWqx6jTSc7eZsvNC4h8WhfM9DoB0SqhCK6_BSpA,4898
|
|
4
|
+
nmk_python/build.yml,sha256=kgG-_6NyAX25B45plaDEHUMk2RYgaSYyM8y7c952JlQ,2989
|
|
5
|
+
nmk_python/doc.yml,sha256=12amAlYylH7JAHaqBIOoEZ_LrXY_bPNKuHrrwUkIj8w,932
|
|
6
|
+
nmk_python/files.py,sha256=T2-MBoc-KEmnhCC78IbGsi6Q-ntk_IcOtWVylLEhSAc,1887
|
|
7
|
+
nmk_python/files.yml,sha256=-vGjogdulwsfdKVEUa5RARYDKxF3m9RmHKFnIhaT3_k,780
|
|
8
|
+
nmk_python/plugin.yml,sha256=hi7osXyIwpHgJAsww5mhOA6emxhzgitDF8cOsvyY29Y,1068
|
|
9
|
+
nmk_python/project.py,sha256=83E9tYv3DpKlF-tens7iNfWcj08v2QBxzBWJwuLHTCk,3063
|
|
10
|
+
nmk_python/project.yml,sha256=Uq8yKoqOgY2sIQweUJhrLALLFuc76uNmUZsz7hrb9pI,947
|
|
11
|
+
nmk_python/ruff.py,sha256=T0o9i_ZZosJDWRuLNkgYIzL9mb_KmNMsmQ1kDq08gBU,1082
|
|
12
|
+
nmk_python/ruff.yml,sha256=VIlH52dc4m-dgsm-SFuD4-AK6knke1-GuJSJQvVsXWE,1863
|
|
13
|
+
nmk_python/tests.py,sha256=nOgi6-Jh0rMQnl0vKqgAFuitiOpQVZmsjdhQZz_vuhc,1236
|
|
14
|
+
nmk_python/tests.yml,sha256=8th_7_7nII84URse1dmbhNJ1_l0ZvsRD-C9uhNWJ7GA,1663
|
|
15
|
+
nmk_python/version.py,sha256=HFZcZ_ZMxwVOR2OjUFF2cFc2Og3qPo5Bph3PiTDkhHU,3133
|
|
16
|
+
nmk_python/version.yml,sha256=YGvVUnJ0-HjWfyXOzDWqQzLSBF68Fd8cNT6ESHAr6oE,904
|
|
17
|
+
nmk_python/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
nmk_python/templates/build.toml.jinja,sha256=EBuBZpWkphpVj3X1j9s3WIL11-OcLj0u7e19CqxJEDU,902
|
|
19
|
+
nmk_python/templates/pytest.toml.jinja,sha256=zQ3SRWnF23Omxm7zInE54j0l3OyjXSOn5pQ5Pak8Boo,641
|
|
20
|
+
nmk_python/templates/python-settings.json.jinja,sha256=-6J1tybWEzChxlDqu4tp9BMWxbYD8HpztGs9G8LkC_w,877
|
|
21
|
+
nmk_python/templates/python-test-debug-launch.json.jinja,sha256=J_xju2EpHNXvgvqBxlf99p7g-ZMh3xE-_2r7o_57gpU,364
|
|
22
|
+
nmk_python/templates/ruff.toml.jinja,sha256=DTHe1ixK1SBY8d2xBCfkGz89HzjAoMO7Ogs12AY1i4Y,346
|
|
23
|
+
nmk_python-0.15.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
24
|
+
nmk_python-0.15.1.dist-info/METADATA,sha256=H4nqMsSwdZLASMcnB-TYIXZzekRoT6XnBq1cQP7WOH4,2790
|
|
25
|
+
nmk_python-0.15.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
26
|
+
nmk_python-0.15.1.dist-info/top_level.txt,sha256=MCb5w_hoOZ0kk3Dajxrd9Pz6lZ3pyuS6TQCRA3vddTo,11
|
|
27
|
+
nmk_python-0.15.1.dist-info/RECORD,,
|
nmk_python/flake.yml
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
config:
|
|
2
|
-
|
|
3
|
-
# Needs flake8 and plugins
|
|
4
|
-
venvPkgDeps:
|
|
5
|
-
- flake8
|
|
6
|
-
- pep8-naming
|
|
7
|
-
- flake8-builtins
|
|
8
|
-
- flake8-blind-except
|
|
9
|
-
- flake8-comprehensions
|
|
10
|
-
- flake8-string-format
|
|
11
|
-
- flake8-pep3101
|
|
12
|
-
- flake8-bugbear
|
|
13
|
-
|
|
14
|
-
# Add setup file for flake8 config
|
|
15
|
-
pythonSetupCfgFiles:
|
|
16
|
-
- ${BASEDIR}/templates/flake.cfg
|
|
17
|
-
|
|
18
|
-
# Also contribute to setup items for extendable flake8 exclusion paths list
|
|
19
|
-
pythonSetupItems:
|
|
20
|
-
flake8:
|
|
21
|
-
exclude:
|
|
22
|
-
- __pycache__
|
|
23
|
-
|
|
24
|
-
# Stamp file for flake8 execution
|
|
25
|
-
pythonFlakeStamp: ${outputDir}/.flake
|
|
26
|
-
|
|
27
|
-
# Ignored rules
|
|
28
|
-
pythonIgnoredRules:
|
|
29
|
-
- E203 # space before ":"; sometimes formatting with black introduces this kind of space in slice expressions
|
|
30
|
-
- E501 # line length; we're working with lines wider than the recommended 79 length
|
|
31
|
-
- B902 # allow blind Exception catch; typically usefull for fallback behaviors
|
|
32
|
-
- W503 # line break before operator; incompatible with black formatting
|
|
33
|
-
|
|
34
|
-
# Require VSCode specific extension for flake8 analysis
|
|
35
|
-
vscodeExtensionsNames:
|
|
36
|
-
- ms-python.flake8
|
|
37
|
-
|
|
38
|
-
tasks:
|
|
39
|
-
|
|
40
|
-
# Python source code analysis with flake8
|
|
41
|
-
py.analyze:
|
|
42
|
-
description: Analyze python code
|
|
43
|
-
emoji: ":snake:.:eye: "
|
|
44
|
-
deps:
|
|
45
|
-
- py.setup
|
|
46
|
-
- py.format
|
|
47
|
-
appendToDeps: setup
|
|
48
|
-
if: ${pythonSrcFiles} # Only if python source files are found
|
|
49
|
-
output:
|
|
50
|
-
- ${pythonFlakeStamp}
|
|
51
|
-
input:
|
|
52
|
-
- ${pythonSetupCfg}
|
|
53
|
-
- ${pythonSrcFiles}
|
|
54
|
-
builder: nmk_python.flake.FlakeBuilder
|
|
55
|
-
params:
|
|
56
|
-
src_folders: ${pythonSrcFolders}
|
nmk_python/format.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from nmk.model.builder import NmkTaskBuilder
|
|
5
|
-
from nmk.utils import run_with_logs
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class FormatBuilder(NmkTaskBuilder):
|
|
9
|
-
def build(self, src_files: List[str], src_folders: List[str], line_length: int):
|
|
10
|
-
# Delegate to black
|
|
11
|
-
run_with_logs([sys.executable, "-m", "black", "-l", str(line_length)] + src_files, self.logger)
|
|
12
|
-
|
|
13
|
-
# Delegate to isort
|
|
14
|
-
run_with_logs([sys.executable, "-m", "isort"] + src_folders, self.logger)
|
|
15
|
-
|
|
16
|
-
# Touch output file
|
|
17
|
-
self.main_output.touch()
|
nmk_python/format.yml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
config:
|
|
2
|
-
|
|
3
|
-
# Needs black and isort
|
|
4
|
-
venvPkgDeps:
|
|
5
|
-
- black
|
|
6
|
-
- isort
|
|
7
|
-
|
|
8
|
-
# Max line length
|
|
9
|
-
pythonLineLength: 160
|
|
10
|
-
|
|
11
|
-
# Stamp file for format tools execution
|
|
12
|
-
pythonFormatStamp: ${outputDir}/.format
|
|
13
|
-
|
|
14
|
-
# Add setup file for isort config
|
|
15
|
-
pythonSetupCfgFiles:
|
|
16
|
-
- ${BASEDIR}/templates/isort.cfg
|
|
17
|
-
|
|
18
|
-
# Require VSCode specific extension for black formatting
|
|
19
|
-
vscodeExtensionsNames:
|
|
20
|
-
- ms-python.black-formatter
|
|
21
|
-
|
|
22
|
-
tasks:
|
|
23
|
-
# Code format tools execution
|
|
24
|
-
py.format:
|
|
25
|
-
description: Format python code
|
|
26
|
-
emoji: ":snake:.:lipstick:"
|
|
27
|
-
deps:
|
|
28
|
-
- out
|
|
29
|
-
- py.setup
|
|
30
|
-
appendToDeps: setup
|
|
31
|
-
if: ${pythonSrcFiles} # Only if python source files are found
|
|
32
|
-
output:
|
|
33
|
-
- ${pythonFormatStamp}
|
|
34
|
-
input:
|
|
35
|
-
- ${pythonSrcFiles}
|
|
36
|
-
- ${pythonSetupCfg}
|
|
37
|
-
builder: nmk_python.format.FormatBuilder
|
|
38
|
-
params:
|
|
39
|
-
src_files: ${pythonSrcFiles}
|
|
40
|
-
src_folders: ${pythonSrcFolders}
|
|
41
|
-
line_length: ${pythonLineLength}
|
nmk_python/setup.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import platform
|
|
2
|
-
from configparser import ConfigParser
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import List, Union
|
|
5
|
-
|
|
6
|
-
from nmk.model.resolver import NmkListConfigResolver
|
|
7
|
-
from nmk_base.common import TemplateBuilder
|
|
8
|
-
|
|
9
|
-
LIST_SEPARATOR = "\n"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class PythonSupportedVersionsResolver(NmkListConfigResolver):
|
|
13
|
-
def get_value(self, name: str) -> List[str]:
|
|
14
|
-
def split_version(v: str) -> List[int]:
|
|
15
|
-
return list(map(int, v.split(".")))
|
|
16
|
-
|
|
17
|
-
# Get min/max values, and verify consistency
|
|
18
|
-
min_ver, max_ver = self.model.config["pythonMinVersion"].value, self.model.config["pythonMaxVersion"].value
|
|
19
|
-
min_split, max_split = split_version(min_ver), split_version(max_ver)
|
|
20
|
-
prefix = "Inconsistency in python min/max supported versions: "
|
|
21
|
-
assert len(min_split) == len(max_split), prefix + "not the same segments count"
|
|
22
|
-
assert len(min_split) == 2, prefix + "can only deal with X.Y versions (2 segments expected)"
|
|
23
|
-
assert min_split[0] == max_split[0], prefix + "can't deal with different major versions"
|
|
24
|
-
assert max_split[1] > min_split[1], prefix + "max isn't greater than min"
|
|
25
|
-
|
|
26
|
-
# Also verifies current runtime is in range
|
|
27
|
-
p_ver = platform.python_version()
|
|
28
|
-
cur_split = split_version(p_ver)
|
|
29
|
-
assert cur_split[0] == max_split[0], prefix + f"current python major version ({p_ver}) doesn't match with supported versions range"
|
|
30
|
-
assert min_split[1] <= cur_split[1] <= max_split[1], prefix + f"current python version ({p_ver}) is out of supported versions range"
|
|
31
|
-
|
|
32
|
-
# Iterate and return versions range
|
|
33
|
-
return [f"{min_split[0]}.{sub}" for sub in range(min_split[1], max_split[1] + 1)]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class PythonSetupBuilder(TemplateBuilder):
|
|
37
|
-
def handle_ini_values(self, values: Union[str, List[str]]):
|
|
38
|
-
# Turn list into a single block of text values
|
|
39
|
-
return (LIST_SEPARATOR + LIST_SEPARATOR.join([self.relative_path(str(v)) for v in values])) if isinstance(values, list) else values
|
|
40
|
-
|
|
41
|
-
def build(self, setup_cfg_files: List[str], setup_items: dict):
|
|
42
|
-
# Merge setup fragments to generate final setup
|
|
43
|
-
setup_cfg_output = self.main_output
|
|
44
|
-
c = ConfigParser()
|
|
45
|
-
for f_path in map(Path, setup_cfg_files):
|
|
46
|
-
# Update config with rendered template
|
|
47
|
-
c.read_string(self.render_template(f_path, {}))
|
|
48
|
-
|
|
49
|
-
# Iterate on items contributed through yml project files (only ones contributing maps)
|
|
50
|
-
for section, values in filter(lambda t: isinstance(t[1], dict), setup_items.items()):
|
|
51
|
-
# Create new section is not done yet
|
|
52
|
-
if not c.has_section(section):
|
|
53
|
-
c.add_section(section)
|
|
54
|
-
|
|
55
|
-
# Handle list of values
|
|
56
|
-
prepared_values = {k: self.handle_ini_values(v) for k, v in values.items()}
|
|
57
|
-
|
|
58
|
-
# Finally update section values
|
|
59
|
-
c[section].update(prepared_values)
|
|
60
|
-
|
|
61
|
-
# Finally write config to output file
|
|
62
|
-
with setup_cfg_output.open("w") as f:
|
|
63
|
-
c.write(f)
|
nmk_python/setup.yml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
config:
|
|
2
|
-
# Generated setup config files
|
|
3
|
-
pythonSetupCfg: ${PROJECTDIR}/setup.cfg
|
|
4
|
-
|
|
5
|
-
# Paths to setup.cfg fragments to be merged
|
|
6
|
-
pythonSetupCfgFiles: []
|
|
7
|
-
|
|
8
|
-
# Contributed setup items from yml files
|
|
9
|
-
pythonSetupItems: {}
|
|
10
|
-
|
|
11
|
-
# Python supported versions range (min-max)
|
|
12
|
-
pythonMinVersion: "3.8"
|
|
13
|
-
pythonMaxVersion: "3.12"
|
|
14
|
-
pythonSupportedVersions:
|
|
15
|
-
__resolver__: nmk_python.setup.PythonSupportedVersionsResolver
|
|
16
|
-
|
|
17
|
-
# Contribute to gitignore
|
|
18
|
-
gitIgnoredFiles:
|
|
19
|
-
- ${pythonSetupCfg}
|
|
20
|
-
|
|
21
|
-
tasks:
|
|
22
|
-
py.setup:
|
|
23
|
-
description: Generate python setup files
|
|
24
|
-
emoji: ":snake:.:toolbox:"
|
|
25
|
-
input:
|
|
26
|
-
- ${pythonSetupCfgFiles}
|
|
27
|
-
- ${pythonVersionStamp}
|
|
28
|
-
output:
|
|
29
|
-
- ${pythonSetupCfg}
|
|
30
|
-
deps:
|
|
31
|
-
- py.version
|
|
32
|
-
appendToDeps: setup
|
|
33
|
-
if: ${pythonSrcFiles} # Only if python source files are found
|
|
34
|
-
builder: nmk_python.setup.PythonSetupBuilder
|
|
35
|
-
params:
|
|
36
|
-
setup_cfg_files: ${pythonSetupCfgFiles}
|
|
37
|
-
setup_items: ${pythonSetupItems}
|
nmk_python/templates/build.cfg
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
name = {{ pythonPackage }}
|
|
3
|
-
author = {{ projectAuthor }}
|
|
4
|
-
maintainer = {{ projectAuthor }}
|
|
5
|
-
version = {{ pythonVersion }}
|
|
6
|
-
long_description = file: README.md
|
|
7
|
-
long_description_content_type = text/markdown
|
|
8
|
-
license = Mozilla Public License Version 2.0
|
|
9
|
-
classifiers ={% for ver in pythonSupportedVersions %}
|
|
10
|
-
Programming Language :: Python :: {{ ver }}{% endfor %}
|
|
11
|
-
|
|
12
|
-
[options]
|
|
13
|
-
package_dir = ={{ pythonSrcFolders[0] }}
|
|
14
|
-
packages = find:
|
|
15
|
-
install_requires ={% for req in pythonPackageRequirements %}
|
|
16
|
-
{{ req }}{% endfor %}
|
|
17
|
-
|
|
18
|
-
[options.packages.find]
|
|
19
|
-
where = {{ pythonSrcFolders[0] }}
|
|
20
|
-
exclude = tests
|
nmk_python/templates/flake.cfg
DELETED
nmk_python/templates/isort.cfg
DELETED
nmk_python/templates/pytest.cfg
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
[tool:pytest]
|
|
2
|
-
testpaths = {{pythonTestSources}}
|
|
3
|
-
junit_family = xunit2
|
|
4
|
-
filterwarnings =
|
|
5
|
-
ignore::DeprecationWarning
|
|
6
|
-
addopts =
|
|
7
|
-
--capture=sys
|
|
8
|
-
--junit-xml={{pythonTestOutput}}/report.xml{% for folder in pythonSrcFolders %}
|
|
9
|
-
--cov={{folder}}{% endfor %}
|
|
10
|
-
--cov-report=html:{{pythonCoverageHtmlReport}}
|
|
11
|
-
--cov-report=xml:{{pythonCoverageXmlReport}}
|
|
12
|
-
--cov-report=term
|
|
13
|
-
--cov-config={{pythonSetupCfg}}
|
|
14
|
-
|
|
15
|
-
; coverage configuration
|
|
16
|
-
[run]
|
|
17
|
-
data_file = {{pythonCoverageFile}}
|
|
18
|
-
concurrency = thread
|
|
19
|
-
disable_warnings = no-data-collected
|
|
20
|
-
branch = True
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
nmk_python/__init__.py,sha256=pVT-8I5dH_SkG5OQBtnOyIrWTtBh16mdua0Rah5ADPw,705
|
|
2
|
-
nmk_python/badges.yml,sha256=TaauzvFP9KIiudSeq-YihcaZACKis6wwF526miVOguw,926
|
|
3
|
-
nmk_python/build.py,sha256=y5W5mcn0ZYSvFC28cxxxOtpm2qodiVFAANxMtBBVKpg,1134
|
|
4
|
-
nmk_python/build.yml,sha256=P_5X-Vj4tDcFLauRpkzWQKg8vMpGX4q33L1jRHy2I2s,2548
|
|
5
|
-
nmk_python/doc.yml,sha256=12amAlYylH7JAHaqBIOoEZ_LrXY_bPNKuHrrwUkIj8w,932
|
|
6
|
-
nmk_python/files.py,sha256=2loPwmbZdqYNod6ML85s0dpBY4jx6--AB5M2zQLzi5s,1152
|
|
7
|
-
nmk_python/files.yml,sha256=fg8oPpTdjOSquIWw_cqcn5UyXfQHZzCVNSyHc95wfvE,703
|
|
8
|
-
nmk_python/flake.py,sha256=5EmeBuTlT4HiUj5KMMnELyQ1eaMd3j3DnEax6m4gzn0,895
|
|
9
|
-
nmk_python/flake.yml,sha256=FaoIzqVuD_YxbW6YruuO-Sfm0YAiwpztOdZv_evJvNA,1701
|
|
10
|
-
nmk_python/format.py,sha256=JQCtKrWB8IsGzcqRTBZ7AbsVe6eG50sFibucj8ig4Ls,546
|
|
11
|
-
nmk_python/format.yml,sha256=3R2Bhbg3AZRV8YgKVBjGY8WnnNJA-28GS867RDeW2AY,1068
|
|
12
|
-
nmk_python/plugin.yml,sha256=ZXP1G7n6zrEu9H50_H_1XqDG-vE09x8SMJJpai7niQ8,1139
|
|
13
|
-
nmk_python/setup.py,sha256=URmntiw4rCe999CgRoO7EhnvvYyO1NAeeO42qplzY2M,2995
|
|
14
|
-
nmk_python/setup.yml,sha256=glSyp98_OwoZbJSgoopgkCFiUeKkbkCntLQTSl4S8sw,1068
|
|
15
|
-
nmk_python/tests.py,sha256=rKKJKKx4g3C6TAVR_aNhsgPlMN3bU61dUqKx3seqq4s,935
|
|
16
|
-
nmk_python/tests.yml,sha256=-rMWZC5PeadjOQu82d_OpmupFDs4Q1mzU3XxZBBlLTM,1825
|
|
17
|
-
nmk_python/version.py,sha256=f-b--EYQMw2G2I8gCeMHDwOM8OQC8nu_a2cEtkAS6rQ,1278
|
|
18
|
-
nmk_python/version.yml,sha256=1l-PnAgRA1WRsjtxTOBmA_iP6LJDSWUu9Jr7bF_dVGM,696
|
|
19
|
-
nmk_python/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
nmk_python/templates/build.cfg,sha256=QxElw9uo30FkBkb-fNRYrdF1lWI5IoGg6c5EDSFMjQA,601
|
|
21
|
-
nmk_python/templates/flake.cfg,sha256=2XufwS9-_MPcGRwDvGHcd0dUAxdLnV4D3IEi69yqRhw,246
|
|
22
|
-
nmk_python/templates/isort.cfg,sha256=7ZGCSLYAm1ZPmREwzDQS718e8Zp3Aa7ETtX5uuVxvR4,67
|
|
23
|
-
nmk_python/templates/pytest.cfg,sha256=m1oCLagZkiwimF4qES5d6BEMJRZm_SiqZMllJK5fj9E,561
|
|
24
|
-
nmk_python/templates/python-settings.json.jinja,sha256=nwuuBJwj-nY6q5d1bBhnCV4nVa1YZvhm7QGBM2fubCM,1094
|
|
25
|
-
nmk_python/templates/python-test-debug-launch.json.jinja,sha256=J_xju2EpHNXvgvqBxlf99p7g-ZMh3xE-_2r7o_57gpU,364
|
|
26
|
-
nmk_python-0.14.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
27
|
-
nmk_python-0.14.1.dist-info/METADATA,sha256=khH9qCDLORITi1aezs5ZfnM_4zhKiC03bxpuvHMjEqY,2849
|
|
28
|
-
nmk_python-0.14.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
29
|
-
nmk_python-0.14.1.dist-info/top_level.txt,sha256=MCb5w_hoOZ0kk3Dajxrd9Pz6lZ3pyuS6TQCRA3vddTo,11
|
|
30
|
-
nmk_python-0.14.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|