Remove all comments and docstrings from Python code using a safe AST-based transform.
Project description
shushpy
Remove all comments and docstrings from Python code using a safe AST-based transform.
- Inline comments (e.g.,
# comment) are dropped. - Module/class/function docstrings are removed.
- The code is re-generated via
ast.unparse, so formatting may change, but semantics are preserved.
Python 3.12+ required. Zero third-party dependencies.
Why this exists
- You need to distribute or compare Python sources without any comments or docstrings.
- You want a robust approach that won’t break on edge-case syntax or string tricks—AST transformations operate on parsed code, not raw text, so it’s resilient.
Installation and usage with uv
Prerequisites:
- Python 3.12+
- uv installed
Clone and set up the project environment:
# clone the repository
git clone https://github.com/vodkar/shushpy.git
cd shushpy
# create a project environment and install dependencies + the project
uv sync
You can run the CLI via uv without manually activating the virtual environment:
# show CLI help
uv run shushpy --help
# strip a single file to stdout
uv run shushpy path/to/file.py
# strip a directory recursively in-place
uv run shushpy src/ --inplace
# read from stdin, write to stdout
cat script.py | uv run shushpy
Alternatively, if you prefer explicit installation into the environment:
uv pip install -e .
Then you can use the console script directly:
shushpy --help
CLI
usage: shushpy [-h] [--inplace] [--encoding ENCODING] [--recursive] [--no-recursive]
[--log-level {CRITICAL,ERROR,WARNING,INFO,DEBUG}]
[paths ...]
Remove all comments and docstrings from Python code.
positional arguments:
paths Files or directories to process. If omitted, read from stdin and write to stdout.
options:
-h, --help show this help message and exit
--inplace Rewrite files in place. Required for multiple-file outputs.
--encoding ENCODING Text encoding for reading/writing files (default: utf-8).
--recursive Recurse into subdirectories (default: enabled).
--no-recursive Disable recursion when processing directories.
--log-level Set the logging level (default: WARNING).
Notes:
- When not using
--inplace, the CLI writes to stdout. If your input expands to multiple files, the CLI refuses to stream multiple outputs to stdout—use--inplacein that case. - When no paths are provided, the CLI reads from stdin and writes to stdout.
Library usage
The library exposes a focused API in the shushpy package.
strip_comments(source: str) -> strstrip_file(path: str | Path, *, inplace: bool = False, encoding: str = "utf-8") -> strstrip_path(path: str | Path, *, inplace: bool = False, encoding: str = "utf-8", recursive: bool = True) -> dict[str, str]strip_paths(paths: list[str | Path], *, inplace: bool = False, encoding: str = "utf-8", recursive: bool = True) -> dict[str, str]
Minimal examples:
from __future__ import annotations
from pathlib import Path
from typing import Final
from shushpy import strip_comments, strip_file, strip_path, strip_paths
# Example 1: Process a source string
SOURCE: Final[str] = """
\"\"\"Module docstring\"\"\"
def add(a: int, b: int) -> int:
\"\"\"Add two numbers\"\"\"
return a + b # inline comment
"""
cleaned: str = strip_comments(SOURCE)
print(cleaned)
# Example 2: Process a single file to a string (no in-place modification)
result_str: str = strip_file("path/to/file.py", inplace=False)
# Example 3: Process a directory recursively, in place
modified: dict[str, str] = strip_path("src", inplace=True, recursive=True)
# Example 4: Process multiple mixed paths, no in-place (returns mapping path->content)
collected: dict[str, str] = strip_paths([Path("a.py"), Path("pkg/")], inplace=False)
Behavior and guarantees
- AST-based: The tool parses Python code into an AST, removes docstrings via AST transformation, and regenerates the code via
ast.unparse. Inline/block comments aren’t part of the AST, so they never appear in the output. - Formatting: The output is re-generated source. Expect normalized formatting and possibly different quoting styles or minor layout changes.
- Errors: Invalid Python raises
SyntaxError. I/O and encoding issues are surfaced with clear errors. - Scope: Docstrings are removed at module, class, and (async) function levels.
Development
Common workflows with uv:
# create or update the environment and install the project
uv sync
# run the CLI
uv run shushpy --help
# run the module directly
uv run python -m shushpy.cli --help
# build distributions (wheel and sdist)
uv build
Project metadata:
- Build backend: Hatchling
- Console script:
shushpy(entry point atshushpy.cli:main)
Release & Publishing
Checklist before release:
- Ensure you have a PyPI account and access to the project name "shushpy" (or choose a unique name).
- Create a PyPI API token and add it as a GitHub Secret named PYPI_API_TOKEN in the repository settings.
- Bump the version in pyproject.toml under [project] version to the new semantic version (e.g., "0.1.1").
- Update README and (optionally) a CHANGELOG with notable changes.
- Run tests locally:
- uv sync
- uv run pytest -q
- Build locally to verify artifacts:
- uv build
- Commit and tag the release:
- git add .
- git commit -m "Release vX.Y.Z"
- git tag vX.Y.Z
- git push && git push --tags
Publishing options:
- GitHub Actions (recommended):
- On pushing a tag like vX.Y.Z (or publishing a GitHub Release), the workflow at .github/workflows/publish.yml will:
- Install deps with uv, run tests, build distributions, and publish to PyPI using PYPI_API_TOKEN.
- On pushing a tag like vX.Y.Z (or publishing a GitHub Release), the workflow at .github/workflows/publish.yml will:
- Manual (optional):
- Build: uv build
- Upload with Twine (ephemeral via uvx):
- uvx twine upload dist/*
- To test on TestPyPI (optional):
- uv build
- uvx twine upload --repository-url https://test.pypi.org/legacy/ dist/*
- Install to verify: uvx pip install -i https://test.pypi.org/simple shushpy==X.Y.Z
Post-release verification:
- Install from PyPI and check CLI:
- uvx pip install shushpy
- uvx shushpy --help
Limitations
- This tool removes docstrings and comments only; it does not attempt to remove dead code or perform obfuscation.
- Because output is regenerated, stable formatting is not guaranteed across Python minor versions if
ast.unparsebehavior changes. - Python 3.12+ is required (uses modern
astbehavior and typing features).
License
See the repository’s LICENSE file.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shushpy-0.0.1.tar.gz.
File metadata
- Download URL: shushpy-0.0.1.tar.gz
- Upload date:
- Size: 14.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61dd0e70009cd01791a6c67ca453ffe0922004d01e62239073244ed533bd16a0
|
|
| MD5 |
0dc77c31889c727616f71f59b3cab271
|
|
| BLAKE2b-256 |
18dd27f4b5a58549086d7ade3593e3b67c716e9677ffc4fb9aaac9847441996e
|
File details
Details for the file shushpy-0.0.1-py3-none-any.whl.
File metadata
- Download URL: shushpy-0.0.1-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79f251a77007750ac915d68aa59b1fef58c23d71a16332736d2c3472ba2a4f1a
|
|
| MD5 |
9079e1562d7a3f27fee3b027bdd74a3e
|
|
| BLAKE2b-256 |
c0e5b4adf61a957c276f340ae224d0a3d8f42a725d3fbe03a80dcaf15b1a4cee
|