Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 28 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,29 @@
# pymini

`pymini` minifies Python source code by simplifying syntax, shortening identifiers, and stripping unnecessary whitespace. Its primary multi-file workflow preserves package structure; one-file bundling is available as an explicit opt-in.
`pymini` is an AST-based Python minifier for scripts and packages. It preserves
package layout by default, can emit a single-file bundle when asked, and can
shrink Python code by roughly `30%` to `90%` depending on the codebase and
whether you compare raw source or compressed archives.

## Status
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Benchmarks](./benchmarks/README.md)

This project is maintained as an AST-based minifier for Python 3.9+ code. It is best suited to scripts and small-to-medium package graphs. Package mode preserves package layout and now covers relative imports, dotted imports, star imports, package re-exports, and `importlib`-based internal imports; bundle mode emits a self-contained loader-backed single file for the same kinds of graphs.
# Getting Started

## Installation

```bash
python3 -m pip install pymini
```

## CLI

Package mode is the default and preserves the package tree:
Package mode preserves the package tree:

```bash
pymini package src -o out
```

Legacy invocation without an explicit mode still defaults to `package`:

```bash
pymini src -o out
```

By default, `pymini` preserves module paths and public globals. When possible, it keeps the public surface stable by emitting aliases while still shortening internal names. To trade API stability for more aggressive compression:

```bash
pymini package src --rename-global-variables -o out
```

Bundle mode emits a single file and is better suited to app-style graphs than libraries:
Bundle mode emits one file:

```bash
pymini bundle src -o out/bundle.py
```

The legacy `--single-file` flag is still accepted as a compatibility alias for bundle mode.

## Python API
You can also use the Python API directly:

```python
from pymini import minify
Expand All @@ -54,56 +37,32 @@ sources, modules = minify(
)
```

## Development

Install development dependencies and run the test suite:

```bash
python3 -m pip install -e ".[dev]"
python3 -m pytest
```

## Compression Examples
# Compression

Checked-in minified outputs for the repo fixtures live in [examples](./examples) and
are regenerated by `scripts/regenerate_examples.py`.
Current checked-in fixtures:

| Input | Original | `pymini` | `pyminifier` | `python-minifier` (`pyminify`) |
| --- | ---: | ---: | ---: | ---: |
| `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes, `62.3%` | `676` bytes, `50.1%` | `1,020` bytes, `24.7%` |
| `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes, `50.7%` | `1,605` bytes, `19.3%` | `983` bytes, `50.6%` |
| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes, `66.3%` | `—` | `—` |
| `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes, `83.2%` | `—` | `—` |
| Input | Original | Minified | Reduction |
| --- | ---: | ---: | ---: |
| `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes | `62.3%` |
| `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes | `50.7%` |
| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes | `66.3%` |
| `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes | `83.2%` |

## TexSoup Validation
For baseline comparisons, speed results, and TexSoup validation details, see
[benchmarks/README.md](./benchmarks/README.md).

`pymini` has been validated against the upstream `TexSoup` test suite in package mode.
Current validation: upstream pytest passes (`78` tests), raw source code `66.3%`
smaller, compressed source code (`.tar.gz`) `83.2%` smaller.
<!-- Raw bytes: 98,181 -> 33,107. Compressed bytes: 70,532 -> 11,850. -->
# Installation

To reproduce that flow locally:
## Pip

```bash
git clone https://github.com/alvinwan/TexSoup /tmp/texsoup
mkdir -p /tmp/texsoup-out/TexSoup
pymini package /tmp/texsoup/TexSoup -o /tmp/texsoup-out/TexSoup
cp -R /tmp/texsoup/tests /tmp/texsoup-tests
PYTHONPATH=/tmp/texsoup-out:/tmp/texsoup-tests python3 -m pytest /tmp/texsoup-tests/tests -o addopts=''
```

To compare raw package bytes before and after minification:

```bash
rg --files /tmp/texsoup/TexSoup -g '*.py' | xargs cat | wc -c
rg --files /tmp/texsoup-out/TexSoup -g '*.py' | xargs cat | wc -c
python3 -m pip install pymini
```

To compare compressed package snapshots:
## From source

```bash
tar -czf /tmp/texsoup-original-package.tar.gz -C /tmp/texsoup TexSoup
tar -czf /tmp/texsoup-minified-package.tar.gz -C /tmp/texsoup-out TexSoup
stat -f%z /tmp/texsoup-original-package.tar.gz
stat -f%z /tmp/texsoup-minified-package.tar.gz
git clone https://github.com/alvinwan/pymini.git
cd pymini
python3 -m pip install -e ".[dev]"
```
79 changes: 79 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Benchmarks

This directory holds the current size and speed measurements for `pymini`, plus
the benchmark harness used to reproduce them.

- [Compression](#compression)
- [Speed](#speed)
- [TexSoup Validation](#texsoup-validation)

# Compression

Checked-in fixture comparison:

| Input | Original | `pymini` | `pyminifier` | `python-minifier` |
| --- | ---: | ---: | ---: | ---: |
| `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes, `62.3%` | `676` bytes, `50.1%` | `1,020` bytes, `24.7%` |
| `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes, `50.7%` | `1,605` bytes, `19.3%` | `983` bytes, `50.6%` |

TexSoup package validation:

| Input | Original | `pymini` | Reduction |
| --- | ---: | ---: | ---: |
| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes | `66.3%` |
| `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes | `83.2%` |

# Speed

Latency is machine-dependent. Recompute these with
`PYTHONPATH=. .venv/bin/python benchmarks/benchmark_speed.py`.

| Input | `pymini` | `pyminifier` | `python-minifier` |
| --- | ---: | ---: | ---: |
| `tests/examples/pyminifier.py` | `14.1 ms` | `0.4 ms` | `1.5 ms` |
| `tests/examples/pyminify.py` | `1227.6 ms` | `1.1 ms` | `4.0 ms` |
| `TexSoup/` package API | `4928.8 ms` | `—` | `—` |
| `TexSoup/` package CLI | `5062.0 ms` | `—` | `—` |

To reproduce those numbers:

```bash
python3 -m pip install -e ".[dev]" python-minifier
git clone https://github.com/liftoff/pyminifier /tmp/pyminifier
PYTHONPATH=. .venv/bin/python benchmarks/benchmark_speed.py --pyminifier-root /tmp/pyminifier
```

# TexSoup Validation

`pymini` has been validated against the upstream `TexSoup` test suite in
package mode. Current validation: upstream pytest passes (`78` tests), raw
source code is `66.3%` smaller, and compressed source code (`.tar.gz`) is
`83.2%` smaller.

<!-- Raw bytes: 98,181 -> 33,107. Compressed bytes: 70,532 -> 11,850. -->

To reproduce that flow locally:

```bash
git clone https://github.com/alvinwan/TexSoup /tmp/texsoup
mkdir -p /tmp/texsoup-out/TexSoup
pymini package /tmp/texsoup/TexSoup -o /tmp/texsoup-out/TexSoup
cp -R /tmp/texsoup/tests /tmp/texsoup-tests
PYTHONPATH=/tmp/texsoup-out:/tmp/texsoup-tests python3 -m pytest /tmp/texsoup-tests/tests -o addopts=''
```

To compare raw package bytes before and after minification:

```bash
rg --files /tmp/texsoup/TexSoup -g '*.py' | xargs cat | wc -c
rg --files /tmp/texsoup-out/TexSoup -g '*.py' | xargs cat | wc -c
```

To compare compressed package snapshots:

```bash
tar -czf /tmp/texsoup-original-package.tar.gz -C /tmp/texsoup TexSoup
tar -czf /tmp/texsoup-minified-package.tar.gz -C /tmp/texsoup-out TexSoup
stat -f%z /tmp/texsoup-original-package.tar.gz
stat -f%z /tmp/texsoup-minified-package.tar.gz
```
Loading
Loading