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
47 changes: 47 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Source compatibility

on: [push, pull_request]

permissions:
contents: read

jobs:
test:
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Python
id: python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Record interpreter
shell: python
run: |
import platform
import struct
import sys
import tempfile
from pathlib import Path

print(sys.version)
print(platform.platform())
print('Architecture bits:', struct.calcsize('P') * 8)
print('Temporary directory:', tempfile.gettempdir())
print('Resolved temporary directory:', Path(tempfile.gettempdir()).resolve())
- name: Run source tests
run: python -m unittest discover -s tests -v
- name: Check source syntax
if: ${{ !cancelled() && steps.python.outcome == 'success' }}
run: python -m compileall -q MHTMLExtractor.py mhtmlextractor
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## Install

- Use Python 3.7 or newer.
- Source CI covers x64 CPython 3.7 through 3.14 on Windows and Linux. Keep the minimum at 3.7 and do not add an upper installation cap.
- Project compatibility testing does not imply upstream maintenance of end-of-life Python versions.
- From the repository root, install normally with `python -m pip install .`.
- For local development, install editable with `python -m pip install -e .`.
- After installation, confirm the command surface with `mhtml-extract --help`.
Expand Down Expand Up @@ -39,10 +41,14 @@ Use `MHTMLExtractor` or `mhtml-extract` when you need files written to disk.

## Test

- `.github/workflows/tests.yml` runs the source suite and syntax check on pushes and pull requests across Windows Server 2022 / Ubuntu 22.04 and CPython 3.7-3.14.
- Source tests use only the standard library and run without a package installation.
- Run tests with `python -m unittest discover -s tests`.
- CI adds `-v` to expose skips: the TOML metadata test skips below 3.11, and symlink coverage may skip when the platform cannot create symlinks.
- Run a syntax check with `python -m compileall -q MHTMLExtractor.py mhtmlextractor`.
- Do not use `python -m py_compile mhtmlextractor\*.py` in PowerShell; the wildcard can be passed literally.
- For installability changes, verify an editable install and run `mhtml-extract --help`.
- Source and editable-install checks do not replace separate clean wheel/sdist installation validation.

## Avoid Unsafe Assumptions

Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ The project uses only Python's standard library.

- Python 3.7 or newer

The source compatibility CI matrix covers x64 CPython 3.7 through 3.14 on
Windows and Linux. The package has no upper Python version cap; versions beyond
this matrix are not yet verified. Compatibility testing of older Python versions
does not imply upstream maintenance. See the
[official Python version status](https://devguide.python.org/versions/).

## Installation

From the repository root:
Expand Down Expand Up @@ -307,12 +313,24 @@ objects.

## Development

Run the test suite:
The [source compatibility workflow](.github/workflows/tests.yml) runs on pushes
and pull requests using Windows Server 2022 and Ubuntu 22.04, with x64 CPython
3.7 through 3.14. It runs the source tests and syntax check directly from the
checkout using only the standard library; no package installation is required.
Each matrix job reports its interpreter version and runs independently so one
failure does not cancel the other versions.

Run the same test suite locally:

```bash
python -m unittest discover -s tests
```

CI adds `-v` to show individual tests and skip reasons. The TOML metadata test
skips on Python 3.7 through 3.10 because `tomllib` is available from Python 3.11;
the extraction tests still run. A symlink test may skip when the platform cannot
create symlinks.

Run a package syntax check:

```bash
Expand All @@ -325,6 +343,9 @@ Check the installed command surface from a local editable install:
mhtml-extract --help
```

Source compatibility and editable-install checks do not verify installed wheel
or source-distribution behavior; those require separate clean installations.

## License

MHTMLExtractor is released under the MIT License. See `LICENSE`.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Internet :: WWW/HTTP",
"Topic :: System :: Archiving",
"Topic :: Utilities",
Expand Down
3 changes: 2 additions & 1 deletion tests/test_duplicate_parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class DuplicatePartTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="mhtml-duplicates-")
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
# Match the extractor's canonical paths when TEMP contains an alias.
self.root = Path(temporary.name).resolve()

def archive(self, parts, name="input"):
payload = [b'Content-Type: multipart/related; boundary="duplicates"\r\n\r\n']
Expand Down
3 changes: 2 additions & 1 deletion tests/test_file_safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ class FileSafetyTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix="mhtml-a61-")
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
# Match the extractor's canonical paths when TEMP contains an alias.
self.root = Path(self.temporary.name).resolve()

def archive_and_output(self, name="case", **options):
case = self.root / name
Expand Down
3 changes: 2 additions & 1 deletion tests/test_static_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class StaticReferenceTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="mhtml-static-refs-")
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
# Match the extractor's canonical paths when TEMP contains an alias.
self.root = Path(temporary.name).resolve()
self.case_number = 0

def archive(self, parts, outer_location=None):
Expand Down