dazpycheck 0.4.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.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: dazpycheck
3
+ Version: 0.4.0
4
+ Summary: A tool to check and validate a Python code repository.
5
+ Author-email: Darren Oakey <darren@oakey.net>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+
12
+ # dazpycheck: Your AI Code's Quality Guardian
13
+
14
+ ![dazpycheck banner](output/banner.png)
15
+
16
+ **dazpycheck** is a powerful and comprehensive tool for ensuring your Python code is clean, correct, and consistent. It's especially useful for checking code generated by AI, which can sometimes take shortcuts or introduce subtle errors.
17
+
18
+ ## Why dazpycheck?
19
+
20
+ AI is a powerful tool, but it's not perfect. AI-generated code can sometimes be messy, inefficient, or even contain bugs. `dazpycheck` helps you catch these issues before they become problems. It's the perfect last step in your development workflow to ensure your code is production-ready.
21
+
22
+ ![dazpycheck logo](output/logo.png)
23
+
24
+ ## Features
25
+
26
+ * **All-in-One Checking:** dazpycheck integrates `black` for formatting, `flake8` for linting, `pyright` for type checking, and `py_compile` for compilation, giving you a complete picture of your code's health.
27
+ * **Automated Fixing:** By default, `dazpycheck` will automatically fix formatting issues. Use the `--readonly` flag to see what would be changed without modifying any files.
28
+ * **Banned Words Enforcement:** Define a list of "banned words" to enforce your own coding standards. `dazpycheck` will scan your entire repository and flag any occurrences of these words.
29
+ * **Parallel Execution:** `dazpycheck` runs all readonly checks in parallel, making it fast and efficient, even on large codebases.
30
+ * **Integrated Test Runner:** By default, `dazpycheck` will run all your `unittest` and `pytest` tests. Use the `--no-test` flag to skip testing.
31
+
32
+ ![Parallel Checks](output/parallel_checks.png)
33
+
34
+ ## Installation
35
+
36
+ `dazpycheck` is available on PyPI. You can install it using `pip`:
37
+
38
+ ```bash
39
+ pip install dazpycheck
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Once installed, you can run `dazpycheck` from anywhere on your system:
45
+
46
+ ```bash
47
+ dazpycheck
48
+ ```
49
+
50
+ This will run all checks and tests on the current directory. You can also specify a directory:
51
+
52
+ ```bash
53
+ dazpycheck /path/to/your/project
54
+ ```
55
+
56
+ ### Options
57
+
58
+ * `--readonly`: Only check for issues, don't modify files.
59
+ * `--no-test`: Skip running tests.
60
+ * `--single-thread`: Run checks sequentially.
61
+ * `--full`: Run all checks regardless of failures.
62
+
63
+ ![Banned Words](output/banned_words.png)
64
+
65
+ We hope you find `dazpycheck` to be a valuable addition to your development workflow!
@@ -0,0 +1,54 @@
1
+ # dazpycheck: Your AI Code's Quality Guardian
2
+
3
+ ![dazpycheck banner](output/banner.png)
4
+
5
+ **dazpycheck** is a powerful and comprehensive tool for ensuring your Python code is clean, correct, and consistent. It's especially useful for checking code generated by AI, which can sometimes take shortcuts or introduce subtle errors.
6
+
7
+ ## Why dazpycheck?
8
+
9
+ AI is a powerful tool, but it's not perfect. AI-generated code can sometimes be messy, inefficient, or even contain bugs. `dazpycheck` helps you catch these issues before they become problems. It's the perfect last step in your development workflow to ensure your code is production-ready.
10
+
11
+ ![dazpycheck logo](output/logo.png)
12
+
13
+ ## Features
14
+
15
+ * **All-in-One Checking:** dazpycheck integrates `black` for formatting, `flake8` for linting, `pyright` for type checking, and `py_compile` for compilation, giving you a complete picture of your code's health.
16
+ * **Automated Fixing:** By default, `dazpycheck` will automatically fix formatting issues. Use the `--readonly` flag to see what would be changed without modifying any files.
17
+ * **Banned Words Enforcement:** Define a list of "banned words" to enforce your own coding standards. `dazpycheck` will scan your entire repository and flag any occurrences of these words.
18
+ * **Parallel Execution:** `dazpycheck` runs all readonly checks in parallel, making it fast and efficient, even on large codebases.
19
+ * **Integrated Test Runner:** By default, `dazpycheck` will run all your `unittest` and `pytest` tests. Use the `--no-test` flag to skip testing.
20
+
21
+ ![Parallel Checks](output/parallel_checks.png)
22
+
23
+ ## Installation
24
+
25
+ `dazpycheck` is available on PyPI. You can install it using `pip`:
26
+
27
+ ```bash
28
+ pip install dazpycheck
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ Once installed, you can run `dazpycheck` from anywhere on your system:
34
+
35
+ ```bash
36
+ dazpycheck
37
+ ```
38
+
39
+ This will run all checks and tests on the current directory. You can also specify a directory:
40
+
41
+ ```bash
42
+ dazpycheck /path/to/your/project
43
+ ```
44
+
45
+ ### Options
46
+
47
+ * `--readonly`: Only check for issues, don't modify files.
48
+ * `--no-test`: Skip running tests.
49
+ * `--single-thread`: Run checks sequentially.
50
+ * `--full`: Run all checks regardless of failures.
51
+
52
+ ![Banned Words](output/banned_words.png)
53
+
54
+ We hope you find `dazpycheck` to be a valuable addition to your development workflow!
@@ -0,0 +1,31 @@
1
+
2
+ [build-system]
3
+ requires = ["setuptools>=61.0"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "dazpycheck"
8
+ dynamic = ["version"]
9
+ authors = [
10
+ { name="Darren Oakey", email="darren@oakey.net" },
11
+ ]
12
+ description = "A tool to check and validate a Python code repository."
13
+ readme = "README.md"
14
+ requires-python = ">=3.7"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+
21
+ [project.scripts]
22
+ dazpycheck = "dazpycheck.main:cli"
23
+
24
+ [tool.setuptools]
25
+ package-dir = {"" = "src"}
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.setuptools.dynamic]
31
+ version = {attr = "dazpycheck.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ """
2
+ Setup configuration for dazpycheck package.
3
+
4
+ This setup script configures the package for distribution. The version number
5
+ is read from dazpycheck/__init__.py to maintain a single source of truth.
6
+ """
7
+
8
+ import os
9
+ import re
10
+ from setuptools import setup
11
+
12
+
13
+ def get_version():
14
+ """Get version from src/dazpycheck/__init__.py"""
15
+ version_file = os.path.join(
16
+ os.path.dirname(__file__), "src", "dazpycheck", "__init__.py"
17
+ )
18
+ with open(version_file, "r", encoding="utf-8") as f:
19
+ version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", f.read(), re.M)
20
+ if version_match:
21
+ return version_match.group(1)
22
+ raise RuntimeError("Unable to find version string")
23
+
24
+
25
+ if __name__ == "__main__":
26
+ setup()
@@ -0,0 +1,11 @@
1
+ # dazpycheck: ignore-banned-words
2
+ """
3
+ dazpycheck - A tool to check and validate Python code repositories
4
+
5
+ This tool enforces code quality standards, test coverage, and anti-mocking practices.
6
+ """
7
+
8
+ from .main import main, cli
9
+
10
+ __version__ = "0.4.0"
11
+ __all__ = ["main", "cli"]
@@ -0,0 +1,222 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ from multiprocessing import Pool, cpu_count
5
+ import coverage
6
+ import argparse
7
+ import unittest
8
+
9
+ # dazpycheck: ignore-banned-words
10
+ BANNED_WORDS = ["mock", "fallback", "simulate", "pretend", "fake"]
11
+ BANNED_WORDS_SPIEL = """
12
+ Banned word found. This isn't about specific words - it's about practices. Mocking is always bad.
13
+ Fallbacks are bad - if something fails, we want it to fail, not pretend to work. If you need or
14
+ want a mock in a test, it suggests the structure of your actual code needs improvement. Look at
15
+ your code and see if it can be restructured to separate dependencies so tests can run fast
16
+ without mocks. Focus on creating small, testable functions with clear interfaces.
17
+ """
18
+
19
+
20
+ def run_command(command):
21
+ try:
22
+ subprocess.run(command, capture_output=True, text=True, check=True)
23
+ return True, ""
24
+ except subprocess.CalledProcessError as e:
25
+ return False, f"{e.stdout}\n{e.stderr}"
26
+
27
+
28
+ def check_banned_words_in_file(file_path):
29
+ try:
30
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
31
+ content = f.read()
32
+ if "dazpycheck: ignore-banned-words" in content:
33
+ return True, ""
34
+ lines = content.splitlines()
35
+ for line_num, line in enumerate(lines, 1):
36
+ for word in BANNED_WORDS:
37
+ if word in line:
38
+ return (
39
+ False,
40
+ f"{file_path}:{line_num}: Banned word '{word}' found.\n{BANNED_WORDS_SPIEL}",
41
+ )
42
+ except Exception:
43
+ pass # Ignore files that can't be read
44
+ return True, ""
45
+
46
+
47
+ def compile_file(file_path):
48
+ return run_command(["python", "-m", "py_compile", file_path])
49
+
50
+
51
+ def run_test_on_file(file_path):
52
+ source_file = file_path.replace("_test.py", ".py")
53
+ if not os.path.exists(source_file):
54
+ return (
55
+ False,
56
+ f"Test file {file_path} exists but corresponding source file {source_file} does not.",
57
+ )
58
+
59
+ # Add the directory of the test file to the python path
60
+ sys.path.insert(0, os.path.dirname(file_path))
61
+
62
+ cov = coverage.Coverage(source=[source_file])
63
+ cov.start()
64
+
65
+ # Try pytest first, then fall back to unittest
66
+ pytest_success, pytest_message = run_command(
67
+ ["python", "-m", "pytest", file_path, "-q", "--tb=no"]
68
+ )
69
+
70
+ if not pytest_success:
71
+ # Try unittest as fallback
72
+ suite = unittest.TestLoader().discover(
73
+ start_dir=os.path.dirname(file_path), pattern=os.path.basename(file_path)
74
+ )
75
+ result = unittest.TextTestRunner(
76
+ failfast=True, stream=open(os.devnull, "w")
77
+ ).run(suite)
78
+ if not result.wasSuccessful():
79
+ cov.stop()
80
+ sys.path.pop(0)
81
+ return (
82
+ False,
83
+ f"Tests failed in {file_path} (tried both pytest and unittest)",
84
+ )
85
+
86
+ cov.stop()
87
+ cov.save()
88
+
89
+ try:
90
+ filename, statements, excluded, missing, formatted = cov.analysis2(source_file)
91
+ total_statements = len(statements)
92
+ executed_statements = total_statements - len(missing)
93
+ coverage_percentage = (
94
+ (executed_statements / total_statements) * 100
95
+ if total_statements > 0
96
+ else 100
97
+ )
98
+ except coverage.misc.NoSource:
99
+ sys.path.pop(0)
100
+ return (
101
+ False,
102
+ f"Coverage data not available for {source_file}. Module may not have been imported.",
103
+ )
104
+
105
+ if coverage_percentage < 50:
106
+ sys.path.pop(0)
107
+ return (
108
+ False,
109
+ f"Coverage for {source_file} is {coverage_percentage:.2f}%, which is less than 50%.",
110
+ )
111
+
112
+ # Remove the directory from the python path
113
+ sys.path.pop(0)
114
+
115
+ return True, ""
116
+
117
+
118
+ def main(directory, fix, single_thread, full):
119
+ if fix:
120
+ run_command(["python3", "-m", "black", directory])
121
+
122
+ py_files = []
123
+ test_files = []
124
+ for root, dirs, files in os.walk(directory):
125
+ # Skip build, dist, and cache directories
126
+ dirs[:] = [
127
+ d
128
+ for d in dirs
129
+ if d not in ("__pycache__", "build", "dist", ".git", ".pytest_cache")
130
+ ]
131
+ for file in files:
132
+ if file.endswith(".py"):
133
+ py_files.append(os.path.join(root, file))
134
+ if file.endswith("_test.py"):
135
+ test_files.append(os.path.join(root, file))
136
+
137
+ # Initial scan for missing tests and banned words
138
+ has_errors = False
139
+ for py_file in py_files:
140
+ if not py_file.endswith("_test.py"):
141
+ # Skip setup.py, __init__.py, and build directory
142
+ if (
143
+ py_file.endswith("/setup.py")
144
+ or py_file == "setup.py"
145
+ or py_file.endswith("/__init__.py")
146
+ or "/build/" in py_file
147
+ ):
148
+ continue
149
+ test_file = py_file.replace(".py", "_test.py")
150
+ if not os.path.exists(test_file):
151
+ has_errors = True
152
+ print(f"Missing test file for {py_file}", file=sys.stderr)
153
+ if not full:
154
+ return 1
155
+
156
+ success, message = check_banned_words_in_file(py_file)
157
+ if not success:
158
+ has_errors = True
159
+ print(message, file=sys.stderr)
160
+ if not full:
161
+ return 1
162
+
163
+ # Parallelizable jobs
164
+ jobs = []
165
+ jobs.append(
166
+ (run_command, ["python3", "-m", "flake8", "--max-line-length=120", directory])
167
+ )
168
+ for py_file in py_files:
169
+ jobs.append((compile_file, py_file))
170
+ for test_file in test_files:
171
+ jobs.append((run_test_on_file, test_file))
172
+
173
+ if single_thread:
174
+ for job, *args in jobs:
175
+ success, message = job(*args)
176
+ if not success:
177
+ has_errors = True
178
+ print(message, file=sys.stderr)
179
+ if not full:
180
+ return 1
181
+ else:
182
+ with Pool(processes=cpu_count()) as pool:
183
+ results = []
184
+ for job_func, *args in jobs:
185
+ result = pool.apply_async(job_func, args)
186
+ results.append(result)
187
+ for result in results:
188
+ success, message = result.get()
189
+ if not success:
190
+ has_errors = True
191
+ print(message, file=sys.stderr)
192
+ if not full:
193
+ return 1
194
+
195
+ return 1 if has_errors else 0
196
+
197
+
198
+ def cli():
199
+ parser = argparse.ArgumentParser(
200
+ description="A tool to check and validate a Python code repository."
201
+ )
202
+ parser.add_argument(
203
+ "--full", action="store_true", help="Run all checks regardless of failures."
204
+ )
205
+ parser.add_argument(
206
+ "--readonly",
207
+ action="store_true",
208
+ help="Only check for issues, don't modify files.",
209
+ )
210
+ parser.add_argument(
211
+ "--single-thread", action="store_true", help="Run checks sequentially."
212
+ )
213
+ parser.add_argument(
214
+ "directory",
215
+ nargs="?",
216
+ default=".",
217
+ help="The directory to check (default: current directory).",
218
+ )
219
+
220
+ args = parser.parse_args()
221
+
222
+ sys.exit(main(args.directory, not args.readonly, args.single_thread, args.full))
@@ -0,0 +1,99 @@
1
+ # dazpycheck: ignore-banned-words
2
+ import os
3
+ import shutil
4
+ import unittest
5
+ from dazpycheck.main import (
6
+ check_banned_words_in_file,
7
+ compile_file,
8
+ run_test_on_file,
9
+ main,
10
+ )
11
+
12
+
13
+ class TestDazpycheck(unittest.TestCase):
14
+
15
+ def setUp(self):
16
+ self.output_dir = "output"
17
+ self.test_project_dir = os.path.join(self.output_dir, "test_project")
18
+ os.makedirs(self.test_project_dir, exist_ok=True)
19
+
20
+ def tearDown(self):
21
+ shutil.rmtree(self.output_dir)
22
+
23
+ def test_check_banned_words_in_file(self):
24
+ file_path = os.path.join(self.test_project_dir, "bad_file.py")
25
+ with open(file_path, "w") as f:
26
+ f.write("print('This is a mock file.')\n")
27
+ success, message = check_banned_words_in_file(file_path)
28
+ print(f"Success: {success}, Message: {message}")
29
+ self.assertFalse(success)
30
+ self.assertIn("Banned word 'mock' found", message)
31
+
32
+ def test_check_banned_words_in_file_ignored(self):
33
+ file_path = os.path.join(self.test_project_dir, "bad_file_ignored.py")
34
+ with open(file_path, "w") as f:
35
+ f.write("# dazpycheck: ignore-banned-words\n")
36
+ f.write("print('This is a mock file.')\n")
37
+ success, message = check_banned_words_in_file(file_path)
38
+ self.assertTrue(success)
39
+
40
+ def test_compile_file(self):
41
+ file_path = os.path.join(self.test_project_dir, "good_file.py")
42
+ with open(file_path, "w") as f:
43
+ f.write("print('Hello, world!')\n")
44
+ success, message = compile_file(file_path)
45
+ self.assertTrue(success)
46
+
47
+ def test_compile_file_with_error(self):
48
+ file_path = os.path.join(self.test_project_dir, "bad_file.py")
49
+ with open(file_path, "w") as f:
50
+ f.write("print('Hello, world!\n")
51
+ success, message = compile_file(file_path)
52
+ self.assertFalse(success)
53
+
54
+ def test_run_test_on_file_with_low_coverage(self):
55
+ source_file = os.path.join(self.test_project_dir, "my_module.py")
56
+ test_file = os.path.join(self.test_project_dir, "my_module_test.py")
57
+ with open(source_file, "w") as f:
58
+ f.write(
59
+ "def my_function():\n return 1\n\n\ndef another_function():\n return 2\n"
60
+ )
61
+ with open(test_file, "w") as f:
62
+ f.write(
63
+ "import unittest\nfrom my_module import my_function\n\n\n"
64
+ "class MyTest(unittest.TestCase):\n def test_my_function(self):\n"
65
+ " self.assertEqual(my_function(), 1)\n"
66
+ )
67
+ success, message = run_test_on_file(test_file)
68
+ self.assertFalse(success)
69
+ self.assertIn("less than 50%", message)
70
+
71
+ def test_integration_main(self):
72
+ # This is an integration test that runs the main function
73
+ # with a project that has multiple issues.
74
+ source_file = os.path.join(self.test_project_dir, "my_module.py")
75
+ test_file = os.path.join(self.test_project_dir, "my_module_test.py")
76
+ with open(source_file, "w") as f:
77
+ f.write(
78
+ "def my_function():\n return 1\n\ndef another_function():\n return 2\n"
79
+ )
80
+ with open(test_file, "w") as f:
81
+ f.write(
82
+ "import unittest\nfrom my_module import my_function\n\n"
83
+ "class MyTest(unittest.TestCase):\n def test_my_function(self):\n"
84
+ " self.assertEqual(my_function(), 1)\n"
85
+ )
86
+ with open(os.path.join(self.test_project_dir, "bad_file.py"), "w") as f:
87
+ f.write("print('This is a mock file.')\n")
88
+
89
+ # Fail fast should stop after the first error
90
+ result = main(self.test_project_dir, False, True, False)
91
+ self.assertEqual(result, 1)
92
+
93
+ # Full run should report all errors
94
+ result = main(self.test_project_dir, False, True, True)
95
+ self.assertEqual(result, 1)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ unittest.main()
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: dazpycheck
3
+ Version: 0.4.0
4
+ Summary: A tool to check and validate a Python code repository.
5
+ Author-email: Darren Oakey <darren@oakey.net>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+
12
+ # dazpycheck: Your AI Code's Quality Guardian
13
+
14
+ ![dazpycheck banner](output/banner.png)
15
+
16
+ **dazpycheck** is a powerful and comprehensive tool for ensuring your Python code is clean, correct, and consistent. It's especially useful for checking code generated by AI, which can sometimes take shortcuts or introduce subtle errors.
17
+
18
+ ## Why dazpycheck?
19
+
20
+ AI is a powerful tool, but it's not perfect. AI-generated code can sometimes be messy, inefficient, or even contain bugs. `dazpycheck` helps you catch these issues before they become problems. It's the perfect last step in your development workflow to ensure your code is production-ready.
21
+
22
+ ![dazpycheck logo](output/logo.png)
23
+
24
+ ## Features
25
+
26
+ * **All-in-One Checking:** dazpycheck integrates `black` for formatting, `flake8` for linting, `pyright` for type checking, and `py_compile` for compilation, giving you a complete picture of your code's health.
27
+ * **Automated Fixing:** By default, `dazpycheck` will automatically fix formatting issues. Use the `--readonly` flag to see what would be changed without modifying any files.
28
+ * **Banned Words Enforcement:** Define a list of "banned words" to enforce your own coding standards. `dazpycheck` will scan your entire repository and flag any occurrences of these words.
29
+ * **Parallel Execution:** `dazpycheck` runs all readonly checks in parallel, making it fast and efficient, even on large codebases.
30
+ * **Integrated Test Runner:** By default, `dazpycheck` will run all your `unittest` and `pytest` tests. Use the `--no-test` flag to skip testing.
31
+
32
+ ![Parallel Checks](output/parallel_checks.png)
33
+
34
+ ## Installation
35
+
36
+ `dazpycheck` is available on PyPI. You can install it using `pip`:
37
+
38
+ ```bash
39
+ pip install dazpycheck
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Once installed, you can run `dazpycheck` from anywhere on your system:
45
+
46
+ ```bash
47
+ dazpycheck
48
+ ```
49
+
50
+ This will run all checks and tests on the current directory. You can also specify a directory:
51
+
52
+ ```bash
53
+ dazpycheck /path/to/your/project
54
+ ```
55
+
56
+ ### Options
57
+
58
+ * `--readonly`: Only check for issues, don't modify files.
59
+ * `--no-test`: Skip running tests.
60
+ * `--single-thread`: Run checks sequentially.
61
+ * `--full`: Run all checks regardless of failures.
62
+
63
+ ![Banned Words](output/banned_words.png)
64
+
65
+ We hope you find `dazpycheck` to be a valuable addition to your development workflow!
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/dazpycheck/__init__.py
5
+ src/dazpycheck/main.py
6
+ src/dazpycheck/main_test.py
7
+ src/dazpycheck.egg-info/PKG-INFO
8
+ src/dazpycheck.egg-info/SOURCES.txt
9
+ src/dazpycheck.egg-info/dependency_links.txt
10
+ src/dazpycheck.egg-info/entry_points.txt
11
+ src/dazpycheck.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dazpycheck = dazpycheck.main:cli
@@ -0,0 +1 @@
1
+ dazpycheck