safexml 1.0.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.
- safexml-1.0.0/.githooks/pre-commit +72 -0
- safexml-1.0.0/.githooks/pre-push +41 -0
- safexml-1.0.0/.github/workflows/ci.yml +88 -0
- safexml-1.0.0/.github/workflows/release.yml +161 -0
- safexml-1.0.0/.gitignore +28 -0
- safexml-1.0.0/.pre-commit-config.yaml +38 -0
- safexml-1.0.0/.releaserc +43 -0
- safexml-1.0.0/AGENTS.md +97 -0
- safexml-1.0.0/CHANGELOG.md +22 -0
- safexml-1.0.0/Cargo.lock +199 -0
- safexml-1.0.0/Cargo.toml +25 -0
- safexml-1.0.0/LICENSE +22 -0
- safexml-1.0.0/PKG-INFO +151 -0
- safexml-1.0.0/README.md +119 -0
- safexml-1.0.0/SECURITY.md +25 -0
- safexml-1.0.0/pyproject.toml +75 -0
- safexml-1.0.0/python/safexml/ElementTree.py +227 -0
- safexml-1.0.0/python/safexml/__init__.py +86 -0
- safexml-1.0.0/python/safexml/common.py +114 -0
- safexml-1.0.0/python/safexml/py.typed +2 -0
- safexml-1.0.0/scripts/sync_version.py +111 -0
- safexml-1.0.0/src/doctype.rs +237 -0
- safexml-1.0.0/src/lib.rs +159 -0
- safexml-1.0.0/src/parser.rs +520 -0
- safexml-1.0.0/tests/conftest.py +136 -0
- safexml-1.0.0/tests/test_benchmarks.py +37 -0
- safexml-1.0.0/tests/test_defusedxml_fixes.py +125 -0
- safexml-1.0.0/tests/test_elementtree_parity.py +110 -0
- safexml-1.0.0/tests/test_security_vectors.py +120 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Export standard toolchain paths if not in PATH
|
|
5
|
+
if [ -n "$USERPROFILE" ]; then
|
|
6
|
+
export PATH="$USERPROFILE/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin:$USERPROFILE/.cargo/bin:$PATH"
|
|
7
|
+
fi
|
|
8
|
+
if [ -n "$HOME" ]; then
|
|
9
|
+
export PATH="$HOME/.cargo/bin:$PATH"
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
echo -e "${BLUE}[git-hook: pre-commit]${NC} Running pre-commit validation checks..."
|
|
19
|
+
|
|
20
|
+
# 1. Multi-Manifest Version Parity Check
|
|
21
|
+
echo -e "${BLUE}[1/3] Checking version parity across manifests...${NC}"
|
|
22
|
+
if command -v uv >/dev/null 2>&1; then
|
|
23
|
+
uv run python scripts/sync_version.py --check || {
|
|
24
|
+
echo -e "${RED}[FAIL] Version parity check failed! Run 'python scripts/sync_version.py --set <version>' to fix.${NC}"
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
echo -e "${GREEN}[OK] Version parity confirmed.${NC}"
|
|
28
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
29
|
+
python3 scripts/sync_version.py --check || exit 1
|
|
30
|
+
echo -e "${GREEN}[OK] Version parity confirmed.${NC}"
|
|
31
|
+
elif command -v python >/dev/null 2>&1; then
|
|
32
|
+
python scripts/sync_version.py --check || exit 1
|
|
33
|
+
echo -e "${GREEN}[OK] Version parity confirmed.${NC}"
|
|
34
|
+
else
|
|
35
|
+
echo -e "${YELLOW}[SKIP] Python/uv not found; skipping version check.${NC}"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# 2. Rust Code Formatting Check
|
|
39
|
+
echo -e "${BLUE}[2/3] Checking Rust code formatting (cargo fmt)...${NC}"
|
|
40
|
+
if command -v cargo >/dev/null 2>&1; then
|
|
41
|
+
cargo fmt --all -- --check || {
|
|
42
|
+
echo -e "${RED}[FAIL] Rust code formatting check failed! Run 'cargo fmt --all' to fix.${NC}"
|
|
43
|
+
exit 1
|
|
44
|
+
}
|
|
45
|
+
echo -e "${GREEN}[OK] Rust formatting clean.${NC}"
|
|
46
|
+
else
|
|
47
|
+
echo -e "${YELLOW}[SKIP] Cargo not found in PATH; skipping cargo fmt.${NC}"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 3. Python Linting & Formatting (Ruff)
|
|
51
|
+
echo -e "${BLUE}[3/3] Checking Python code quality (ruff)...${NC}"
|
|
52
|
+
if command -v uvx >/dev/null 2>&1; then
|
|
53
|
+
uvx ruff check python tests || {
|
|
54
|
+
echo -e "${RED}[FAIL] Ruff lint failed! Run 'uvx ruff check --fix' to auto-fix.${NC}"
|
|
55
|
+
exit 1
|
|
56
|
+
}
|
|
57
|
+
uvx ruff format --check python tests || {
|
|
58
|
+
echo -e "${RED}[FAIL] Ruff format failed! Run 'uvx ruff format' to auto-format.${NC}"
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
echo -e "${GREEN}[OK] Python code quality clean.${NC}"
|
|
62
|
+
elif command -v ruff >/dev/null 2>&1; then
|
|
63
|
+
ruff check python tests || exit 1
|
|
64
|
+
ruff format --check python tests || exit 1
|
|
65
|
+
echo -e "${GREEN}[OK] Python code quality clean.${NC}"
|
|
66
|
+
else
|
|
67
|
+
echo -e "${YELLOW}[SKIP] Ruff not found; skipping python linting.${NC}"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo -e "${GREEN}[git-hook: pre-commit] All pre-commit checks passed successfully!${NC}"
|
|
71
|
+
exit 0
|
|
72
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
if [ -n "$USERPROFILE" ]; then
|
|
5
|
+
export PATH="$USERPROFILE/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin:$USERPROFILE/.cargo/bin:$PATH"
|
|
6
|
+
fi
|
|
7
|
+
if [ -n "$HOME" ]; then
|
|
8
|
+
export PATH="$HOME/.cargo/bin:$PATH"
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
BLUE='\033[0;34m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
echo -e "${BLUE}[git-hook: pre-push]${NC} Running pre-push verification tests..."
|
|
17
|
+
|
|
18
|
+
# 1. Rust Cargo Check & Tests
|
|
19
|
+
echo -e "${BLUE}[1/2] Running Rust tests (cargo test)...${NC}"
|
|
20
|
+
cargo test || {
|
|
21
|
+
echo -e "${RED}[FAIL] cargo test failed!${NC}"
|
|
22
|
+
exit 1
|
|
23
|
+
}
|
|
24
|
+
echo -e "${GREEN}[OK] Cargo tests passed.${NC}"
|
|
25
|
+
|
|
26
|
+
# 2. Python Tests
|
|
27
|
+
echo -e "${BLUE}[2/2] Running Python tests (pytest)...${NC}"
|
|
28
|
+
if [ -f ".venv/bin/pytest" ]; then
|
|
29
|
+
.venv/bin/pytest || {
|
|
30
|
+
echo -e "${RED}[FAIL] pytest failed!${NC}"
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
echo -e "${GREEN}[OK] Pytest passed.${NC}"
|
|
34
|
+
elif command -v pytest >/dev/null 2>&1; then
|
|
35
|
+
pytest || exit 1
|
|
36
|
+
echo -e "${GREEN}[OK] Pytest passed.${NC}"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo -e "${GREEN}[git-hook: pre-push] All tests passed! Ready to push.${NC}"
|
|
40
|
+
exit 0
|
|
41
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
rust-check:
|
|
19
|
+
name: Rust Checks (fmt, clippy, test)
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Setup Rust toolchain
|
|
25
|
+
uses: dtolnay/rust-toolchain@stable
|
|
26
|
+
with:
|
|
27
|
+
components: rustfmt, clippy
|
|
28
|
+
|
|
29
|
+
- name: Rust Cache
|
|
30
|
+
uses: Swatinem/rust-cache@v2
|
|
31
|
+
|
|
32
|
+
- name: Check formatting
|
|
33
|
+
run: cargo fmt --all -- --check
|
|
34
|
+
|
|
35
|
+
- name: Run clippy
|
|
36
|
+
run: cargo clippy --all-targets -- -D warnings
|
|
37
|
+
|
|
38
|
+
- name: Run Rust tests
|
|
39
|
+
run: cargo test --all-targets
|
|
40
|
+
|
|
41
|
+
python-test:
|
|
42
|
+
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
43
|
+
needs: rust-check
|
|
44
|
+
runs-on: ${{ matrix.os }}
|
|
45
|
+
strategy:
|
|
46
|
+
fail-fast: false
|
|
47
|
+
matrix:
|
|
48
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
49
|
+
python-version: ["3.12", "3.13"]
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
|
|
53
|
+
- name: Setup Python ${{ matrix.python-version }}
|
|
54
|
+
uses: actions/setup-python@v5
|
|
55
|
+
with:
|
|
56
|
+
python-version: ${{ matrix.python-version }}
|
|
57
|
+
|
|
58
|
+
- name: Setup Rust toolchain
|
|
59
|
+
uses: dtolnay/rust-toolchain@stable
|
|
60
|
+
|
|
61
|
+
- name: Rust Cache
|
|
62
|
+
uses: Swatinem/rust-cache@v2
|
|
63
|
+
|
|
64
|
+
- name: Setup uv
|
|
65
|
+
uses: astral-sh/setup-uv@v5
|
|
66
|
+
|
|
67
|
+
- name: Install dependencies and build safexml
|
|
68
|
+
run: uv pip install --system -e ".[dev]"
|
|
69
|
+
|
|
70
|
+
- name: Lint Python with Ruff
|
|
71
|
+
run: |
|
|
72
|
+
ruff check python tests
|
|
73
|
+
ruff format --check python tests
|
|
74
|
+
|
|
75
|
+
- name: Run pytest
|
|
76
|
+
run: pytest -v
|
|
77
|
+
|
|
78
|
+
version-parity:
|
|
79
|
+
name: Version Parity Check
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@v4
|
|
83
|
+
- uses: actions/setup-python@v5
|
|
84
|
+
with:
|
|
85
|
+
python-version: "3.12"
|
|
86
|
+
- name: Verify Cargo.toml and pyproject.toml versions match
|
|
87
|
+
run: python scripts/sync_version.py --check
|
|
88
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
name: Release Python Wheels (PyPI)
|
|
2
|
+
|
|
3
|
+
concurrency:
|
|
4
|
+
group: release-pypi-${{ github.ref_name || github.ref }}
|
|
5
|
+
cancel-in-progress: false
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- 'v*'
|
|
11
|
+
release:
|
|
12
|
+
types: [published]
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
linux:
|
|
21
|
+
name: Build Linux Wheels (${{ matrix.target }})
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
target: [x86_64, aarch64]
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- name: Sync Tag Version Across Manifests
|
|
32
|
+
shell: bash
|
|
33
|
+
run: |
|
|
34
|
+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
35
|
+
TAG_VER="${GITHUB_REF_NAME#v}"
|
|
36
|
+
echo "Synchronizing version from tag to: $TAG_VER"
|
|
37
|
+
python scripts/sync_version.py --set "$TAG_VER"
|
|
38
|
+
fi
|
|
39
|
+
- name: Build Wheels
|
|
40
|
+
uses: PyO3/maturin-action@v1
|
|
41
|
+
with:
|
|
42
|
+
target: ${{ matrix.target }}
|
|
43
|
+
args: --release --out dist
|
|
44
|
+
sccache: 'true'
|
|
45
|
+
manylinux: auto
|
|
46
|
+
- name: Upload Wheels
|
|
47
|
+
uses: actions/upload-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: wheels-linux-${{ matrix.target }}
|
|
50
|
+
path: dist
|
|
51
|
+
|
|
52
|
+
windows:
|
|
53
|
+
name: Build Windows Wheels (${{ matrix.target }})
|
|
54
|
+
runs-on: windows-latest
|
|
55
|
+
strategy:
|
|
56
|
+
matrix:
|
|
57
|
+
target: [x64]
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- uses: actions/setup-python@v5
|
|
61
|
+
with:
|
|
62
|
+
python-version: "3.12"
|
|
63
|
+
- name: Sync Tag Version Across Manifests
|
|
64
|
+
shell: bash
|
|
65
|
+
run: |
|
|
66
|
+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
67
|
+
TAG_VER="${GITHUB_REF_NAME#v}"
|
|
68
|
+
echo "Synchronizing version from tag to: $TAG_VER"
|
|
69
|
+
python scripts/sync_version.py --set "$TAG_VER"
|
|
70
|
+
fi
|
|
71
|
+
- name: Build Wheels
|
|
72
|
+
uses: PyO3/maturin-action@v1
|
|
73
|
+
with:
|
|
74
|
+
target: ${{ matrix.target }}
|
|
75
|
+
args: --release --out dist
|
|
76
|
+
sccache: 'true'
|
|
77
|
+
- name: Upload Wheels
|
|
78
|
+
uses: actions/upload-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: wheels-windows-${{ matrix.target }}
|
|
81
|
+
path: dist
|
|
82
|
+
|
|
83
|
+
macos:
|
|
84
|
+
name: Build macOS Wheels (${{ matrix.target }})
|
|
85
|
+
runs-on: macos-14
|
|
86
|
+
strategy:
|
|
87
|
+
matrix:
|
|
88
|
+
target: [x86_64-apple-darwin, aarch64-apple-darwin]
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
- uses: actions/setup-python@v5
|
|
92
|
+
with:
|
|
93
|
+
python-version: "3.12"
|
|
94
|
+
- name: Sync Tag Version Across Manifests
|
|
95
|
+
shell: bash
|
|
96
|
+
run: |
|
|
97
|
+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
98
|
+
TAG_VER="${GITHUB_REF_NAME#v}"
|
|
99
|
+
echo "Synchronizing version from tag to: $TAG_VER"
|
|
100
|
+
python scripts/sync_version.py --set "$TAG_VER"
|
|
101
|
+
fi
|
|
102
|
+
- name: Build Wheels
|
|
103
|
+
uses: PyO3/maturin-action@v1
|
|
104
|
+
with:
|
|
105
|
+
target: ${{ matrix.target }}
|
|
106
|
+
args: --release --out dist
|
|
107
|
+
sccache: 'true'
|
|
108
|
+
- name: Upload Wheels
|
|
109
|
+
uses: actions/upload-artifact@v4
|
|
110
|
+
with:
|
|
111
|
+
name: wheels-macos-${{ matrix.target }}
|
|
112
|
+
path: dist
|
|
113
|
+
|
|
114
|
+
sdist:
|
|
115
|
+
name: Build Source Distribution
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
steps:
|
|
118
|
+
- uses: actions/checkout@v4
|
|
119
|
+
- uses: actions/setup-python@v5
|
|
120
|
+
with:
|
|
121
|
+
python-version: "3.12"
|
|
122
|
+
- name: Sync Tag Version Across Manifests
|
|
123
|
+
shell: bash
|
|
124
|
+
run: |
|
|
125
|
+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
126
|
+
TAG_VER="${GITHUB_REF_NAME#v}"
|
|
127
|
+
echo "Synchronizing version from tag to: $TAG_VER"
|
|
128
|
+
python scripts/sync_version.py --set "$TAG_VER"
|
|
129
|
+
fi
|
|
130
|
+
- name: Build Source Distribution
|
|
131
|
+
uses: PyO3/maturin-action@v1
|
|
132
|
+
with:
|
|
133
|
+
command: sdist
|
|
134
|
+
args: --out dist
|
|
135
|
+
- name: Upload sdist
|
|
136
|
+
uses: actions/upload-artifact@v4
|
|
137
|
+
with:
|
|
138
|
+
name: wheels-sdist
|
|
139
|
+
path: dist
|
|
140
|
+
|
|
141
|
+
publish:
|
|
142
|
+
name: Publish to PyPI
|
|
143
|
+
needs: [linux, windows, macos, sdist]
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
environment:
|
|
146
|
+
name: pypi
|
|
147
|
+
url: https://pypi.org/p/safexml
|
|
148
|
+
steps:
|
|
149
|
+
- name: Download all artifacts
|
|
150
|
+
uses: actions/download-artifact@v4
|
|
151
|
+
with:
|
|
152
|
+
path: dist
|
|
153
|
+
pattern: wheels-*
|
|
154
|
+
merge-multiple: true
|
|
155
|
+
|
|
156
|
+
- name: Publish package distributions to PyPI
|
|
157
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
158
|
+
with:
|
|
159
|
+
packages-dir: dist/
|
|
160
|
+
skip-existing: true
|
|
161
|
+
|
safexml-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Rust
|
|
2
|
+
target/
|
|
3
|
+
**/*.rs.bk
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
*.so
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
uv.lock
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.eggs/
|
|
17
|
+
|
|
18
|
+
# IDE & Editor
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# Testing & Coverage
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.6.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
args: ['--unsafe']
|
|
9
|
+
- id: check-toml
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
args: ['--maxkb=500']
|
|
12
|
+
- id: check-merge-conflict
|
|
13
|
+
|
|
14
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
15
|
+
rev: v0.6.9
|
|
16
|
+
hooks:
|
|
17
|
+
- id: ruff
|
|
18
|
+
args: [--fix]
|
|
19
|
+
files: ^(python|tests)/
|
|
20
|
+
- id: ruff-format
|
|
21
|
+
files: ^(python|tests)/
|
|
22
|
+
|
|
23
|
+
- repo: local
|
|
24
|
+
hooks:
|
|
25
|
+
- id: version-parity
|
|
26
|
+
name: Version parity check (Cargo.toml vs pyproject.toml)
|
|
27
|
+
entry: python scripts/sync_version.py --check
|
|
28
|
+
language: system
|
|
29
|
+
pass_filenames: false
|
|
30
|
+
files: ^(Cargo\.toml|pyproject\.toml)$
|
|
31
|
+
|
|
32
|
+
- id: cargo-fmt
|
|
33
|
+
name: Rust format check (cargo fmt)
|
|
34
|
+
entry: cargo fmt --all -- --check
|
|
35
|
+
language: system
|
|
36
|
+
pass_filenames: false
|
|
37
|
+
files: \.rs$
|
|
38
|
+
|
safexml-1.0.0/.releaserc
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tagFormat": "v${version}",
|
|
3
|
+
"branches": [
|
|
4
|
+
"main"
|
|
5
|
+
],
|
|
6
|
+
"plugins": [
|
|
7
|
+
"@semantic-release/commit-analyzer",
|
|
8
|
+
"@semantic-release/release-notes-generator",
|
|
9
|
+
[
|
|
10
|
+
"@semantic-release/changelog",
|
|
11
|
+
{
|
|
12
|
+
"changelogFile": "CHANGELOG.md"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
"@semantic-release/exec",
|
|
17
|
+
{
|
|
18
|
+
"prepareCmd": "python3 scripts/sync_version.py --set ${nextRelease.version} && cargo check"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"@semantic-release/git",
|
|
23
|
+
{
|
|
24
|
+
"assets": [
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"Cargo.toml",
|
|
27
|
+
"Cargo.lock",
|
|
28
|
+
"pyproject.toml"
|
|
29
|
+
],
|
|
30
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"@semantic-release/github",
|
|
35
|
+
{
|
|
36
|
+
"successComment": false,
|
|
37
|
+
"failComment": false,
|
|
38
|
+
"releasedLabels": false
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
|
safexml-1.0.0/AGENTS.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions and guidelines for AI coding assistants working in the `safexml` repository.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Project Overview
|
|
8
|
+
|
|
9
|
+
`safexml` is a high-performance, secure, modern XML parser and drop-in `defusedxml` replacement built in Rust.
|
|
10
|
+
|
|
11
|
+
- **Technology**: Rust 2021, PyO3 (`abi3-py312`), `quick-xml` (0.42+), `maturin`.
|
|
12
|
+
- **Repository**: `polyxml/safexml`
|
|
13
|
+
- **Supported Python**: `Python >= 3.12` exclusively. No legacy Python 2 or Python 3.7–3.11 backward-compatibility shims.
|
|
14
|
+
- **Maintainer**: Bailey Nguyen (`bailey.tan.nguyen@gmail.com`).
|
|
15
|
+
- **Organization**: [PolyXML](https://github.com/polyxml).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. Core Architectural Decisions & Invariants
|
|
20
|
+
|
|
21
|
+
When contributing or refactoring, strictly maintain the following invariants:
|
|
22
|
+
|
|
23
|
+
1. **Python >= 3.12 Exclusively**:
|
|
24
|
+
- Never introduce Python 2 or legacy Python 3 (< 3.12) compatibility shims (e.g. `sys.version_info < (3, ...)` or `six`).
|
|
25
|
+
- Use modern syntax (`X | Y` type unions, PEP 561 typing, built-in generics).
|
|
26
|
+
|
|
27
|
+
2. **Drop-in Compatibility with `defusedxml.ElementTree` & `xml.etree.ElementTree`**:
|
|
28
|
+
- Returns standard `xml.etree.ElementTree.Element` instances to maintain 100% compatibility with libraries like `pysaml2`, `python3-saml`, `openpyxl`, `python-docx`, `feedparser`, `botocore`.
|
|
29
|
+
- Re-exports standard ElementTree symbols (`Element`, `SubElement`, `Comment`, `PI`, `indent`, `register_namespace`, `dump`, `tostring`, `tostringlist`).
|
|
30
|
+
- Signature parity: `fromstring(text, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True, ...)` must accept `parser` keyword argument (resolving `tiran/defusedxml#78`).
|
|
31
|
+
|
|
32
|
+
3. **True GIL Detachment in Rust**:
|
|
33
|
+
- All tokenization, XML scanning, DTD validation, and entity checking in Rust must execute outside the Python GIL using `py.detach(|| ...)` to allow multi-threaded Python servers (FastAPI, Django, Celery, gunicorn) to parse XML concurrently without blocking.
|
|
34
|
+
|
|
35
|
+
4. **Security by Default**:
|
|
36
|
+
- **Billion Laughs / Entity Expansion**: Reject non-predefined entities when `forbid_entities=True` (`EntitiesForbidden`).
|
|
37
|
+
- **XXE (XML External Entity)**: Reject `SYSTEM` and `PUBLIC` references when `forbid_external=True` (`ExternalReferenceForbidden`).
|
|
38
|
+
- **DTD Injections**: Reject `<!DOCTYPE>` declarations when `forbid_dtd=True` (`DTDForbidden`).
|
|
39
|
+
- **Nesting Depth Limit**: Enforce `max_depth` (default 1,000) to protect against stack exhaustion.
|
|
40
|
+
- **Size Limits**: Enforce `max_text_size` and `max_entity_size` to prevent memory exhaustion attacks.
|
|
41
|
+
|
|
42
|
+
5. **Upstream Issue Fixes**:
|
|
43
|
+
- Maintain the fixes for tracked upstream issues:
|
|
44
|
+
- `tiran/defusedxml#105`: Exception formatting for `rich` tracebacks.
|
|
45
|
+
- `tiran/defusedxml#104`: PEP 561 `py.typed` marker and full type annotations.
|
|
46
|
+
- `tiran/defusedxml#87`: Standard `ElementTree.indent()` support.
|
|
47
|
+
- `tiran/defusedxml#80`: Direct `Element` class export.
|
|
48
|
+
- `tiran/defusedxml#79`: `register_namespace()` support.
|
|
49
|
+
- `tiran/defusedxml#76`, `#77`, `#88`: Safe `defuse_stdlib()` without breaking 3rd-party libs.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Tooling & Development Workflow
|
|
54
|
+
|
|
55
|
+
### Rust Toolchain
|
|
56
|
+
- Check workspace:
|
|
57
|
+
```bash
|
|
58
|
+
cargo check
|
|
59
|
+
```
|
|
60
|
+
- Run Rust tests:
|
|
61
|
+
```bash
|
|
62
|
+
cargo test
|
|
63
|
+
```
|
|
64
|
+
- Format code:
|
|
65
|
+
```bash
|
|
66
|
+
cargo fmt --all
|
|
67
|
+
```
|
|
68
|
+
- Clippy:
|
|
69
|
+
```bash
|
|
70
|
+
cargo clippy -- -D warnings
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Python Development
|
|
74
|
+
- Compile extension into local virtualenv:
|
|
75
|
+
```bash
|
|
76
|
+
maturin develop
|
|
77
|
+
```
|
|
78
|
+
- Run test suite:
|
|
79
|
+
```bash
|
|
80
|
+
pytest -v
|
|
81
|
+
```
|
|
82
|
+
- Lint & format with Ruff:
|
|
83
|
+
```bash
|
|
84
|
+
ruff check python tests
|
|
85
|
+
ruff format python tests
|
|
86
|
+
```
|
|
87
|
+
- Version parity check:
|
|
88
|
+
```bash
|
|
89
|
+
python scripts/sync_version.py --check
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 4. Release Process
|
|
95
|
+
- Releases are automated via GitHub Actions (`.github/workflows/release.yml`) using Semantic Release (`.releaserc`) and PyPI Trusted Publishing.
|
|
96
|
+
- Manifest versions (`Cargo.toml` and `pyproject.toml`) are kept in sync via `scripts/sync_version.py`.
|
|
97
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [1.0.0] - 2026-10-11
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- Initial release of `safexml`: High-performance, secure, modern XML parser and drop-in `defusedxml` replacement built in Rust.
|
|
10
|
+
- Core Rust streaming XML parser powered by `quick-xml` and `PyO3` with true GIL release (`py.detach`).
|
|
11
|
+
- Full drop-in parity for `defusedxml.ElementTree` and `xml.etree.ElementTree`.
|
|
12
|
+
- Strict security against Billion Laughs (exponential entity expansion), Quadratic Blowup, XXE, cyclic entities, and external DTDs.
|
|
13
|
+
- Enforced depth recursion limits (default 1,000) and text/entity memory bounds.
|
|
14
|
+
- Resolved upstream `defusedxml` open issues:
|
|
15
|
+
- tiran/defusedxml#105: Rich traceback exception formatting.
|
|
16
|
+
- tiran/defusedxml#104: PEP 561 `py.typed` marker and full typing annotations.
|
|
17
|
+
- tiran/defusedxml#87: Standard `ElementTree.indent()` support.
|
|
18
|
+
- tiran/defusedxml#80: `Element` class export.
|
|
19
|
+
- tiran/defusedxml#79: `register_namespace()` helper.
|
|
20
|
+
- tiran/defusedxml#78: `fromstring(..., parser=None)` keyword signature parity.
|
|
21
|
+
- tiran/defusedxml#76, #77, #88: Safe non-destructive `defuse_stdlib()`.
|
|
22
|
+
|