diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..44119a9 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,19 @@ +# Changes here will be overwritten by Copier +_commit: v0.0.5 +_src_path: git@github.com:15r10nk/project-template.git +full_coverage: false +github_user: 15r10nk +insider_only: false +module_name: pysource_minimize +project_description: minimize python source code +project_main: true +project_name: pysource-minimize +python_versions: +- '3.8' +- '3.9' +- '3.10' +- '3.11' +- '3.12' +- '3.13' +show_project_logo: false +use_mkdocs: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a84981..fab7e11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,28 +10,28 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{matrix.python-version}} architecture: x64 - - run: pip install nox==2022.11.21 - - run: pip install poetry==1.2.2 - - run: nox --session mypy-${{matrix.python-version}} + allow-prereleases: true + - run: pip install hatch + - run: hatch run +py=${{matrix.python-version}} mypy:test test: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{matrix.python-version}} architecture: x64 - - run: pip install nox==2022.11.21 - - run: pip install poetry==1.2.2 - - run: nox --sessions test-${{matrix.python-version}} + allow-prereleases: true + - run: pip install hatch + - run: hatch test -py ${{matrix.python-version}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..25a5c6a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Contributing +Contributions are welcome. +Please create an issue before writing a pull request so we can discuss what needs to be changed. + +# Testing +The code can be tested with [hatch](https://hatch.pypa.io/latest/) + +* `hatch run cov:test` can be used to test all supported python versions and to check for coverage. +* `hatch run +py=3.10 all:test -- --sw` runs pytest for python 3.10 with the `--sw` argument. + + + +# Commits +Please use [pre-commit](https://pre-commit.com/) for your commits. diff --git a/README.md b/README.md index 65b11df..92a9909 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,40 @@ + + + +![ci](https://github.com/15r10nk/pysource-minimize/actions/workflows/ci.yml/badge.svg?branch=main) +[![Docs](https://img.shields.io/badge/docs-mkdocs-green)](https://15r10nk.github.io/pysource-minimize/) [![pypi version](https://img.shields.io/pypi/v/pysource-minimize.svg)](https://pypi.org/project/pysource-minimize/) ![Python Versions](https://img.shields.io/pypi/pyversions/pysource-minimize) ![PyPI - Downloads](https://img.shields.io/pypi/dw/pysource-minimize) [![GitHub Sponsors](https://img.shields.io/github/sponsors/15r10nk)](https://github.com/sponsors/15r10nk) -# pysource-minimize - -If you build a linter, formatter or any other tool which has to analyse python source code you might end up searching bugs in pretty large input files. + -`pysource_minimize` is able to remove everything from the python source which is not related to the problem. +## Installation -## CLI -You can use `pysource-minimize` from the command line like follow: +You can install "pysource-minimize" via [pip](https://pypi.org/project/pip/): -```bash -pysource-minimize --file bug.py --track "Assertion" -- python bug.py +``` bash +pip install pysource-minimize ``` -This will run `python bug.py` and try to find the string "Assertion" in the output. -The `--file bug.py` gets minimized as long as "Assertion" is part of the output of the command. -> [!WARNING] -> Be careful when you execute code which gets minimized. -> It might be that some combination of the code you minimize erases your hard drive -> or does other unintended things. +## Key Features -![example](example.gif) +- **list** of features ... +## Usage -## API -Example: -``` pycon ->>> from pysource_minimize import minimize +usage ... ->>> source = """ -... def f(): -... print("bug"+"other string") -... return 1+1 -... f() -... """ + +## Issues ->>> print(minimize(source, lambda new_source: "bug" in new_source)) -"""bug""" +If you encounter any problems, please [report an issue](https://github.com/15r10nk/pysource-minimize/issues) along with a detailed description. + -``` +## License + +Distributed under the terms of the [MIT](http://opensource.org/licenses/MIT) license, "pysource-minimize" is free and open source software. diff --git a/pyproject.toml b/pyproject.toml index c57c9e9..38e96e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,48 @@ -[tool.poetry] -name = "pysource-minimize" -version = "0.6.3" -description = "minimize python source code" -authors = ["Frank Hoffmann"] +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +authors = [ + {name = "Frank Hoffmann", email = "15r10nk@polarbit.de"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy" +] +dependencies = [ +"rich >=12.6.0", +"astunparse >=1.6.3", +"click >=8.1.7", +] +description = 'minimize python source code' +keywords = [] license = "MIT" +name = "pysource-minimize" readme = "README.md" -packages = [{include = "pysource_minimize"}] +requires-python = ">=3.8" +version="0.6.3" + + +[project.scripts] +pysource-minimize = "pysource_minimize.__main__:main" + + +[project.urls] +Documentation = "https://github.com/15r10nk/pysource-minimize#readme" +Issues = "https://github.com/15r10nk/pysource-minimize/issues" +Source = "https://github.com/15r10nk/pysource-minimize" + +[tool.black] +target-version = ["py38"] [tool.commitizen] changelog_incremental = true @@ -13,45 +50,34 @@ major_version_zero = true tag_format = "v$major.$minor.$patch$prerelease" update_changelog_on_bump = true version_files = [ - "pysource_minimize/__init__.py:version" + "src/pysource_minimize/__init__.py:version" ] -version_provider = "poetry" - -[tool.poetry.dependencies] -python = ">=3.7" -asttokens = ">=2.0.8" -rich = ">=12.6.0" -astunparse = ">=1.6.3" -click = ">=8.1.7" - -[tool.poetry.group.dev.dependencies] -pytest = ">=7.1.3" -pytest-subtests = ">=0.8.0" -pytest-xdist = {extras = ["psutil"], version = ">=3.1.0"} -coverage-enable-subprocess = ">=1.0" -coverage = ">=6.5.0" -mypy = ">=1.2.0" -pysource-codegen = ">=0.4.1" - -[tool.poetry.scripts] -pysource-minimize = "pysource_minimize.__main__:main" +version_provider = "pep621" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.hatch.envs.hatch-test] +extra-dependencies = [ +"pysource-codegen>=0.4.1", + "inline-snapshot", + "pytest-xdist[psutil] >=3.1.0", +] +[[tool.hatch.envs.hatch-test.matrix]] +python=['3.8',"3.9","3.10","3.11","3.12","3.13"] -[tool.coverage.run] -source = ["tests","pysource_minimize"] -parallel = true -branch = true -data_file = "$TOP/.coverage" -[tool.coverage.report] -exclude_lines = ["assert False", "raise NotImplemented"] +[[tool.hatch.envs.mypy.matrix]] +python=['3.8',"3.10","3.11","3.12","3.13"] -[tool.black] -force-exclude = "tests/.*_samples" -skip_magic_trailing_comma = true +[tool.hatch.envs.mypy] +extra-dependencies=["mypy"] +scripts.test = ["mypy src"] + +[tool.hatch.envs.docs] +dependencies = [ + "mkdocs>=1.4.2", + "mkdocs-material[imaging]>=8.5.10", + "mkdocstrings>=0.19.0" +] +scripts.serve = ["mkdocs serve {args}"] -[tool.mypy] -exclude="tests/.*_samples" +[tool.hatch.version] +path = "src/pysource_minimize/__init__.py" diff --git a/pysource_minimize/__init__.py b/src/pysource_minimize/__init__.py similarity index 100% rename from pysource_minimize/__init__.py rename to src/pysource_minimize/__init__.py diff --git a/pysource_minimize/__main__.py b/src/pysource_minimize/__main__.py similarity index 100% rename from pysource_minimize/__main__.py rename to src/pysource_minimize/__main__.py diff --git a/pysource_minimize/_minimize.py b/src/pysource_minimize/_minimize.py similarity index 93% rename from pysource_minimize/_minimize.py rename to src/pysource_minimize/_minimize.py index 7dc9b2a..41e7623 100644 --- a/pysource_minimize/_minimize.py +++ b/src/pysource_minimize/_minimize.py @@ -64,6 +64,7 @@ def minimize( *, progress_callback: Callable[[int, int], object] = lambda current, total: None, retries: int = 1, + compilable=True, ) -> str: """ minimzes the source code @@ -73,6 +74,7 @@ def minimize( checker: a function which gets the source and returns `True` when the criteria is fullfilled. progress_callback: function which is called everytime the source gets a bit smaller. retries: the number of retries which sould be performed when the ast could be minimized (useful for non deterministic issues) + compilable: make shure that the minimized code can also be compiled and not just parsed. returns the minimized source """ @@ -84,7 +86,8 @@ def source_checker(new_ast): with warnings.catch_warnings(): source = unparse(new_ast) warnings.simplefilter("ignore", SyntaxWarning) - compile(source, "", "exec") + if compilable: + compile(source, "", "exec") except: return False diff --git a/pysource_minimize/_minimize_base.py b/src/pysource_minimize/_minimize_base.py similarity index 100% rename from pysource_minimize/_minimize_base.py rename to src/pysource_minimize/_minimize_base.py diff --git a/pysource_minimize/_minimize_structure.py b/src/pysource_minimize/_minimize_structure.py similarity index 95% rename from pysource_minimize/_minimize_structure.py rename to src/pysource_minimize/_minimize_structure.py index 07acf1a..0961485 100644 --- a/pysource_minimize/_minimize_structure.py +++ b/src/pysource_minimize/_minimize_structure.py @@ -307,6 +307,24 @@ def minimize_args_of(self, func): return False def minimize_stmt(self, node): + + if sys.version_info >= (3, 12) and hasattr(node, "type_params"): + + for p in node.type_params: + if isinstance(p, ast.TypeVar): + if p.bound is not None and self.try_only(node, p.bound): + self.minimize(p.bound) + return + + if ( + sys.version_info >= (3, 13) + and p.default_value is not None + and self.try_only(node, p.default_value) + ): + self.minimize(p.default_value) + return + self.minimize_list(node.type_params, self.minimize_type_param) + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): if self.try_only_minimize(node, node.decorator_list): return @@ -342,17 +360,6 @@ def minimize_stmt(self, node): if self.minimize_args_of(node): return - if sys.version_info >= (3, 12): - for p in node.type_params: - if ( - isinstance(p, ast.TypeVar) - and p.bound is not None - and self.try_only(node, p.bound) - ): - self.minimize(p.bound) - return - self.minimize_list(node.type_params, self.minimize_type_param) - if node.returns: if not self.try_none(node.returns): self.minimize_expr(node.returns) @@ -364,17 +371,6 @@ def minimize_stmt(self, node): if self.try_only_minimize(node, node.body): return - if sys.version_info >= (3, 12): - for p in node.type_params: - if ( - isinstance(p, ast.TypeVar) - and p.bound is not None - and self.try_only(node, p.bound) - ): - self.minimize(p.bound) - return - self.minimize_list(node.type_params, self.minimize_type_param) - for e in [ *[kw.value for kw in node.keywords], *[ @@ -594,17 +590,8 @@ def minimize_except_handler(handler): if sys.version_info >= (3, 8): self.minimize_list(node.type_ignores, lambda e: None) elif sys.version_info >= (3, 12) and isinstance(node, ast.TypeAlias): - for p in node.type_params: - if ( - isinstance(p, ast.TypeVar) - and p.bound is not None - and self.try_only(node, p.bound) - ): - self.minimize(p.bound) - return if self.try_only_minimize(node, node.name, node.value): return - self.minimize_list(node.type_params, self.minimize_type_param) elif isinstance(node, ast.Pass): pass @@ -616,6 +603,10 @@ def minimize_type_param(self, node): if isinstance(node, ast.TypeVar): self.minimize_optional(node.bound) + if sys.version_info >= (3, 13): + if not self.try_none(node.default_value): + self.minimize(node.default_value) + def minimize_lists(self, lists, terminals=None, minimal=0): if terminals is None: terminals = [self.minimize for _ in lists] diff --git a/pysource_minimize/_minimize_value.py b/src/pysource_minimize/_minimize_value.py similarity index 100% rename from pysource_minimize/_minimize_value.py rename to src/pysource_minimize/_minimize_value.py diff --git a/pysource_minimize/_utils.py b/src/pysource_minimize/_utils.py similarity index 100% rename from pysource_minimize/_utils.py rename to src/pysource_minimize/_utils.py diff --git a/pysource_minimize/py.typed b/src/pysource_minimize/py.typed similarity index 100% rename from pysource_minimize/py.typed rename to src/pysource_minimize/py.typed diff --git a/tests/needle_samples/1fb072a0e339dd5ea40cb67ab63630fb74aaebc601b2239f5c4e834e9eaf158b.py b/tests/needle_samples/1fb072a0e339dd5ea40cb67ab63630fb74aaebc601b2239f5c4e834e9eaf158b.py new file mode 100644 index 0000000..9572a9c --- /dev/null +++ b/tests/needle_samples/1fb072a0e339dd5ea40cb67ab63630fb74aaebc601b2239f5c4e834e9eaf158b.py @@ -0,0 +1 @@ +type name_0[name_4 = needle_17597] = name_1 \ No newline at end of file diff --git a/tests/test_needle.py b/tests/test_needle.py index 2eb8ea6..7d3e6e8 100644 --- a/tests/test_needle.py +++ b/tests/test_needle.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import Any +import pysource_minimize._minimize import pytest from pysource_codegen import generate -import pysource_minimize._minimize from .utils import testing_enabled try: diff --git a/tests/test_remove_one.py b/tests/test_remove_one.py index 487be1a..ea94d1c 100644 --- a/tests/test_remove_one.py +++ b/tests/test_remove_one.py @@ -4,13 +4,13 @@ import sys from pathlib import Path +import pysource_minimize._minimize import pytest from pysource_codegen import generate +from pysource_minimize import minimize -import pysource_minimize._minimize from . import session_config from .dump_tree import dump_tree -from pysource_minimize import minimize from tests.utils import testing_enabled try: