93 Commits
Author SHA1 Message Date
xixu-me 4ba2749e82 Update logo URLs in README files
CI/CD / test (macos-latest, 3.12) (push) Waiting to run
CI/CD / test (macos-latest, 3.13) (push) Waiting to run
CI/CD / test (ubuntu-latest, 3.12) (push) Waiting to run
CI/CD / test (ubuntu-latest, 3.13) (push) Waiting to run
CI/CD / test (windows-latest, 3.12) (push) Waiting to run
CI/CD / test (windows-latest, 3.13) (push) Waiting to run
Replaced relative paths for the logo image with absolute URLs pointing to the GitHub repository. This ensures the logo is correctly displayed across all localized README files.
2025-06-09 13:48:06 +08:00
xixu-me 3fc59f248a Bump version to 1.2.5
Updated the `__version__` in `__init__.py` from 1.2.4 to 1.2.5 to reflect the latest changes or release.
2025-06-09 13:44:27 +08:00
xixu-me 308e7e3c12 Add custom icon to PyInstaller builds
Updated the PyInstaller commands in the CI workflow to include a custom icon (`docs/_static/favicon.ico`) for all binary builds. This ensures the binaries have a consistent branding across platforms.
2025-06-09 13:43:58 +08:00
xixu-me 68800be7a9 Bump version to 1.2.4
Updated the __version__ attribute in the __init__.py file to reflect the new version 1.2.4.
2025-06-09 12:20:49 +08:00
xixu-me af3e657aa4 Update README files with logo and documentation link
Added a centered logo and a link to the documentation in all README files across multiple languages. This enhances the visual presentation and provides direct access to the project's documentation.
2025-06-09 12:18:44 +08:00
xixu-me 305c13119f Improve robustness and clarity in TzstArchive
Refactored error handling, added default behaviors for unknown resolutions, and improved documentation for unsupported modes. Enhanced memory efficiency and compatibility in archive operations, streamlined extraction logic, and validated compression levels more effectively.
2025-06-09 11:14:01 +08:00
xixu-me efbcbbce99 Improve error handling and path validation
Updated compression level validation to use a fallback max level if the zstandard constant is unavailable. Added checks to ensure paths are not None before file operations in the extract_archive function, improving robustness and preventing potential errors.
2025-06-09 10:51:20 +08:00
xixu-me 320ed0a27a Improve robustness in TzstArchive handling
Enhanced error handling and validation in TzstArchive methods, including better checks for file object initialization and compressed stream creation. Updated compression level validation to use dynamic max level from zstd library. Simplified and clarified comments and error messages for unsupported modes and extraction operations.
2025-06-09 10:41:09 +08:00
xixu-me 36ffd4fd2b Update index.md 2025-06-08 11:42:28 +08:00
xixu-me 8abad3616a Update publish_docs.yml 2025-06-08 11:35:26 +08:00
xixu-me ec8705a7cf Update README.pt.md 2025-06-08 11:31:48 +08:00
xixu-me 89efc8e3e8 Update README.ko.md 2025-06-08 11:31:34 +08:00
xixu-me 64f2dd6757 Update README.fr.md 2025-06-08 11:31:18 +08:00
xixu-me 7b306554f7 Update README.de.md 2025-06-08 11:31:01 +08:00
xixu-me c78d74768b Update README.ru.md 2025-06-08 11:30:43 +08:00
xixu-me 37af38e8bd Update README.ar.md 2025-06-08 11:30:16 +08:00
xixu-me 6476e2445a Update README.ja.md 2025-06-08 11:29:59 +08:00
xixu-me d9ca6d0d4e Update README.es.md 2025-06-08 11:29:38 +08:00
xixu-me 641256a133 Update README.zh.md 2025-06-08 11:29:19 +08:00
xixu-me 0b37b707b2 Update README.md 2025-06-08 11:27:22 +08:00
xixu-me 069ac67eea Update examples.md 2025-06-07 00:30:24 +08:00
xixu-me 7e3bb1cf6a Update quickstart.md 2025-06-07 00:12:44 +08:00
xixu-me 57c5fd0e6c Update development.md 2025-06-07 00:00:49 +08:00
xixu-me 7dc51f18c1 Add docs/_static/tzst-square-logo.png 2025-06-06 22:55:37 +08:00
xixu-me 2e40a77f29 Delete docs/_static/tzst-square-logo.png 2025-06-06 22:53:58 +08:00
xixu-me e60f252abc Update development.md 2025-06-06 22:41:45 +08:00
xixu-me 56b39fea44 Update index.md 2025-06-06 22:31:44 +08:00
xixu-me ce061aa396 Update index.md 2025-06-06 22:19:41 +08:00
xixu-me 8de8ec0356 Add social media metadata to documentation
Updated multiple documentation files to include Open Graph and Twitter metadata for better social media sharing. Added a new logo image to the static assets folder and referenced it in the metadata.
2025-06-06 22:15:54 +08:00
xixu-me 6c48d80d9a Update badges in documentation
Added new badges for PyPI downloads and GitHub stars to the documentation. Updated the GitHub license badge link to point to the correct URL.
2025-06-06 22:02:50 +08:00
xixu-me 44843e0035 Update documentation and add new guides
This commit updates multiple documentation files to improve clarity, remove emojis, and add new sections. Key changes include the addition of 'development.md' and 'performance.md', updates to the quickstart guide, and enhancements to the API and CLI documentation. These changes aim to provide better guidance for users and contributors.
2025-06-06 22:00:11 +08:00
xixu-me 42180498e0 Fix broken link in documentation index
Replaced `{doc}` with `{ref}` for the 'genindex' link to ensure proper rendering and functionality in the documentation.
2025-06-06 20:34:57 +08:00
xixu-me 9b9ad446c1 Update docs and remove CLI interactive option
Updated the Sphinx configuration to set 'includehidden' to False and revised the documentation index to streamline content. Removed the '--interactive' option from CLI commands 'extract' and 'extract_flat' as it is no longer supported.
2025-06-06 20:27:47 +08:00
xixu-me af7bef5371 Update documentation index with hidden toctree
Added a hidden toctree section in docs/index.md to include 404 and README entries. This improves the structure and navigation of the documentation.
2025-06-06 20:05:49 +08:00
xixu-me 27e1809bf9 Update documentation URLs to new domain
Replaced all occurrences of the old domain 'xixu-me.github.io/tzst' with the new domain 'tzst.xi-xu.me' in layout.html and conf.py for improved SEO and consistency.
2025-06-06 19:59:46 +08:00
xixu-me 4d3d34051f Update Sphinx config and documentation index
Added 'sphinx.ext.coverage' to the Sphinx extensions in conf.py to enable coverage reporting. Removed 'README' and '404' entries from the documentation index in index.md for cleanup.
2025-06-06 19:50:17 +08:00
xixu-me 526ddb76ea Update logo image in documentation
Replaced the existing logo image in the documentation static files with an updated version.
2025-06-06 19:35:32 +08:00
xixu-me 199fe41293 Update documentation references and add anchors
Updated internal references in the documentation to use simplified anchor names. Added missing anchors for sections in 'examples.md' and 'quickstart.md'. Minor formatting adjustments were also made for consistency.
2025-06-06 19:07:31 +08:00
xixu-me 6a9a783ad3 Update documentation workflow and 404 page
Added a new 404.md file for handling 'Page Not Found' errors in the documentation. Updated the publish_docs.yml workflow to include a 'cname' parameter. Removed the CNAME file from the docs directory as it is now managed in the workflow.
2025-06-06 18:55:00 +08:00
xixu-me 571809c36f Add CNAME file for custom domain
Added a CNAME file to configure the custom domain tzst.xi-xu.me for the documentation site.
2025-06-06 18:40:40 +08:00
xixu-me 8b39da60da Update documentation structure and metadata
Added favicon and logo to the static assets. Updated layout.html for improved SEO and performance. Migrated metadata in documentation files to use 'myst' format for consistency. Adjusted configuration in conf.py and fixed formatting issues.
2025-06-06 18:34:17 +08:00
xixu-me b5f7fa8dca Enhance documentation with SEO metadata and layout
Added a new custom layout template for SEO and social media meta tags. Updated multiple documentation files with metadata for improved search engine optimization and social sharing. Enhanced Sphinx configuration with additional HTML options and meta tags.
2025-06-06 17:50:43 +08:00
xixu-me abbeab83f1 Update documentation with indexing and references
Added the ':no-index:' directive to CLI, core, and exceptions API documentation to exclude them from indexing. Updated examples documentation to use Sphinx references for better navigation and added section anchors for improved structure.
2025-06-06 17:39:15 +08:00
xixu-me ef5d06cb0e Enhance documentation for tzst library
- Updated the introduction to provide a clearer overview of tzst's capabilities and features.
- Expanded the Key Features section with detailed descriptions and icons for better readability.
- Improved the Quick Example section to include both command line and Python API usage.
- Added installation options with detailed steps for PyPI, standalone binaries, and source installation.
- Enhanced the Quick Start Guide with structured installation methods and basic usage examples.
- Introduced advanced features like security filters, conflict resolution, and performance optimization.
- Provided comprehensive error handling and common patterns for backup scripts and archive verification.
- Updated the requirements and reference links for better navigation.
2025-06-06 17:31:08 +08:00
xixu-me d0e09347eb Update documentation URL in CLI script
Replaced the outdated GitHub README URL with the new documentation URL (https://tzst.xi-xu.me) in the CLI script for better reference.
2025-06-06 16:23:25 +08:00
xixu-me 76e56c984f Update Sphinx build ignore rules
Removed '_static/' from .gitignore to allow tracking of the '_static' directory. Added a .gitkeep file in '_static' to ensure the directory is included in the repository.
2025-06-06 16:05:45 +08:00
xixu-me d3e5b4e064 Remove duplicate CLI function documentation
Eliminated redundant sections for `format_size` and `validate_compression_level` in the CLI documentation to improve clarity and avoid repetition.
2025-06-06 16:04:34 +08:00
xixu-me 23dcdab305 Update documentation structure and configuration
Revised Sphinx directives in CLI and exceptions API docs to use ':no-index' instead of ':members', ':undoc-members', and ':show-inheritance'. Removed 'display_version' from Sphinx configuration. Added 'README' to the index page for better navigation.
2025-06-06 15:47:38 +08:00
xixu-me 2777fea4fc Add linkify-it-py to documentation requirements
Added the linkify-it-py package (version 2.0.0 or higher) to the docs/requirements.txt file to support automatic linkification in the documentation.
2025-06-06 15:26:27 +08:00
xixu-me 2ca81e0918 Update docs workflow and improve documentation scripts
Modified the GitHub Actions workflow to trigger on push and pull requests to the main branch. Updated `.gitignore` to include additional Sphinx build outputs. Refactored `build_docs.py` for better formatting and error handling. Removed 'changelog' from the documentation index.
2025-06-06 15:15:52 +08:00
xixu-me eb3f66f85e Update README.zh.md 2025-06-06 00:23:20 +08:00
xixu-me 028c7e3650 Update README.ru.md 2025-06-06 00:23:06 +08:00
xixu-me 9e5a677155 Update README.pt.md 2025-06-06 00:22:55 +08:00
xixu-me 19356b819f Update README.md 2025-06-06 00:22:43 +08:00
xixu-me bac2f44072 Update README.ko.md 2025-06-06 00:22:30 +08:00
xixu-me ffb6981965 Update README.ja.md 2025-06-06 00:22:19 +08:00
xixu-me 15d0828fae Update README.fr.md 2025-06-06 00:22:08 +08:00
xixu-me 710c22f605 Update README.es.md 2025-06-06 00:21:55 +08:00
xixu-me 7c268be215 Update README.de.md 2025-06-06 00:21:32 +08:00
xixu-me b92b5c8652 Update README.ar.md 2025-06-06 00:21:18 +08:00
xixu-me af0159a9fe Update language links in README files
Replaced '🇬🇧 English' with 'us English' in all README files and standardized placeholder text for version numbers in the Arabic README. These changes improve consistency across documentation.
2025-06-05 15:29:12 +08:00
xixu-me f6d1ff631d Add Portuguese, Russian, etc. translations for README files 2025-06-05 12:19:39 +08:00
xixu-me b5a654c187 Update README.zh.md 2025-06-05 10:34:33 +08:00
xixu-me 9b30a8657c Update README.ko.md 2025-06-05 02:05:03 +08:00
xixu-me 7f2e53c9d0 Create README.ko.md 2025-06-05 02:03:47 +08:00
xixu-me 58400c8f42 Update README.ja.md 2025-06-05 01:51:28 +08:00
xixu-me cd56764f84 Update README.ja.md 2025-06-05 00:55:16 +08:00
xixu-me 8b3e4d2429 Create README.ja.md 2025-06-05 00:54:04 +08:00
xixu-me 22a879a002 Update README.es.md 2025-06-05 00:51:21 +08:00
xixu-me ac14a4bca7 Create README.es.md 2025-06-05 00:42:39 +08:00
xixu-me f89a10ec50 Update README.zh.md 2025-06-05 00:31:31 +08:00
xixu-me 518e90f0ee Create README.zh.md 2025-06-05 00:03:31 +08:00
xixu-me 194037b87c Update README.md 2025-06-05 00:01:14 +08:00
xixu-me 47d6504f39 Update README.md 2025-06-04 22:33:38 +08:00
xixu-me a15a497c52 Update PyPI badge and README formatting
Modified the PyPI version badge URL in the CI workflow and README to simplify the URL. Enhanced README with emojis for better visual appeal and added detailed installation instructions for standalone binaries.
2025-06-04 22:16:41 +08:00
xixu-me f866f30b7e Bump version to 1.2.3
CI/CD / test (macos-latest, 3.12) (push) Waiting to run
CI/CD / test (macos-latest, 3.13) (push) Waiting to run
CI/CD / test (ubuntu-latest, 3.12) (push) Waiting to run
CI/CD / test (ubuntu-latest, 3.13) (push) Waiting to run
CI/CD / test (windows-latest, 3.12) (push) Waiting to run
CI/CD / test (windows-latest, 3.13) (push) Waiting to run
Updated the `__version__` in `__init__.py` from 1.2.2 to 1.2.3 to reflect the latest changes or release.
2025-06-04 21:39:18 +08:00
xixu-me b2eebdedb2 Simplify GitHub release notes in CI workflow
Updated the GitHub release creation step to use a simplified title format and removed detailed release notes. This change streamlines the release process by omitting installation instructions and commit history links.
2025-06-04 21:39:02 +08:00
xixu-me 1414138341 Update badges [skip ci] 2025-06-04 13:29:39 +00:00
xixu-me eb4d29e503 Bump version to 1.2.2
Updated the `__version__` in `__init__.py` from 1.2.1 to 1.2.2 to reflect the latest changes or release.
2025-06-04 21:27:09 +08:00
xixu-me 76963aaf9f Fix indentation in CI workflow script
Corrected the indentation in the Python script within the CI workflow to ensure proper execution of the version assignment and file writing logic.
2025-06-04 21:26:38 +08:00
xixu-me 8035a77c10 Bump version to 1.2.1
Updated the `__version__` in `__init__.py` from 1.2.0 to 1.2.1 to reflect the latest changes or release.
2025-06-04 21:18:57 +08:00
xixu-me d95f173308 Update PyInstaller command in CI workflow
Modified the PyInstaller command in the CI configuration to specify 'src/main.py' as the entry point for building binaries across macOS, Windows, and Linux ARM64 platforms.
2025-06-04 21:12:43 +08:00
xixu-me 637f1d2562 Update CI workflow and add main entry point
Modified the CI workflow to correctly specify the path to the main script for PyInstaller builds. Added a new standalone entry point in src/main.py to serve as the executable script for tzst.
2025-06-04 21:08:33 +08:00
xixu-me d2f11636be Revert "Add standalone entry point for tzst"
This reverts commit 69918b4be5.
2025-06-04 21:05:15 +08:00
xixu-me 69918b4be5 Add standalone entry point for tzst
Created a new main.py file as the standalone entry point for tzst, intended for PyInstaller builds. This script sets up the Python path and invokes the CLI main function.
2025-06-04 21:04:24 +08:00
xixu-me 7d688d420b Bump version to 1.2.0
Updated the `__version__` in `__init__.py` to reflect the new release version 1.2.0.
2025-06-04 20:54:49 +08:00
xixu-me 56a10bc038 Update CI workflow for macOS and binary verification
This commit updates the CI workflow to use macOS-14 for ARM-based runners, adds binary verification steps for Linux, macOS, and Windows, and improves release asset upload handling with success checks. Additionally, it refines the README badge update process for PyPI version.
2025-06-04 20:54:31 +08:00
xixu-me 1dd3cde654 Enhance CI/CD workflow for versioned tags
Updated the CI/CD pipeline to support versioned tags for releases. Added new jobs for building binaries across multiple platforms and architectures, creating release assets, and uploading them to GitHub. Improved conditional logic for triggering workflows based on tags and streamlined badge updates.
2025-06-04 20:42:48 +08:00
xixu-me a1de2e1855 Remove unused report configuration in Codecov
The 'report.exclude_labels' section was removed from the Codecov configuration as it is no longer needed. This simplifies the configuration file.
2025-06-04 20:06:18 +08:00
xixu-me 6397968a4d Improve conflict resolution in archive extraction
Updated the `extract_archive` function to handle string-based conflict resolution by converting it to an enum. Added fallback to `ConflictResolution.REPLACE` for invalid values, ensuring safer and more predictable behavior.
2025-06-04 19:27:41 +08:00
xixu-me cb5c198d16 Refactor tests and enhance coverage for conflict resolution and edge cases
- Removed outdated tests for missing lines in core.py.
- Added comprehensive tests for conflict resolution functionality, including various resolution strategies and edge cases.
- Improved error handling tests for archive creation and extraction processes.
- Enhanced unit tests for basic functionality and convenience functions with pytest markers.
- Introduced tests for unique filename generation and conflict handling scenarios.
- Added edge case tests for archive extraction and error conditions to improve overall test coverage.
2025-06-04 18:55:53 +08:00
xixu-me afe5d738a6 Merge pull request #5 from xixu-me/alert-autofix-5
Potential fix for code scanning alert no. 5: Workflow does not contain permissions
2025-06-02 22:02:55 +08:00
xixu-meandCopilot Autofix powered by AI 06eac113fa Potential fix for code scanning alert no. 5: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-02 21:46:34 +08:00
47 changed files with 9384 additions and 2212 deletions

No files matched your search

-5
View File
@@ -11,11 +11,6 @@ coverage:
threshold: 1%
informational: true
# Report coverage for all files, even if not touched in PR
report:
exclude_labels:
- "skip-coverage"
comment:
layout: "reach,diff,flags,tree"
behavior: default
+204 -8
View File
@@ -3,6 +3,8 @@ name: CI/CD
on:
push:
branches: [main, develop]
tags:
- "v*.*.*"
paths-ignore:
- "*.md"
- "LICENSE"
@@ -93,7 +95,7 @@ jobs:
publish:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
url: https://pypi.org/p/tzst
@@ -110,9 +112,201 @@ jobs:
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
build-binaries:
needs: test
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
strategy:
matrix:
include:
# Linux architectures
- os: ubuntu-latest
os_name: linux
arch: x86_64
python-version: "3.12"
- os: ubuntu-latest
os_name: linux
arch: aarch64
python-version: "3.12"
cross_compile: true
# Windows architectures
- os: windows-latest
os_name: windows
arch: amd64
python-version: "3.12"
- os: windows-latest
os_name: windows
arch: arm64
python-version: "3.12"
cross_compile: true
# macOS architectures
- os: macos-13 # Intel-based runner
os_name: macos
arch: x86_64
python-version: "3.12"
- os: macos-14 # ARM-based runner (M1/M2)
os_name: macos
arch: arm64
python-version: "3.12"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Extract version from tag
id: version
shell: bash
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pyinstaller
- name: Build binary
run: |
python -m PyInstaller --onefile --name tzst --console --icon docs/_static/favicon.ico src/main.py
- name: Verify binary (Linux/macOS)
if: matrix.os != 'windows-latest'
run: |
if [ ! -f "dist/tzst" ]; then
echo "Binary build failed!"
exit 1
fi
- name: Verify binary (Windows)
if: matrix.os == 'windows-latest'
run: |
if (-not (Test-Path "dist/tzst.exe")) {
Write-Host "Binary build failed!"
exit 1
}
- name: Build binary for ARM64 on macOS
if: matrix.os == 'macos-14' && matrix.arch == 'arm64'
run: |
python -m PyInstaller --onefile --name tzst --console --target-arch arm64 --icon docs/_static/favicon.ico src/main.py
- name: Build binary for ARM64 on Windows
if: matrix.os == 'windows-latest' && matrix.arch == 'arm64'
run: |
python -m PyInstaller --onefile --name tzst --console --target-arch arm64 --icon docs/_static/favicon.ico src/main.py
- name: Setup cross-compilation for Linux ARM64
if: matrix.os == 'ubuntu-latest' && matrix.arch == 'aarch64'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
- name: Build binary for ARM64 on Linux
if: matrix.os == 'ubuntu-latest' && matrix.arch == 'aarch64'
env:
CC: aarch64-linux-gnu-gcc
run: |
python -m PyInstaller --onefile --name tzst --console --target-arch aarch64 --icon docs/_static/favicon.ico src/main.py
- name: Prepare archive contents (Linux/macOS)
if: matrix.os != 'windows-latest'
run: |
mkdir -p archive
cp dist/tzst archive/
cp README.md archive/
cp LICENSE archive/
- name: Prepare archive contents (Windows)
if: matrix.os == 'windows-latest'
run: |
New-Item -ItemType Directory -Path archive -Force
Copy-Item dist/tzst.exe archive/
Copy-Item README.md archive/
Copy-Item LICENSE archive/
- name: Create zip archive (Linux/macOS)
if: matrix.os != 'windows-latest'
run: |
cd archive
zip -r ../tzst-v${{ steps.version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}.zip .
- name: Create zip archive (Windows)
if: matrix.os == 'windows-latest'
run: |
cd archive
Compress-Archive -Path * -DestinationPath ../tzst-v${{ steps.version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}.zip
- name: Upload binary artifacts
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os_name }}-${{ matrix.arch }}
path: tzst-v${{ steps.version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}.zip
create-release:
needs: [publish, build-binaries]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download all binary artifacts
uses: actions/download-artifact@v4
with:
pattern: binary-*
merge-multiple: true
- name: Create GitHub Release
run: |
gh release create ${{ github.ref_name }} \
--title "tzst ${{ steps.version.outputs.version }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release Assets
run: |
echo "Available files:"
ls -la tzst-v${{ steps.version.outputs.version }}-*.zip 2>/dev/null || echo "No zip files found"
success=0
for file in tzst-v${{ steps.version.outputs.version }}-*.zip; do
if [ -f "$file" ]; then
echo "Uploading $file"
if gh release upload ${{ github.ref_name }} "$file" --clobber; then
echo "Successfully uploaded $file"
success=$((success + 1))
else
echo "Failed to upload $file"
fi
fi
done
if [ $success -eq 0 ]; then
echo "Warning: No files were successfully uploaded"
else
echo "Successfully uploaded $success file(s)"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_badges:
needs: [build, publish]
if: always() && needs.build.result == 'success'
if: always() && needs.build.result == 'success' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
@@ -120,11 +314,6 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
- name: Wait if this is after a publish
if: github.event_name == 'release' && github.event.action == 'published'
run: sleep 30
- name: Set up Python
uses: actions/setup-python@v5
@@ -151,6 +340,13 @@ jobs:
- name: Update README badge
run: |
echo "Updating PyPI badge with version: ${{ steps.pypi_version.outputs.version }}"
# Update PyPI version badge in README.md
if [ -f "README.md" ]; then
# Replace PyPI version badge
sed -i 's|https://img.shields.io/pypi/v/tzst[^)]*|https://img.shields.io/pypi/v/tzst|g' README.md || true
# Update version in badge alt text if exists
sed -i 's|PyPI - Version[^]]*|PyPI - Version|g' README.md || true
fi
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
@@ -159,4 +355,4 @@ jobs:
with:
commit_message: "Update badges [skip ci]"
file_pattern: README.md
branch: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
branch: main
+11
View File
@@ -4,9 +4,17 @@ on:
push:
branches:
- main
paths-ignore:
- "*.md"
- "LICENSE"
- ".gitignore"
pull_request:
branches:
- main
paths-ignore:
- "*.md"
- "LICENSE"
- ".gitignore"
jobs:
build-and-deploy-docs:
@@ -64,9 +72,12 @@ jobs:
user_name: "github-actions[bot]"
user_email: "github-actions[bot]@users.noreply.github.com"
commit_message: "Deploy documentation from ${{ github.sha }}"
cname: tzst.xi-xu.me
docs-quality-check:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
+521
View File
@@ -0,0 +1,521 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | **🇦🇪 العربية** | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
<div dir="rtl" lang="ar">
**tzst** هي مكتبة Python من الجيل التالي مُطورة لإدارة الأرشيف الحديث، تستفيد من ضغط Zstandard المتطور لتقديم أداء وأمان وموثوقية فائقة. مبنية حصرياً لـ Python 3.12+، هذا الحل على مستوى المؤسسة يدمج العمليات الذرية وكفاءة التدفق ووواجهة برمجة التطبيقات المصممة بعناية فائقة لإعادة تعريف كيفية تعامل المطورين مع أرشيف `.tzst`/`.tar.zst` في بيئات الإنتاج. 🚀
## ✨ الميزات
- **🗜️ ضغط عالي**: ضغط Zstandard لنسب ضغط وسرعة ممتازة
- **📁 توافق Tar**: ينشئ أرشيف tar قياسي مضغوط بـ Zstandard
- **💻 واجهة سطر الأوامر**: واجهة CLI بديهية مع دعم التدفق وخيارات شاملة
- **🐍 Python API**: واجهة برمجة تطبيقات نظيفة وpythonic للاستخدام البرمجي
- **🌍 متعدد المنصات**: يعمل على Windows وmacOS وLinux
- **📂 امتدادات متعددة**: يدعم كلاً من امتدادات `.tzst` و `.tar.zst`
- **💾 فعال في الذاكرة**: وضع التدفق للتعامل مع الأرشيف الكبير باستخدام أقل للذاكرة
- **⚡ عمليات ذرية**: عمليات ملف آمنة مع تنظيف تلقائي عند المقاطعة
- **🔒 آمن افتراضياً**: يستخدم مرشح 'data' للحد الأقصى من الأمان أثناء الاستخراج
- **🚨 معالجة أخطاء محسنة**: رسائل خطأ واضحة مع بدائل مفيدة
## 📥 التثبيت
### من إصدارات GitHub
تحميل ملفات تنفيذية مستقلة لا تتطلب تثبيت Python:
#### المنصات المدعومة
| المنصة | المعمارية | الملف |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{version}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{version}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{version}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{version}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{version}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{version}-macos-arm64.zip` |
#### 🛠️ خطوات التثبيت
1. **📥 تحميل** الأرشيف المناسب لمنصتك من [صفحة الإصدارات الأحدث](https://github.com/xixu-me/tzst/releases/latest)
2. **📦 استخراج** الأرشيف للحصول على الملف التنفيذي `tzst` (أو `tzst.exe` على Windows)
3. **📂 نقل** الملف التنفيذي إلى مجلد في PATH الخاص بك:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: أضف المجلد الذي يحتوي على `tzst.exe` إلى متغير البيئة PATH
4. **✅ تحقق** من التثبيت: `tzst --help`
#### 🎯 فوائد التثبيت الثنائي
- ✅ **لا يتطلب Python** - ملف تنفيذي مستقل
- ✅ **بدء تشغيل أسرع** - بدون إضافة مفسر Python
- ✅ **نشر سهل** - توزيع ملف واحد
- ✅ **سلوك متسق** - تبعيات مجمعة
### 📦 من PyPI
```bash
pip install tzst
```
### 🔧 من المصدر
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 تثبيت التطوير
يستخدم هذا المشروع معايير تعبئة Python الحديثة:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 البداية السريعة
### 💻 استخدام سطر الأوامر
> **ملاحظة**: تحميل [الملف الثنائي المستقل](#من-إصدارات-github) للحصول على أفضل أداء وعدم الاعتماد على Python. بدلاً من ذلك، استخدم `uvx tzst` للتشغيل دون تثبيت. راجع [وثائق uv](https://docs.astral.sh/uv/) للتفاصيل.
```bash
# 📁 إنشاء أرشيف
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 استخراج أرشيف
tzst x archive.tzst
# 📋 قائمة محتويات الأرشيف
tzst l archive.tzst
# 🧪 اختبار سلامة الأرشيف
tzst t archive.tzst
```
### 🐍 استخدام Python API
```python
from tzst import create_archive, extract_archive, list_archive
# إنشاء أرشيف
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# استخراج أرشيف
extract_archive("archive.tzst", "output_directory/")
# قائمة محتويات الأرشيف
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 واجهة سطر الأوامر
### 📁 عمليات الأرشيف
#### ➕ إنشاء أرشيف
```bash
# الاستخدام الأساسي
tzst a archive.tzst file1.txt file2.txt
# مع مستوى الضغط (1-22، افتراضي: 3)
tzst a archive.tzst files/ -l 15
# أوامر بديلة
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 استخراج أرشيف
```bash
# استخراج مع هيكل المجلد الكامل
tzst x archive.tzst
# استخراج إلى مجلد محدد
tzst x archive.tzst -o output/
# استخراج ملفات محددة
tzst x archive.tzst file1.txt dir/file2.txt
# استخراج بدون هيكل المجلد (مسطح)
tzst e archive.tzst -o output/
# استخدام وضع التدفق للأرشيف الكبير
tzst x archive.tzst --streaming -o output/
```
#### 📋 قائمة المحتويات
```bash
# قائمة بسيطة
tzst l archive.tzst
# قائمة مفصلة مع التفاصيل
tzst l archive.tzst -v
# استخدام وضع التدفق للأرشيف الكبير
tzst l archive.tzst --streaming -v
```
#### 🧪 اختبار السلامة
```bash
# اختبار سلامة الأرشيف
tzst t archive.tzst
# اختبار مع وضع التدفق
tzst t archive.tzst --streaming
```
### 📊 مرجع الأوامر
| الأمر | البدائل | الوصف | دعم التدفق |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | إنشاء أو إضافة إلى أرشيف | N/A |
| `x` | `extract` | استخراج مع المسارات الكاملة | ✓ `--streaming` |
| `e` | `extract-flat` | استخراج بدون هيكل المجلد | ✓ `--streaming` |
| `l` | `list` | قائمة محتويات الأرشيف | ✓ `--streaming` |
| `t` | `test` | اختبار سلامة الأرشيف | ✓ `--streaming` |
### ⚙️ خيارات CLI
- `-v, --verbose`: تمكين الإخراج المفصل
- `-o, --output DIR`: تحديد مجلد الإخراج (أوامر الاستخراج)
- `-l, --level LEVEL`: تحديد مستوى الضغط 1-22 (أمر الإنشاء)
- `--streaming`: تمكين وضع التدفق للمعالجة الفعالة في الذاكرة
- `--filter FILTER`: مرشح الأمان للاستخراج (data/tar/fully_trusted)
- `--no-atomic`: تعطيل العمليات الذرية للملفات (غير مستحسن)
### 🔒 مرشحات الأمان
```bash
# استخراج مع أقصى أمان (افتراضي)
tzst x archive.tzst --filter data
# استخراج مع توافق tar قياسي
tzst x archive.tzst --filter tar
# استخراج مع ثقة كاملة (خطر - فقط للأرشيف الموثوق)
tzst x archive.tzst --filter fully_trusted
```
**🔐 خيارات مرشح الأمان:**
- `data` (افتراضي): الأكثر أماناً. يحجب الملفات الخطيرة والمسارات المطلقة والمسارات خارج مجلد الاستخراج
- `tar`: توافق tar قياسي. يحجب المسارات المطلقة واجتياز المجلد
- `fully_trusted`: لا قيود أمان. استخدم فقط مع الأرشيف الموثوق تماماً
## 🐍 Python API
### 📦 فئة TzstArchive
```python
from tzst import TzstArchive
# إنشاء أرشيف جديد
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# قراءة أرشيف موجود
with TzstArchive("archive.tzst", "r") as archive:
# قائمة المحتويات
contents = archive.list(verbose=True)
# استخراج مع مرشح الأمان
archive.extract("file.txt", "output/", filter="data")
# اختبار السلامة
is_valid = archive.test()
# للأرشيف الكبير، استخدم وضع التدفق
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ قيود مهمة:**
- **❌ وضع الإلحاق غير مدعوم**: أنشئ أرشيف متعدد أو أعد إنشاء الأرشيف بالكامل بدلاً من ذلك
### 🎯 دوال الراحة
#### 📁 create_archive()
```python
from tzst import create_archive
# إنشاء مع عمليات ذرية (افتراضي)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# استخراج مع الأمان (افتراضي: مرشح 'data')
extract_archive("backup.tzst", "restore/")
# استخراج ملفات محددة
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# تسطيح هيكل المجلد
extract_archive("backup.tzst", "restore/", flatten=True)
# استخدام التدفق للأرشيف الكبير
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# قائمة بسيطة
files = list_archive("backup.tzst")
# قائمة مفصلة
files = list_archive("backup.tzst", verbose=True)
# تدفق للأرشيف الكبير
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# اختبار سلامة أساسي
if test_archive("backup.tzst"):
print("الأرشيف صالح")
# اختبار مع التدفق
if test_archive("large_backup.tzst", streaming=True):
print("الأرشيف الكبير صالح")
```
## 🔧 الميزات المتقدمة
### 📂 امتدادات الملفات
تتعامل المكتبة تلقائياً مع امتدادات الملفات مع التطبيع الذكي:
- `.tzst` - الامتداد الأساسي لأرشيف tar+zstandard
- `.tar.zst` - امتداد قياسي بديل
- الكشف التلقائي عند فتح الأرشيف الموجود
- إضافة الامتداد التلقائي عند إنشاء الأرشيف
```python
# هذه كلها تنشئ أرشيف صالح
create_archive("backup.tzst", files) # ينشئ backup.tzst
create_archive("backup.tar.zst", files) # ينشئ backup.tar.zst
create_archive("backup", files) # ينشئ backup.tzst
create_archive("backup.txt", files) # ينشئ backup.tzst (مُطبع)
```
### 🗜️ مستويات الضغط
تتراوح مستويات ضغط Zstandard من 1 (الأسرع) إلى 22 (أفضل ضغط):
- **المستوى 1-3**: ضغط سريع، ملفات أكبر
- **المستوى 3** (افتراضي): توازن جيد بين السرعة والضغط
- **المستوى 10-15**: ضغط أفضل، أبطأ
- **المستوى 20-22**: أقصى ضغط، أبطأ بكثير
### 🌊 وضع التدفق
استخدم وضع التدفق للمعالجة الفعالة في الذاكرة للأرشيف الكبير:
**✅ الفوائد:**
- انخفاض كبير في استخدام الذاكرة
- أداء أفضل للأرشيف الذي لا يناسب الذاكرة
- تنظيف تلقائي للموارد
**🎯 متى تستخدم:**
- أرشيف أكبر من 100 ميجابايت
- بيئات ذاكرة محدودة
- معالجة أرشيف بملفات كبيرة كثيرة
```python
# مثال: معالجة أرشيف نسخ احتياطي كبير
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# عمليات فعالة في الذاكرة
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ العمليات الذرية
جميع عمليات إنشاء الملفات تستخدم عمليات ملف ذرية افتراضياً:
- الأرشيف منشأ في ملفات مؤقتة أولاً، ثم نُقل ذرياً
- تنظيف تلقائي إذا تمت مقاطعة العملية
- لا خطر من أرشيف تالف أو غير مكتمل
- توافق متعدد المنصات
```python
# العمليات الذرية ممكنة افتراضياً
create_archive("important.tzst", files) # آمن من المقاطعة
# يمكن تعطيلها إذا لزم الأمر (غير مستحسن)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 معالجة الأخطاء
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("فشل في إلغاء ضغط الأرشيف")
except TzstFileNotFoundError:
print("ملف الأرشيف غير موجود")
except KeyboardInterrupt:
print("العملية مقاطعة من قبل المستخدم")
# التنظيف يتم تلقائياً
```
## 🚀 الأداء والمقارنة
### 💡 نصائح الأداء
1. **🗜️ مستويات الضغط**: المستوى 3 هو الأمثل لمعظم حالات الاستخدام
2. **🌊 التدفق**: استخدم للأرشيف أكبر من 100 ميجابايت
3. **📦 عمليات الدفعات**: أضف ملفات متعددة في جلسة واحدة
4. **📄 أنواع الملفات**: الملفات المضغوطة مسبقاً لن تنضغط كثيراً أكثر
### 🆚 مقابل أدوات أخرى
**مقابل tar + gzip:**
- ✅ نسب ضغط أفضل
- ⚡ إلغاء ضغط أسرع
- 🔄 خوارزمية حديثة
**مقابل tar + xz:**
- 🚀 ضغط أسرع بشكل كبير
- 📊 نسب ضغط مماثلة
- ⚖️ توازن سرعة/ضغط أفضل
**مقابل zip:**
- 🗜️ ضغط أفضل
- 🔐 يحافظ على أذونات Unix والبيانات الوصفية
- 🌊 دعم تدفق أفضل
## 📋 المتطلبات
- 🐍 Python 3.12 أو أعلى
- 📦 zstandard >= 0.19.0
## 🛠️ التطوير
### 🚀 إعداد بيئة التطوير
يستخدم هذا المشروع معايير تعبئة Python الحديثة:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 تشغيل الاختبارات
```bash
# تشغيل الاختبارات مع التغطية
pytest --cov=tzst --cov-report=html
# أو استخدم الأمر الأبسط (إعدادات التغطية في pyproject.toml)
pytest
```
### ✨ جودة الكود
```bash
# فحص جودة الكود
ruff check src tests
# تنسيق الكود
ruff format src tests
```
## 🤝 المساهمة
نرحب بالمساهمات! يرجى قراءة [دليل المساهمة](CONTRIBUTING.md) لـ:
- إعداد التطوير وهيكل المشروع
- إرشادات أسلوب الكود وأفضل الممارسات
- متطلبات الاختبار وكتابة الاختبارات
- عملية طلب السحب وسير عمل المراجعة
### 🚀 البداية السريعة للمساهمين
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 أنواع المساهمات المرحب بها
- 🐛 **إصلاح الأخطاء** - إصلاح مشاكل في الوظائف الموجودة
- ✨ **الميزات** - إضافة قدرات جديدة للمكتبة
- 📚 **التوثيق** - تحسين أو إضافة التوثيق
- 🧪 **الاختبارات** - إضافة أو تحسين تغطية الاختبار
- ⚡ **الأداء** - تحسين الكود الموجود
- 🔒 **الأمان** - معالجة الثغرات الأمنية
## 🙏 الشكر والتقدير
- [Meta Zstandard](https://github.com/facebook/zstd) لخوارزمية الضغط الممتازة
- [python-zstandard](https://github.com/indygreg/python-zstandard) لروابط Python
- مجتمع Python للإلهام والملاحظات
## 📄 الترخيص
حقوق النشر &copy; 2025 [شي شو](https://xi-xu.me). جميع الحقوق محفوظة.
مرخص تحت ترخيص [BSD 3-Clause](LICENSE).
</div>
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | **🇩🇪 Deutsch** | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** ist eine Python-Bibliothek der nächsten Generation, die für modernes Archivmanagement entwickelt wurde und hochmoderne Zstandard-Komprimierung nutzt, um überlegene Leistung, Sicherheit und Zuverlässigkeit zu bieten. Ausschließlich für Python 3.12+ entwickelt, kombiniert diese Unternehmenslösung atomare Operationen, Streaming-Effizienz und eine sorgfältig erstellte API, um die Art und Weise neu zu definieren, wie Entwickler mit `.tzst`/`.tar.zst`-Archiven in Produktionsumgebungen umgehen. 🚀
## ✨ Funktionen
- **🗜️ Hohe Komprimierung**: Zstandard-Komprimierung für ausgezeichnete Komprimierungsraten und Geschwindigkeit
- **📁 Tar-Kompatibilität**: Erstellt Standard-Tar-Archive, komprimiert mit Zstandard
- **💻 Kommandozeilenschnittstelle**: Intuitive CLI mit Streaming-Unterstützung und umfassenden Optionen
- **🐍 Python API**: Saubere, pythonische API für programmatische Nutzung
- **🌍 Plattformübergreifend**: Funktioniert auf Windows, macOS und Linux
- **📂 Mehrere Erweiterungen**: Unterstützt sowohl `.tzst` als auch `.tar.zst` Erweiterungen
- **💾 Speichereffizient**: Streaming-Modus für die Behandlung großer Archive mit minimalem Speicherverbrauch
- **⚡ Atomare Operationen**: Sichere Dateioperationen mit automatischer Bereinigung bei Unterbrechung
- **🔒 Standardmäßig sicher**: Verwendet den 'data' Filter für maximale Sicherheit beim Extrahieren
- **🚨 Verbesserte Fehlerbehandlung**: Klare Fehlermeldungen mit hilfreichen Alternativen
## 📥 Installation
### Von GitHub Releases
Lade eigenständige ausführbare Dateien herunter, die keine Python-Installation erfordern:
#### Unterstützte Plattformen
| Plattform | Architektur | Datei |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{Version}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{Version}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{Version}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{Version}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{Version}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{Version}-macos-arm64.zip` |
#### 🛠️ Installationsschritte
1. **📥 Lade** das entsprechende Archiv für deine Plattform von der [Seite der neuesten Releases](https://github.com/xixu-me/tzst/releases/latest) herunter
2. **📦 Extrahiere** das Archiv, um die ausführbare Datei `tzst` (oder `tzst.exe` unter Windows) zu erhalten
3. **📂 Verschiebe** die ausführbare Datei in ein Verzeichnis in deinem PATH:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: Füge das Verzeichnis mit `tzst.exe` zu deiner PATH-Umgebungsvariable hinzu
4. **✅ Überprüfe** die Installation: `tzst --help`
#### 🎯 Vorteile der Binärinstallation
- ✅ **Kein Python erforderlich** - Eigenständige ausführbare Datei
- ✅ **Schnellerer Start** - Kein Python-Interpreter-Overhead
- ✅ **Einfache Bereitstellung** - Einzeldatei-Distribution
- ✅ **Konsistentes Verhalten** - Gebündelte Abhängigkeiten
### 📦 Von PyPI
```bash
pip install tzst
```
### 🔧 Aus dem Quellcode
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 Entwicklungsinstallation
Dieses Projekt verwendet moderne Python-Packaging-Standards:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 Schnellstart
### 💻 Kommandozeilennutzung
> **Hinweis**: Lade die [eigenständige Binärdatei](#von-github-releases) für beste Leistung und keine Python-Abhängigkeit herunter. Alternativ verwende `uvx tzst` für die Ausführung ohne Installation. Siehe [uv-Dokumentation](https://docs.astral.sh/uv/) für Details.
```bash
# 📁 Archiv erstellen
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 Archiv extrahieren
tzst x archive.tzst
# 📋 Archivinhalt auflisten
tzst l archive.tzst
# 🧪 Archivintegrität testen
tzst t archive.tzst
```
### 🐍 Python API Nutzung
```python
from tzst import create_archive, extract_archive, list_archive
# Archiv erstellen
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# Archiv extrahieren
extract_archive("archive.tzst", "output_directory/")
# Archivinhalt auflisten
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 Kommandozeilenschnittstelle
### 📁 Archivoperationen
#### ➕ Archiv erstellen
```bash
# Grundlegende Nutzung
tzst a archive.tzst file1.txt file2.txt
# Mit Komprimierungsstufe (1-22, Standard: 3)
tzst a archive.tzst files/ -l 15
# Alternative Befehle
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 Archiv extrahieren
```bash
# Mit vollständiger Verzeichnisstruktur extrahieren
tzst x archive.tzst
# In spezifisches Verzeichnis extrahieren
tzst x archive.tzst -o output/
# Spezifische Dateien extrahieren
tzst x archive.tzst file1.txt dir/file2.txt
# Ohne Verzeichnisstruktur extrahieren (flach)
tzst e archive.tzst -o output/
# Streaming-Modus für große Archive verwenden
tzst x archive.tzst --streaming -o output/
```
#### 📋 Inhalt auflisten
```bash
# Einfache Auflistung
tzst l archive.tzst
# Ausführliche Auflistung mit Details
tzst l archive.tzst -v
# Streaming-Modus für große Archive verwenden
tzst l archive.tzst --streaming -v
```
#### 🧪 Integrität testen
```bash
# Archivintegrität testen
tzst t archive.tzst
# Mit Streaming-Modus testen
tzst t archive.tzst --streaming
```
### 📊 Befehlsreferenz
| Befehl | Aliase | Beschreibung | Streaming-Unterstützung |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Archiv erstellen oder hinzufügen | N/A |
| `x` | `extract` | Mit vollständigen Pfaden extrahieren | ✓ `--streaming` |
| `e` | `extract-flat` | Ohne Verzeichnisstruktur extrahieren | ✓ `--streaming` |
| `l` | `list` | Archivinhalt auflisten | ✓ `--streaming` |
| `t` | `test` | Archivintegrität testen | ✓ `--streaming` |
### ⚙️ CLI-Optionen
- `-v, --verbose`: Ausführliche Ausgabe aktivieren
- `-o, --output DIR`: Ausgabeverzeichnis spezifizieren (Extraktionsbefehle)
- `-l, --level LEVEL`: Komprimierungsstufe 1-22 setzen (Erstellungsbefehl)
- `--streaming`: Streaming-Modus für speichereffiziente Verarbeitung aktivieren
- `--filter FILTER`: Sicherheitsfilter für Extraktion (data/tar/fully_trusted)
- `--no-atomic`: Atomare Dateioperationen deaktivieren (nicht empfohlen)
### 🔒 Sicherheitsfilter
```bash
# Mit maximaler Sicherheit extrahieren (Standard)
tzst x archive.tzst --filter data
# Mit Standard-Tar-Kompatibilität extrahieren
tzst x archive.tzst --filter tar
# Mit vollem Vertrauen extrahieren (gefährlich - nur für vertrauenswürdige Archive)
tzst x archive.tzst --filter fully_trusted
```
**🔐 Sicherheitsfilter-Optionen:**
- `data` (Standard): Am sichersten. Blockiert gefährliche Dateien, absolute Pfade und Pfade außerhalb des Extraktionsverzeichnisses
- `tar`: Standard-Tar-Kompatibilität. Blockiert absolute Pfade und Verzeichnisdurchquerung
- `fully_trusted`: Keine Sicherheitsbeschränkungen. Nur bei vollständig vertrauenswürdigen Archiven verwenden
## 🐍 Python API
### 📦 TzstArchive Klasse
```python
from tzst import TzstArchive
# Neues Archiv erstellen
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Vorhandenes Archiv lesen
with TzstArchive("archive.tzst", "r") as archive:
# Inhalt auflisten
contents = archive.list(verbose=True)
# Mit Sicherheitsfilter extrahieren
archive.extract("file.txt", "output/", filter="data")
# Integrität testen
is_valid = archive.test()
# Für große Archive, Streaming-Modus verwenden
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ Wichtige Einschränkungen:**
- **❌ Anhängemodus nicht unterstützt**: Erstelle mehrere Archive oder erstelle das gesamte Archiv neu
### 🎯 Convenience-Funktionen
#### 📁 create_archive()
```python
from tzst import create_archive
# Mit atomaren Operationen erstellen (Standard)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# Mit Sicherheit extrahieren (Standard: 'data' Filter)
extract_archive("backup.tzst", "restore/")
# Spezifische Dateien extrahieren
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# Verzeichnisstruktur abflachen
extract_archive("backup.tzst", "restore/", flatten=True)
# Streaming für große Archive verwenden
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# Einfache Auflistung
files = list_archive("backup.tzst")
# Detaillierte Auflistung
files = list_archive("backup.tzst", verbose=True)
# Streaming für große Archive
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# Grundlegende Integritätsprüfung
if test_archive("backup.tzst"):
print("Archiv ist gültig")
# Mit Streaming testen
if test_archive("large_backup.tzst", streaming=True):
print("Großes Archiv ist gültig")
```
## 🔧 Erweiterte Funktionen
### 📂 Dateierweiterungen
Die Bibliothek behandelt Dateierweiterungen automatisch mit intelligenter Normalisierung:
- `.tzst` - Primäre Erweiterung für tar+zstandard Archive
- `.tar.zst` - Alternative Standarderweiterung
- Automatische Erkennung beim Öffnen vorhandener Archive
- Automatisches Hinzufügen von Erweiterungen beim Erstellen von Archiven
```python
# Diese erstellen alle gültige Archive
create_archive("backup.tzst", files) # Erstellt backup.tzst
create_archive("backup.tar.zst", files) # Erstellt backup.tar.zst
create_archive("backup", files) # Erstellt backup.tzst
create_archive("backup.txt", files) # Erstellt backup.tzst (normalisiert)
```
### 🗜️ Komprimierungsstufen
Zstandard-Komprimierungsstufen reichen von 1 (schnellste) bis 22 (beste Komprimierung):
- **Stufe 1-3**: Schnelle Komprimierung, größere Dateien
- **Stufe 3** (Standard): Guter Kompromiss zwischen Geschwindigkeit und Komprimierung
- **Stufe 10-15**: Bessere Komprimierung, langsamer
- **Stufe 20-22**: Maximale Komprimierung, viel langsamer
### 🌊 Streaming-Modus
Verwende den Streaming-Modus für speichereffiziente Verarbeitung großer Archive:
**✅ Vorteile:**
- Deutlich reduzierter Speicherverbrauch
- Bessere Leistung für Archive, die nicht in den Speicher passen
- Automatische Bereinigung von Ressourcen
**🎯 Wann verwenden:**
- Archive größer als 100MB
- Umgebungen mit begrenztem Speicher
- Verarbeitung von Archiven mit vielen großen Dateien
```python
# Beispiel: Verarbeitung eines großen Backup-Archivs
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# Speichereffiziente Operationen
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ Atomare Operationen
Alle Dateierstellungsoperationen verwenden standardmäßig atomare Dateioperationen:
- Archive werden zuerst in temporären Dateien erstellt, dann atomisch verschoben
- Automatische Bereinigung bei Prozessunterbrechung
- Kein Risiko von beschädigten oder unvollständigen Archiven
- Plattformübergreifende Kompatibilität
```python
# Atomare Operationen standardmäßig aktiviert
create_archive("important.tzst", files) # Sicher vor Unterbrechung
# Kann bei Bedarf deaktiviert werden (nicht empfohlen)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 Fehlerbehandlung
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Fehler beim Dekomprimieren des Archivs")
except TzstFileNotFoundError:
print("Archivdatei nicht gefunden")
except KeyboardInterrupt:
print("Operation vom Benutzer unterbrochen")
# Bereinigung wird automatisch durchgeführt
```
## 🚀 Leistung und Vergleich
### 💡 Leistungstipps
1. **🗜️ Komprimierungsstufen**: Stufe 3 ist optimal für die meisten Anwendungsfälle
2. **🌊 Streaming**: Verwende für Archive größer als 100MB
3. **📦 Batch-Operationen**: Füge mehrere Dateien in einer Sitzung hinzu
4. **📄 Dateitypen**: Bereits komprimierte Dateien werden nicht viel weiter komprimiert
### 🆚 vs Andere Tools
**vs tar + gzip:**
- ✅ Bessere Komprimierungsraten
- ⚡ Schnellere Dekomprimierung
- 🔄 Moderner Algorithmus
**vs tar + xz:**
- 🚀 Deutlich schnellere Komprimierung
- 📊 Ähnliche Komprimierungsraten
- ⚖️ Besserer Geschwindigkeit/Komprimierung-Kompromiss
**vs zip:**
- 🗜️ Bessere Komprimierung
- 🔐 Bewahrt Unix-Berechtigungen und Metadaten
- 🌊 Bessere Streaming-Unterstützung
## 📋 Anforderungen
- 🐍 Python 3.12 oder höher
- 📦 zstandard >= 0.19.0
## 🛠️ Entwicklung
### 🚀 Entwicklungsumgebung einrichten
Dieses Projekt verwendet moderne Python-Packaging-Standards:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 Tests ausführen
```bash
# Tests mit Coverage ausführen
pytest --cov=tzst --cov-report=html
# Oder den einfacheren Befehl verwenden (Coverage-Einstellungen sind in pyproject.toml)
pytest
```
### ✨ Code-Qualität
```bash
# Code-Qualität prüfen
ruff check src tests
# Code formatieren
ruff format src tests
```
## 🤝 Beitragen
Wir begrüßen Beiträge! Bitte lies unseren [Beitragsleitfaden](CONTRIBUTING.md) für:
- Entwicklungssetup und Projektstruktur
- Code-Stil-Richtlinien und bewährte Praktiken
- Testanforderungen und Schreibtests
- Pull-Request-Prozess und Review-Workflow
### 🚀 Schnellstart für Mitwirkende
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 Arten willkommener Beiträge
- 🐛 **Fehlerbehebungen** - Probleme in vorhandener Funktionalität beheben
- ✨ **Funktionen** - Neue Fähigkeiten zur Bibliothek hinzufügen
- 📚 **Dokumentation** - Dokumentation verbessern oder hinzufügen
- 🧪 **Tests** - Testabdeckung hinzufügen oder verbessern
- ⚡ **Leistung** - Vorhandenen Code optimieren
- 🔒 **Sicherheit** - Sicherheitsschwachstellen beheben
## 🙏 Danksagungen
- [Meta Zstandard](https://github.com/facebook/zstd) für den exzellenten Komprimierungsalgorithmus
- [python-zstandard](https://github.com/indygreg/python-zstandard) für Python-Bindings
- Der Python-Community für Inspiration und Feedback
## 📄 Lizenz
Urheberrecht &copy; 2025 [Xi Xu](https://xi-xu.me). Alle Rechte vorbehalten.
Lizenziert unter der [BSD 3-Clause](LICENSE) Lizenz.
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | **🇪🇸 español** | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** es una biblioteca de Python de próxima generación diseñada para la gestión moderna de archivos, aprovechando la compresión Zstandard de vanguardia para ofrecer un rendimiento, seguridad y fiabilidad superiores. Construida exclusivamente para Python 3.12+, esta solución de nivel empresarial combina operaciones atómicas, eficiencia de transmisión (streaming) y una API meticulosamente elaborada para redefinir cómo los desarrolladores manejan los archivos `.tzst`/`.tar.zst` en entornos de producción. 🚀
## ✨ Características
- **🗜️ Alta Compresión**: Compresión Zstandard para excelentes ratios de compresión y velocidad.
- **📁 Compatibilidad con Tar**: Crea archivos tar estándar comprimidos con Zstandard.
- **💻 Interfaz de Línea de Comandos**: CLI intuitiva con soporte para transmisión y opciones completas.
- **🐍 API de Python**: API limpia y pitónica para uso programático.
- **🌍 Multiplataforma**: Funciona en Windows, macOS y Linux.
- **📂 Múltiples Extensiones**: Soporta las extensiones `.tzst` y `.tar.zst`.
- **💾 Eficiente en Memoria**: Modo de transmisión para manejar archivos grandes con un uso mínimo de memoria.
- **⚡ Operaciones Atómicas**: Operaciones de archivo seguras con limpieza automática en caso de interrupción.
- **🔒 Seguro por Defecto**: Utiliza el filtro 'data' para máxima seguridad durante la extracción.
- **🚨 Manejo de Errores Mejorado**: Mensajes de error claros con alternativas útiles.
## 📥 Instalación
### Desde los Lanzamientos de GitHub
Descarga ejecutables independientes que no requieren instalación de Python:
#### Plataformas Soportadas
| Plataforma | Arquitectura | Archivo |
|--------------|---------------|---------------------------------------|
| **🐧 Linux** | x86_64 | `tzst-v{versión}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{versión}-linux-aarch64.zip` |
| **🪟 Windows**| x64 | `tzst-v{versión}-windows-amd64.zip` |
| **🪟 Windows**| ARM64 | `tzst-v{versión}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{versión}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{versión}-macos-arm64.zip` |
#### 🛠️ Pasos de Instalación
1. **📥 Descarga** el archivo apropiado para tu plataforma desde la [página de lanzamientos más recientes](https://github.com/xixu-me/tzst/releases/latest).
2. **📦 Extrae** el archivo para obtener el ejecutable `tzst` (o `tzst.exe` en Windows).
3. **📂 Mueve** el ejecutable a un directorio en tu PATH:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: Añade el directorio que contiene `tzst.exe` a tu variable de entorno PATH.
4. **✅ Verifica** la instalación: `tzst --help`
#### 🎯 Beneficios de la Instalación Binaria
- ✅ **No requiere Python** - Ejecutable independiente.
- ✅ **Inicio más rápido** - Sin la sobrecarga del intérprete de Python.
- ✅ **Despliegue fácil** - Distribución en un solo archivo.
- ✅ **Comportamiento consistente** - Dependencias incluidas.
### 📦 Desde PyPI
```
pip install tzst
```
### 🔧 Desde el Código Fuente
```
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 Instalación para Desarrollo
Este proyecto utiliza estándares modernos de empaquetado de Python:
```
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 Inicio Rápido
### 💻 Uso desde la Línea de Comandos
> **Nota**: Descarga el [binario independiente](#desde-los-lanzamientos-de-github) para obtener el mejor rendimiento y no depender de Python. Alternativamente, usa `uvx tzst` para ejecutar sin instalación. Consulta la [documentación de uv](https://docs.astral.sh/uv/) para más detalles.
```
# 📁 Crear un archivo
tzst a archivo.tzst archivo1.txt archivo2.txt directorio/
# 📤 Extraer un archivo
tzst x archivo.tzst
# 📋 Listar el contenido del archivo
tzst l archivo.tzst
# 🧪 Probar la integridad del archivo
tzst t archivo.tzst
```
### 🐍 Uso de la API de Python
```
from tzst import create_archive, extract_archive, list_archive
# Crear un archivo
create_archive("archivo.tzst", ["archivo1.txt", "archivo2.txt", "directorio/"])
# Extraer un archivo
extract_archive("archivo.tzst", "directorio_salida/")
# Listar el contenido del archivo
contents = list_archive("archivo.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 Interfaz de Línea de Comandos
### 📁 Operaciones con Archivos
#### ➕ Crear Archivo
```
# Uso básico
tzst a archivo.tzst archivo1.txt archivo2.txt
# Con nivel de compresión (1-22, por defecto: 3)
tzst a archivo.tzst archivos/ -l 15
# Comandos alternativos
tzst add archivo.tzst archivos/
tzst create archivo.tzst archivos/
```
#### 📤 Extraer Archivo
```
# Extraer con la estructura de directorios completa
tzst x archivo.tzst
# Extraer a un directorio específico
tzst x archivo.tzst -o salida/
# Extraer archivos específicos
tzst x archivo.tzst archivo1.txt dir/archivo2.txt
# Extraer sin estructura de directorios (plano)
tzst e archivo.tzst -o salida/
# Usar modo de transmisión para archivos grandes
tzst x archivo.tzst --streaming -o salida/
```
#### 📋 Listar Contenido
```
# Listado simple
tzst l archivo.tzst
# Listado detallado con detalles
tzst l archivo.tzst -v
# Usar modo de transmisión para archivos grandes
tzst l archivo.tzst --streaming -v
```
#### 🧪 Probar Integridad
```
# Probar la integridad del archivo
tzst t archivo.tzst
# Probar con modo de transmisión
tzst t archivo.tzst --streaming
```
### 📊 Referencia de Comandos
| Comando | Alias | Descripción | Soporte de Transmisión |
|---------|--------------------|-------------------------------------------|------------------------|
| `a` | `add`, `create` | Crear o añadir a un archivo | N/A |
| `x` | `extract` | Extraer con rutas completas | ✓ `--streaming` |
| `e` | `extract-flat` | Extraer sin estructura de directorios | ✓ `--streaming` |
| `l` | `list` | Listar el contenido del archivo | ✓ `--streaming` |
| `t` | `test` | Probar la integridad del archivo | ✓ `--streaming` |
### ⚙️ Opciones de CLI
- `-v, --verbose`: Habilitar salida detallada.
- `-o, --output DIR`: Especificar directorio de salida (comandos de extracción).
- `-l, --level NIVEL`: Establecer nivel de compresión 1-22 (comando de creación).
- `--streaming`: Habilitar modo de transmisión para procesamiento eficiente en memoria.
- `--filter FILTRO`: Filtro de seguridad para extracción (data/tar/fully_trusted).
- `--no-atomic`: Deshabilitar operaciones de archivo atómicas (no recomendado).
### 🔒 Filtros de Seguridad
```
# Extraer con máxima seguridad (por defecto)
tzst x archivo.tzst --filter data
# Extraer con compatibilidad estándar de tar
tzst x archivo.tzst --filter tar
# Extraer con confianza total (peligroso - solo para archivos de confianza)
tzst x archivo.tzst --filter fully_trusted
```
**🔐 Opciones de Filtro de Seguridad:**
- `data` (por defecto): El más seguro. Bloquea archivos peligrosos, rutas absolutas y rutas fuera del directorio de extracción.
- `tar`: Compatibilidad estándar con tar. Bloquea rutas absolutas y recorrido de directorios (directory traversal).
- `fully_trusted`: Sin restricciones de seguridad. Usar solo con archivos completamente confiables.
## 🐍 API de Python
### 📦 Clase TzstArchive
```
from tzst import TzstArchive
# Crear un nuevo archivo
with TzstArchive("archivo.tzst", "w", compression_level=5) as archive:
archive.add("archivo.txt")
archive.add("directorio/", recursive=True)
# Leer un archivo existente
with TzstArchive("archivo.tzst", "r") as archive:
# Listar contenido
contents = archive.list(verbose=True)
# Extraer con filtro de seguridad
archive.extract("archivo.txt", "salida/", filter="data")
# Probar integridad
is_valid = archive.test()
# Para archivos grandes, usar modo de transmisión
with TzstArchive("archivo_grande.tzst", "r", streaming=True) as archive:
archive.extract(path="salida/")
```
**⚠️ Limitaciones Importantes:**
- **❌ Modo de Añadir No Soportado**: Crea múltiples archivos o recrea el archivo completo en su lugar.
### 🎯 Funciones de Conveniencia
#### 📁 create_archive()
```
from tzst import create_archive
# Crear con operaciones atómicas (por defecto)
create_archive(
archive_path="backup.tzst",
files=["documentos/", "fotos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```
from tzst import extract_archive
# Extraer con seguridad (por defecto: filtro 'data')
extract_archive("backup.tzst", "restaurar/")
# Extraer archivos específicos
extract_archive("backup.tzst", "restaurar/", members=["config.txt"])
# Aplanar estructura de directorios
extract_archive("backup.tzst", "restaurar/", flatten=True)
# Usar transmisión para archivos grandes
extract_archive("backup_grande.tzst", "restaurar/", streaming=True)
```
#### 📋 list_archive()
```
from tzst import list_archive
# Listado simple
files = list_archive("backup.tzst")
# Listado detallado
files = list_archive("backup.tzst", verbose=True)
# Transmisión para archivos grandes
files = list_archive("backup_grande.tzst", streaming=True)
```
#### 🧪 test_archive()
```
from tzst import test_archive
# Prueba de integridad básica
if test_archive("backup.tzst"):
print("El archivo es válido")
# Prueba con transmisión
if test_archive("backup_grande.tzst", streaming=True):
print("El archivo grande es válido")
```
## 🔧 Características Avanzadas
### 📂 Extensiones de Archivo
La biblioteca maneja automáticamente las extensiones de archivo con normalización inteligente:
- `.tzst` - Extensión principal para archivos tar+zstandard.
- `.tar.zst` - Extensión estándar alternativa.
- Autodetección al abrir archivos existentes.
- Adición automática de extensión al crear archivos.
```
# Todos estos crean archivos válidos
create_archive("backup.tzst", files) # Crea backup.tzst
create_archive("backup.tar.zst", files) # Crea backup.tar.zst
create_archive("backup", files) # Crea backup.tzst
create_archive("backup.txt", files) # Crea backup.tzst (normalizado)
```
### 🗜️ Niveles de Compresión
Los niveles de compresión de Zstandard van de 1 (más rápido) a 22 (mejor compresión):
- **Nivel 1-3**: Compresión rápida, archivos más grandes.
- **Nivel 3** (por defecto): Buen equilibrio entre velocidad y compresión.
- **Nivel 10-15**: Mejor compresión, más lento.
- **Nivel 20-22**: Máxima compresión, mucho más lento.
### 🌊 Modo de Transmisión (Streaming)
Usa el modo de transmisión para el procesamiento eficiente en memoria de archivos grandes:
**✅ Beneficios:**
- Uso de memoria significativamente reducido.
- Mejor rendimiento para archivos que no caben en memoria.
- Limpieza automática de recursos.
**🎯 Cuándo Usar:**
- Archivos mayores de 100MB.
- Entornos con memoria limitada.
- Procesamiento de archivos con muchos archivos grandes.
```
# Ejemplo: Procesando un archivo de copia de seguridad grande
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# Operaciones eficientes en memoria
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ Operaciones Atómicas
Todas las operaciones de creación de archivos utilizan operaciones de archivo atómicas por defecto:
- Los archivos se crean primero en archivos temporales y luego se mueven atómicamente.
- Limpieza automática si el proceso se interrumpe.
- Sin riesgo de archivos corruptos o incompletos.
- Compatibilidad multiplataforma.
```
# Operaciones atómicas habilitadas por defecto
create_archive("importante.tzst", files) # Seguro contra interrupciones
# Se pueden deshabilitar si es necesario (no recomendado)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 Manejo de Errores
```
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archivo.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Falló la descompresión del archivo")
except TzstFileNotFoundError:
print("Archivo no encontrado")
except KeyboardInterrupt:
print("Operación interrumpida por el usuario")
# La limpieza se maneja automáticamente
```
## 🚀 Rendimiento y Comparación
### 💡 Consejos de Rendimiento
1. **🗜️ Niveles de compresión**: El nivel 3 es óptimo para la mayoría de los casos de uso.
2. **🌊 Transmisión**: Usar para archivos mayores de 100MB.
3. **📦 Operaciones por lotes**: Añadir múltiples archivos en una sola sesión.
4. **📄 Tipos de archivo**: Los archivos ya comprimidos no se comprimirán mucho más.
### 🆚 vs Otras Herramientas
**vs tar + gzip:**
- ✅ Mejores ratios de compresión.
- ⚡ Descompresión más rápida.
- 🔄 Algoritmo moderno.
**vs tar + xz:**
- 🚀 Compresión significativamente más rápida.
- 📊 Ratios de compresión similares.
- ⚖️ Mejor equilibrio velocidad/compresión.
**vs zip:**
- 🗜️ Mejor compresión.
- 🔐 Preserva permisos y metadatos de Unix.
- 🌊 Mejor soporte para transmisión.
## 📋 Requisitos
- 🐍 Python 3.12 o superior
- 📦 zstandard >= 0.19.0
## 🛠️ Desarrollo
### 🚀 Configuración del Entorno de Desarrollo
Este proyecto utiliza estándares modernos de empaquetado de Python:
```
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 Ejecución de Pruebas
```
# Ejecutar pruebas con cobertura
pytest --cov=tzst --cov-report=html
# O usar el comando más simple (la configuración de cobertura está en pyproject.toml)
pytest
```
### ✨ Calidad del Código
```
# Comprobar la calidad del código
ruff check src tests
# Formatear el código
ruff format src tests
```
## 🤝 Contribuir
¡Aceptamos contribuciones! Por favor, lee nuestra [Guía de Contribución](CONTRIBUTING.md) para:
- Configuración del desarrollo y estructura del proyecto.
- Directrices de estilo de código y mejores prácticas.
- Requisitos de prueba y escritura de pruebas.
- Proceso de pull request y flujo de trabajo de revisión.
### 🚀 Inicio Rápido para Colaboradores
```
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 Tipos de Contribuciones Bienvenidas
- 🐛 **Corrección de errores** - Soluciona problemas en la funcionalidad existente.
- ✨ **Características** - Añade nuevas capacidades a la biblioteca.
- 📚 **Documentación** - Mejora o añade documentación.
- 🧪 **Pruebas** - Añade o mejora la cobertura de pruebas.
- ⚡ **Rendimiento** - Optimiza el código existente.
- 🔒 **Seguridad** - Aborda vulnerabilidades de seguridad.
## 🙏 Agradecimientos
- [Meta Zstandard](https://github.com/facebook/zstd) por el excelente algoritmo de compresión.
- [python-zstandard](https://github.com/indygreg/python-zstandard) por los bindings de Python.
- La comunidad de Python por la inspiración y los comentarios.
## 📄 Licencia
Copyright &copy; 2025 [Xi Xu](https://xi-xu.me). Todos los derechos reservados.
Licenciado bajo la licencia [BSD 3-Clause](LICENSE).
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | **🇫🇷 français** | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** est une bibliothèque Python de nouvelle génération conçue pour la gestion moderne d'archives, exploitant la compression Zstandard de pointe pour offrir des performances, une sécurité et une fiabilité supérieures. Construite exclusivement pour Python 3.12+, cette solution de niveau entreprise combine des opérations atomiques, l'efficacité du streaming et une API méticuleusement conçue pour redéfinir la façon dont les développeurs gèrent les archives `.tzst`/`.tar.zst` dans les environnements de production. 🚀
## ✨ Fonctionnalités
- **🗜️ Compression élevée** : Compression Zstandard pour d'excellents taux de compression et une vitesse remarquable
- **📁 Compatibilité Tar** : Crée des archives tar standard compressées avec Zstandard
- **💻 Interface en ligne de commande** : CLI intuitive avec support de streaming et options complètes
- **🐍 API Python** : API propre et pythonique pour un usage programmatique
- **🌍 Multi-plateforme** : Fonctionne sur Windows, macOS et Linux
- **📂 Extensions multiples** : Supporte les extensions `.tzst` et `.tar.zst`
- **💾 Efficace en mémoire** : Mode streaming pour gérer de grandes archives avec une utilisation mémoire minimale
- **⚡ Opérations atomiques** : Opérations de fichiers sécurisées avec nettoyage automatique en cas d'interruption
- **🔒 Sécurisé par défaut** : Utilise le filtre 'data' pour une sécurité maximale lors de l'extraction
- **🚨 Gestion d'erreurs améliorée** : Messages d'erreur clairs avec des alternatives utiles
## 📥 Installation
### Depuis les Releases GitHub
Téléchargez des exécutables autonomes qui ne nécessitent pas d'installation Python :
#### Plateformes supportées
| Plateforme | Architecture | Fichier |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{version}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{version}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{version}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{version}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{version}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{version}-macos-arm64.zip` |
#### 🛠️ Étapes d'installation
1. **📥 Téléchargez** l'archive appropriée pour votre plateforme depuis la [page des dernières versions](https://github.com/xixu-me/tzst/releases/latest)
2. **📦 Extrayez** l'archive pour obtenir l'exécutable `tzst` (ou `tzst.exe` sous Windows)
3. **📂 Déplacez** l'exécutable vers un répertoire dans votre PATH :
- **🐧 Linux/macOS** : `sudo mv tzst /usr/local/bin/`
- **🪟 Windows** : Ajoutez le répertoire contenant `tzst.exe` à votre variable d'environnement PATH
4. **✅ Vérifiez** l'installation : `tzst --help`
#### 🎯 Avantages de l'installation binaire
- ✅ **Aucun Python requis** - Exécutable autonome
- ✅ **Démarrage plus rapide** - Aucune surcharge d'interpréteur Python
- ✅ **Déploiement facile** - Distribution en fichier unique
- ✅ **Comportement cohérent** - Dépendances intégrées
### 📦 Depuis PyPI
```bash
pip install tzst
```
### 🔧 Depuis le code source
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 Installation de développement
Ce projet utilise les standards modernes d'empaquetage Python :
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 Démarrage rapide
### 💻 Utilisation en ligne de commande
> **Note** : Téléchargez le [binaire autonome](#depuis-les-releases-github) pour les meilleures performances et aucune dépendance Python. Alternativement, utilisez `uvx tzst` pour exécuter sans installation. Voir la [documentation uv](https://docs.astral.sh/uv/) pour les détails.
```bash
# 📁 Créer une archive
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 Extraire une archive
tzst x archive.tzst
# 📋 Lister le contenu d'une archive
tzst l archive.tzst
# 🧪 Tester l'intégrité d'une archive
tzst t archive.tzst
```
### 🐍 Utilisation de l'API Python
```python
from tzst import create_archive, extract_archive, list_archive
# Créer une archive
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# Extraire une archive
extract_archive("archive.tzst", "output_directory/")
# Lister le contenu d'une archive
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 Interface en ligne de commande
### 📁 Opérations d'archives
#### ➕ Créer une archive
```bash
# Utilisation de base
tzst a archive.tzst file1.txt file2.txt
# Avec niveau de compression (1-22, défaut : 3)
tzst a archive.tzst files/ -l 15
# Commandes alternatives
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 Extraire une archive
```bash
# Extraire avec structure complète des répertoires
tzst x archive.tzst
# Extraire vers un répertoire spécifique
tzst x archive.tzst -o output/
# Extraire des fichiers spécifiques
tzst x archive.tzst file1.txt dir/file2.txt
# Extraire sans structure de répertoires (à plat)
tzst e archive.tzst -o output/
# Utiliser le mode streaming pour de grandes archives
tzst x archive.tzst --streaming -o output/
```
#### 📋 Lister le contenu
```bash
# Liste simple
tzst l archive.tzst
# Liste détaillée avec informations
tzst l archive.tzst -v
# Utiliser le mode streaming pour de grandes archives
tzst l archive.tzst --streaming -v
```
#### 🧪 Tester l'intégrité
```bash
# Tester l'intégrité de l'archive
tzst t archive.tzst
# Tester avec le mode streaming
tzst t archive.tzst --streaming
```
### 📊 Référence des commandes
| Commande | Alias | Description | Support streaming |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Créer ou ajouter à une archive | N/A |
| `x` | `extract` | Extraire avec chemins complets | ✓ `--streaming` |
| `e` | `extract-flat` | Extraire sans structure de répertoires | ✓ `--streaming` |
| `l` | `list` | Lister le contenu de l'archive | ✓ `--streaming` |
| `t` | `test` | Tester l'intégrité de l'archive | ✓ `--streaming` |
### ⚙️ Options CLI
- `-v, --verbose` : Activer la sortie détaillée
- `-o, --output DIR` : Spécifier le répertoire de sortie (commandes d'extraction)
- `-l, --level LEVEL` : Définir le niveau de compression 1-22 (commande de création)
- `--streaming` : Activer le mode streaming pour un traitement efficace en mémoire
- `--filter FILTER` : Filtre de sécurité pour l'extraction (data/tar/fully_trusted)
- `--no-atomic` : Désactiver les opérations de fichiers atomiques (non recommandé)
### 🔒 Filtres de sécurité
```bash
# Extraire avec sécurité maximale (défaut)
tzst x archive.tzst --filter data
# Extraire avec compatibilité tar standard
tzst x archive.tzst --filter tar
# Extraire avec confiance totale (dangereux - uniquement pour les archives de confiance)
tzst x archive.tzst --filter fully_trusted
```
**🔐 Options de filtre de sécurité :**
- `data` (défaut) : Le plus sécurisé. Bloque les fichiers dangereux, les chemins absolus et les chemins en dehors du répertoire d'extraction
- `tar` : Compatibilité tar standard. Bloque les chemins absolus et la traversée de répertoires
- `fully_trusted` : Aucune restriction de sécurité. À utiliser uniquement avec des archives entièrement fiables
## 🐍 API Python
### 📦 Classe TzstArchive
```python
from tzst import TzstArchive
# Créer une nouvelle archive
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Lire une archive existante
with TzstArchive("archive.tzst", "r") as archive:
# Lister le contenu
contents = archive.list(verbose=True)
# Extraire avec filtre de sécurité
archive.extract("file.txt", "output/", filter="data")
# Tester l'intégrité
is_valid = archive.test()
# Pour de grandes archives, utiliser le mode streaming
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ Limitations importantes :**
- **❌ Mode d'ajout non supporté** : Créez plusieurs archives ou recréez l'archive entière à la place
### 🎯 Fonctions de convenance
#### 📁 create_archive()
```python
from tzst import create_archive
# Créer avec opérations atomiques (défaut)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# Extraire avec sécurité (défaut : filtre 'data')
extract_archive("backup.tzst", "restore/")
# Extraire des fichiers spécifiques
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# Aplatir la structure des répertoires
extract_archive("backup.tzst", "restore/", flatten=True)
# Utiliser le streaming pour de grandes archives
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# Liste simple
files = list_archive("backup.tzst")
# Liste détaillée
files = list_archive("backup.tzst", verbose=True)
# Streaming pour de grandes archives
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# Test d'intégrité de base
if test_archive("backup.tzst"):
print("L'archive est valide")
# Tester avec streaming
if test_archive("large_backup.tzst", streaming=True):
print("La grande archive est valide")
```
## 🔧 Fonctionnalités avancées
### 📂 Extensions de fichiers
La bibliothèque gère automatiquement les extensions de fichiers avec normalisation intelligente :
- `.tzst` - Extension principale pour les archives tar+zstandard
- `.tar.zst` - Extension standard alternative
- Détection automatique lors de l'ouverture d'archives existantes
- Ajout automatique d'extension lors de la création d'archives
```python
# Toutes ces créent des archives valides
create_archive("backup.tzst", files) # Crée backup.tzst
create_archive("backup.tar.zst", files) # Crée backup.tar.zst
create_archive("backup", files) # Crée backup.tzst
create_archive("backup.txt", files) # Crée backup.tzst (normalisé)
```
### 🗜️ Niveaux de compression
Les niveaux de compression Zstandard vont de 1 (le plus rapide) à 22 (meilleure compression) :
- **Niveau 1-3** : Compression rapide, fichiers plus volumineux
- **Niveau 3** (défaut) : Bon équilibre entre vitesse et compression
- **Niveau 10-15** : Meilleure compression, plus lent
- **Niveau 20-22** : Compression maximale, beaucoup plus lent
### 🌊 Mode streaming
Utilisez le mode streaming pour un traitement efficace en mémoire de grandes archives :
**✅ Avantages :**
- Utilisation mémoire considérablement réduite
- Meilleures performances pour les archives qui ne tiennent pas en mémoire
- Nettoyage automatique des ressources
**🎯 Quand utiliser :**
- Archives supérieures à 100MB
- Environnements à mémoire limitée
- Traitement d'archives avec de nombreux gros fichiers
```python
# Exemple : Traitement d'une grande archive de sauvegarde
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# Opérations efficaces en mémoire
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ Opérations atomiques
Toutes les opérations de création de fichiers utilisent des opérations de fichiers atomiques par défaut :
- Archives créées dans des fichiers temporaires d'abord, puis déplacées atomiquement
- Nettoyage automatique si le processus est interrompu
- Aucun risque d'archives corrompues ou incomplètes
- Compatibilité multi-plateforme
```python
# Opérations atomiques activées par défaut
create_archive("important.tzst", files) # Sûr contre les interruptions
# Peut être désactivé si nécessaire (non recommandé)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 Gestion des erreurs
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Échec de la décompression de l'archive")
except TzstFileNotFoundError:
print("Fichier d'archive non trouvé")
except KeyboardInterrupt:
print("Opération interrompue par l'utilisateur")
# Le nettoyage est géré automatiquement
```
## 🚀 Performance et comparaison
### 💡 Conseils de performance
1. **🗜️ Niveaux de compression** : Le niveau 3 est optimal pour la plupart des cas d'usage
2. **🌊 Streaming** : Utilisez pour les archives supérieures à 100MB
3. **📦 Opérations par lots** : Ajoutez plusieurs fichiers en une seule session
4. **📄 Types de fichiers** : Les fichiers déjà compressés ne se compresseront pas beaucoup plus
### 🆚 vs Autres outils
**vs tar + gzip :**
- ✅ Meilleurs taux de compression
- ⚡ Décompression plus rapide
- 🔄 Algorithme moderne
**vs tar + xz :**
- 🚀 Compression significativement plus rapide
- 📊 Taux de compression similaires
- ⚖️ Meilleur compromis vitesse/compression
**vs zip :**
- 🗜️ Meilleure compression
- 🔐 Préserve les permissions Unix et métadonnées
- 🌊 Meilleur support de streaming
## 📋 Exigences
- 🐍 Python 3.12 ou supérieur
- 📦 zstandard >= 0.19.0
## 🛠️ Développement
### 🚀 Configuration de l'environnement de développement
Ce projet utilise les standards modernes d'empaquetage Python :
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 Exécution des tests
```bash
# Exécuter les tests avec couverture
pytest --cov=tzst --cov-report=html
# Ou utiliser la commande plus simple (paramètres de couverture dans pyproject.toml)
pytest
```
### ✨ Qualité du code
```bash
# Vérifier la qualité du code
ruff check src tests
# Formater le code
ruff format src tests
```
## 🤝 Contribution
Nous accueillons les contributions ! Veuillez lire notre [Guide de contribution](CONTRIBUTING.md) pour :
- Configuration de développement et structure du projet
- Directives de style de code et meilleures pratiques
- Exigences de test et écriture de tests
- Processus de pull request et workflow de révision
### 🚀 Démarrage rapide pour les contributeurs
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 Types de contributions bienvenues
- 🐛 **Corrections de bugs** - Corriger les problèmes dans la fonctionnalité existante
- ✨ **Fonctionnalités** - Ajouter de nouvelles capacités à la bibliothèque
- 📚 **Documentation** - Améliorer ou ajouter de la documentation
- 🧪 **Tests** - Ajouter ou améliorer la couverture de tests
- ⚡ **Performance** - Optimiser le code existant
- 🔒 **Sécurité** - Traiter les vulnérabilités de sécurité
## 🙏 Remerciements
- [Meta Zstandard](https://github.com/facebook/zstd) pour l'excellent algorithme de compression
- [python-zstandard](https://github.com/indygreg/python-zstandard) pour les liaisons Python
- La communauté Python pour l'inspiration et les retours
## 📄 Licence
Droits d'auteur &copy; 2025 [Xi Xu](https://xi-xu.me). Tous droits réservés.
Sous licence [BSD 3-Clause](LICENSE).
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | **🇯🇵 日本語** | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** は、最新の Zstandard 圧縮技術を活用した次世代 Python ライブラリで、優れたパフォーマンス、セキュリティ、信頼性を提供するモダンなアーカイブ管理を実現します。 Python 3.12+ 専用に構築されたこのエンタープライズグレードのソリューションは、アトミック操作、ストリーミング効率、厳密に設計された API を組み合わせ、本番環境における `.tzst` / `.tar.zst` アーカイブの扱い方を再定義します。 🚀
## ✨ 特徴
- **🗜️ 高圧縮率**: Zstandard 圧縮による優れた圧縮率と速度
- **📁 Tar 互換性**: Zstandard で圧縮された標準 tar アーカイブを作成
- **💻 コマンドラインインターフェース**: ストリーミング対応の直感的な CLI と包括的なオプション
- **🐍 Python API**: プログラム利用のためのクリーンで Pythonic な API
- **🌍 クロスプラットフォーム**: Windows 、 macOS 、 Linux で動作
- **📂 複数拡張子対応**: `.tzst` と `.tar.zst` の両方の拡張子をサポート
- **💾 メモリ効率**: 大容量アーカイブを最小メモリ使用量で処理するストリーミングモード
- **⚡ アトミック操作**: 中断時にも安全な自動クリーンアップ付きファイル操作
- **🔒 デフォルトで安全**: 展開時の最大セキュリティのために「data」フィルタを使用
- **🚨 強化されたエラーハンドリング**: 代替案を示す明確なエラーメッセージ
## 📥 インストール
### GitHub リリースから
Python インストール不要のスタンドアロン実行ファイルをダウンロード:
#### サポート対象プラットフォーム
| プラットフォーム | アーキテクチャ | ファイル |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{バージョン}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{バージョン}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{バージョン}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{バージョン}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{バージョン}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{バージョン}-macos-arm64.zip` |
#### 🛠️ インストール手順
1. **📥 ダウンロード**: [最新リリースページ](https://github.com/xixu-me/tzst/releases/latest)からお使いのプラットフォームに合ったアーカイブをダウンロード
2. **📦 展開**: アーカイブを展開し、 `tzst` 実行ファイル(Windows の場合は `tzst.exe` )を取得
3. **📂 移動**: 実行ファイルを PATH が通ったディレクトリに移動:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: `tzst.exe` を含むディレクトリを PATH 環境変数に追加
4. **✅ 確認**: インストールを検証: `tzst --help`
#### 🎯 バイナリインストールの利点
- ✅ **Python 不要** - スタンドアロン実行ファイル
- ✅ **高速起動** - Python インタプリタのオーバーヘッドなし
- ✅ **簡単なデプロイ** - 単一ファイル配布
- ✅ **一貫した動作** - 依存関係をバンドル
### 📦 PyPI から
```bash
pip install tzst
```
### 🔧 ソースから
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 開発用インストール
このプロジェクトはモダンな Python パッケージング標準を使用します:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 クイックスタート
### 💻 コマンドラインの使い方
> **注**: 最高のパフォーマンスと Python 依存なしを実現するには[スタンドアロンバイナリ](#github-リリースから)をダウンロードしてください。または、インストールなしで実行するには `uvx tzst` を使用します。詳細は [uv ドキュメント](https://docs.astral.sh/uv/)を参照。
```bash
# 📁 アーカイブ作成
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 アーカイブ展開
tzst x archive.tzst
# 📋 アーカイブ内容一覧
tzst l archive.tzst
# 🧪 アーカイブ整合性テスト
tzst t archive.tzst
```
### 🐍 Python API の使い方
```python
from tzst import create_archive, extract_archive, list_archive
# アーカイブ作成
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# アーカイブ展開
extract_archive("archive.tzst", "output_directory/")
# アーカイブ内容一覧
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 コマンドラインインターフェース
### 📁 アーカイブ操作
#### ➕ アーカイブ作成
```bash
# 基本使用法
tzst a archive.tzst file1.txt file2.txt
# 圧縮レベル指定 (1-22, デフォルト: 3)
tzst a archive.tzst files/ -l 15
# 代替コマンド
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 アーカイブ展開
```bash
# 完全なディレクトリ構造で展開
tzst x archive.tzst
# 特定ディレクトリに展開
tzst x archive.tzst -o output/
# 特定ファイルのみ展開
tzst x archive.tzst file1.txt dir/file2.txt
# ディレクトリ構造なしで展開 (フラット)
tzst e archive.tzst -o output/
# 大容量アーカイブ用ストリーミングモード
tzst x archive.tzst --streaming -o output/
```
#### 📋 内容一覧
```bash
# シンプルな一覧表示
tzst l archive.tzst
# 詳細情報付き一覧表示
tzst l archive.tzst -v
# 大容量アーカイブ用ストリーミングモード
tzst l archive.tzst --streaming -v
```
#### 🧪 整合性テスト
```bash
# アーカイブ整合性テスト
tzst t archive.tzst
# ストリーミングモードでテスト
tzst t archive.tzst --streaming
```
### 📊 コマンドリファレンス
| コマンド | エイリアス | 説明 | ストリーミングサポート |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | アーカイブ作成または追加 | N/A |
| `x` | `extract` | 完全パスで展開 | ✓ `--streaming` |
| `e` | `extract-flat` | ディレクトリ構造なしで展開 | ✓ `--streaming` |
| `l` | `list` | アーカイブ内容一覧 | ✓ `--streaming` |
| `t` | `test` | アーカイブ整合性テスト | ✓ `--streaming` |
### ⚙️ CLI オプション
- `-v, --verbose`: 詳細出力を有効化
- `-o, --output DIR`: 出力ディレクトリ指定 (展開コマンド)
- `-l, --level LEVEL`: 圧縮レベル設定 1-22 (作成コマンド)
- `--streaming`: メモリ効率処理のためのストリーミングモードを有効化
- `--filter FILTER`: 展開用セキュリティフィルタ (data/tar/fully_trusted)
- `--no-atomic`: アトミックファイル操作を無効化 (非推奨)
### 🔒 セキュリティフィルタ
```bash
# 最大セキュリティで展開 (デフォルト)
tzst x archive.tzst --filter data
# 標準 tar 互換で展開
tzst x archive.tzst --filter tar
# 完全信頼で展開 (危険 - 信頼済みアーカイブ専用)
tzst x archive.tzst --filter fully_trusted
```
**🔐 セキュリティフィルタオプション:**
- `data` (デフォルト): 最強のセキュリティ。危険なファイル、絶対パス、展開ディレクトリ外のパスをブロック
- `tar`: 標準 tar 互換。絶対パスとディレクトリトラバーサルをブロック
- `fully_trusted`: セキュリティ制限なし。完全に信頼できるアーカイブ専用
## 🐍 Python API
### 📦 TzstArchive クラス
```python
from tzst import TzstArchive
# 新規アーカイブ作成
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# 既存アーカイブ読み込み
with TzstArchive("archive.tzst", "r") as archive:
# 内容一覧
contents = archive.list(verbose=True)
# セキュリティフィルタ付き展開
archive.extract("file.txt", "output/", filter="data")
# 整合性テスト
is_valid = archive.test()
# 大容量アーカイブ用ストリーミングモード
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ 重要な制限事項:**
- **❌ 追加モード非対応**: 複数アーカイブを作成するか、アーカイブ全体を再作成してください
### 🎯 便利関数
#### 📁 create_archive()
```python
from tzst import create_archive
# アトミック操作で作成 (デフォルト)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# セキュリティ付き展開 (デフォルト: 'data' フィルタ)
extract_archive("backup.tzst", "restore/")
# 特定ファイルのみ展開
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# ディレクトリ構造をフラット化
extract_archive("backup.tzst", "restore/", flatten=True)
# 大容量アーカイブ用ストリーミングモード
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# シンプルな一覧
files = list_archive("backup.tzst")
# 詳細一覧
files = list_archive("backup.tzst", verbose=True)
# 大容量アーカイブ用ストリーミングモード
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# 基本的な整合性テスト
if test_archive("backup.tzst"):
print("アーカイブは有効です")
# ストリーミングでテスト
if test_archive("large_backup.tzst", streaming=True):
print("大容量アーカイブは有効です")
```
## 🔧 高度な機能
### 📂 ファイル拡張子
ライブラリはインテリジェントな正規化でファイル拡張子を自動処理:
- `.tzst` - tar + zstandard アーカイブの主要拡張子
- `.tar.zst` - 代替標準拡張子
- 既存アーカイブを開く際の自動検出
- アーカイブ作成時の自動拡張子追加
```python
# すべて有効なアーカイブを作成
create_archive("backup.tzst", files) # backup.tzst を作成
create_archive("backup.tar.zst", files) # backup.tar.zst を作成
create_archive("backup", files) # backup.tzst を作成
create_archive("backup.txt", files) # backup.tzst を作成 (正規化)
```
### 🗜️ 圧縮レベル
Zstandard 圧縮レベルは 1 (最速) から 22 (最高圧縮) の範囲:
- **レベル 1-3**: 高速圧縮、ファイルサイズ大
- **レベル 3** (デフォルト): 速度と圧縮率の良いバランス
- **レベル 10-15**: 高圧縮、低速
- **レベル 20-22**: 最高圧縮、大幅に低速
### 🌊 ストリーミングモード
大容量アーカイブのメモリ効率処理にストリーミングモードを使用:
**✅ 利点:**
- メモリ使用量の大幅削減
- メモリに収まらないアーカイブのパフォーマンス向上
- リソースの自動クリーンアップ
**🎯 使用推奨ケース:**
- 100 MB を超えるアーカイブ
- メモリ制限環境
- 多数の大容量ファイルを含むアーカイブ処理
```python
# 例: 大容量バックアップアーカイブ処理
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# メモリ効率の良い操作
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ アトミック操作
すべてのファイル作成操作はデフォルトでアトミックファイル操作を使用:
- 一時ファイルでアーカイブ作成後、アトミック移動
- プロセス中断時の自動クリーンアップ
- 破損/不完全なアーカイブのリスクなし
- クロスプラットフォーム互換性
```python
# デフォルトでアトミック操作有効
create_archive("important.tzst", files) # 中断から安全
# 必要時に無効化可能 (非推奨)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 エラーハンドリング
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("アーカイブの解凍に失敗しました")
except TzstFileNotFoundError:
print("アーカイブファイルが見つかりません")
except KeyboardInterrupt:
print("ユーザーにより操作中断")
# クリーンアップは自動処理
```
## 🚀 パフォーマンスと比較
### 💡 パフォーマンスのヒント
1. **🗜️ 圧縮レベル**: ほとんどのユースケースでレベル 3 が最適
2. **🌊 ストリーミング**: 100 MB を超えるアーカイブで使用
3. **📦 バッチ操作**: 単一セッションで複数ファイル追加
4. **📄 ファイルタイプ**: 既に圧縮されたファイルはそれ以上圧縮されない
### 🆚 他のツールとの比較
**vs tar + gzip:**
- ✅ より高い圧縮率
- ⚡ 高速な解凍
- 🔄 モダンなアルゴリズム
**vs tar + xz:**
- 🚀 大幅に高速な圧縮
- 📊 同等の圧縮率
- ⚖️ 速度/圧縮率のトレードオフが優れる
**vs zip:**
- 🗜️ より高い圧縮率
- 🔐 Unix 権限とメタデータを保持
- 🌊 優れたストリーミングサポート
## 📋 要件
- 🐍 Python 3.12 以上
- 📦 zstandard >= 0.19.0
## 🛠️ 開発
### 🚀 開発環境セットアップ
このプロジェクトはモダンな Python パッケージング標準を使用:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 テスト実行
```bash
# カバレッジ付きテスト実行
pytest --cov=tzst --cov-report=html
# またはシンプルなコマンド (カバレッジ設定は pyproject.toml 内)
pytest
```
### ✨ コード品質
```bash
# コード品質チェック
ruff check src tests
# コードフォーマット
ruff format src tests
```
## 🤝 貢献
貢献を歓迎します!以下の内容については[貢献ガイド](CONTRIBUTING.md)をお読みください:
- 開発セットアップとプロジェクト構造
- コードスタイルガイドラインとベストプラクティス
- テスト要件とテスト作成
- プルリクエストプロセスとレビューワークフロー
### 🚀 貢献者向けクイックスタート
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 歓迎する貢献の種類
- 🐛 **バグ修正** - 既存機能の問題修正
- ✨ **機能** - ライブラリへの新機能追加
- 📚 **ドキュメント** - ドキュメントの改善・追加
- 🧪 **テスト** - テストカバレッジの追加・改善
- ⚡ **パフォーマンス** - 既存コードの最適化
- 🔒 **セキュリティ** - セキュリティ脆弱性への対応
## 🙏 謝辞
- [Meta Zstandard](https://github.com/facebook/zstd) - 優れた圧縮アルゴリズム
- [python-zstandard](https://github.com/indygreg/python-zstandard) - Python バインディング
- インスピレーションとフィードバックを提供した Python コミュニティ
## 📄 ライセンス
著作権 &copy; 2025 [Xi Xu](https://xi-xu.me)。全著作権を保留します。
[BSD 3-Clause](LICENSE) ライセンスのもとで公開されています。
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | **🇰🇷 한국어** | [🇧🇷 português](./README.pt.md)
**tzst**는 최신 Zstandard 압축 기술을 활용하여 우수한 성능, 보안 및 신뢰성을 제공하는 차세대 Python 라이브러리입니다. Python 3.12+ 전용으로 제작된 이 엔터프라이즈급 솔루션은 원자적 작업, 스트리밍 효율성 및 정교하게 설계된 API를 결합하여 `.tzst`/`.tar.zst` 아카이브를 프로덕션 환경에서 처리하는 방식을 재정의합니다. 🚀
## ✨ 기능
- **🗜️ 고압축률**: 우수한 압축률과 속도를 위한 Zstandard 압축
- **📁 Tar 호환성**: Zstandard로 압축된 표준 tar 아카이브 생성
- **💻 명령줄 인터페이스**: 스트리밍 지원과 포괄적인 옵션을 갖춘 직관적인 CLI
- **🐍 Python API**: 프로그램적 사용을 위한 깔끔하고 Python 스타일의 API
- **🌍 크로스 플랫폼**: Windows, macOS, Linux에서 작동
- **📂 다중 확장자**: `.tzst` 및 `.tar.zst` 확장자 모두 지원
- **💾 메모리 효율적**: 최소 메모리 사용으로 대용량 아카이브 처리 가능한 스트리밍 모드
- **⚡ 원자적 작업**: 중단 시 자동 정리 기능을 통한 안전한 파일 작업
- **🔒 기본 보안**: 추출 시 최대 보안을 위해 'data' 필터 사용
- **🚨 향상된 오류 처리**: 유용한 대안 제시와 함께 명확한 오류 메시지
## 📥 설치
### GitHub 릴리스에서
Python 설치가 필요 없는 독립형 실행 파일 다운로드:
#### 지원 플랫폼
| 플랫폼 | 아키텍처 | 파일 |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{버전}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{버전}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{버전}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{버전}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{버전}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{버전}-macos-arm64.zip` |
#### 🛠️ 설치 단계
1. [최신 릴리스 페이지](https://github.com/xixu-me/tzst/releases/latest)에서 플랫폼에 맞는 아카이브 **📥 다운로드**
2. 아카이브를 **📦 추출**하여 `tzst` 실행 파일 획득 (Windows는 `tzst.exe`)
3. 실행 파일을 PATH 환경 변수 디렉터리로 **📂 이동**:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: `tzst.exe`가 포함된 디렉터리를 PATH 환경 변수에 추가
4. 설치 **✅ 확인**: `tzst --help`
#### 🎯 바이너리 설치의 장점
- ✅ **Python 불필요** - 독립형 실행 파일
- ✅ **빠른 시작** - Python 인터프리터 오버헤드 없음
- ✅ **쉬운 배포** - 단일 파일 배포
- ✅ **일관된 동작** - 번들링된 의존성
### 📦 PyPI에서
```bash
pip install tzst
```
### 🔧 소스에서
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 개발 설치
최신 Python 패키징 표준 사용:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 빠른 시작
### 💻 명령줄 사용법
> **참고**: 최상의 성능과 Python 의존성 없이 사용하려면 [독립형 바이너리](#github-릴리스에서)를 다운로드하세요. 또는 설치 없이 실행하려면 `uvx tzst`를 사용하세요. 자세한 내용은 [uv 문서](https://docs.astral.sh/uv/) 참조.
```bash
# 📁 아카이브 생성
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 아카이브 추출
tzst x archive.tzst
# 📋 아카이브 내용 목록
tzst l archive.tzst
# 🧪 아카이브 무결성 테스트
tzst t archive.tzst
```
### 🐍 Python API 사용법
```python
from tzst import create_archive, extract_archive, list_archive
# 아카이브 생성
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# 아카이브 추출
extract_archive("archive.tzst", "output_directory/")
# 아카이브 내용 목록
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 명령줄 인터페이스
### 📁 아카이브 작업
#### ➕ 아카이브 생성
```bash
# 기본 사용법
tzst a archive.tzst file1.txt file2.txt
# 압축 레벨 지정 (1-22, 기본값: 3)
tzst a archive.tzst files/ -l 15
# 대체 명령어
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 아카이브 추출
```bash
# 전체 디렉터리 구조 유지하며 추출
tzst x archive.tzst
# 특정 디렉터리로 추출
tzst x archive.tzst -o output/
# 특정 파일 추출
tzst x archive.tzst file1.txt dir/file2.txt
# 디렉터리 구조 없이 추출 (플랫)
tzst e archive.tzst -o output/
# 대용량 아카이브에 스트리밍 모드 사용
tzst x archive.tzst --streaming -o output/
```
#### 📋 내용 목록
```bash
# 간단한 목록
tzst l archive.tzst
# 상세 정보 포함 목록
tzst l archive.tzst -v
# 대용량 아카이브에 스트리밍 모드 사용
tzst l archive.tzst --streaming -v
```
#### 🧪 무결성 테스트
```bash
# 아카이브 무결성 테스트
tzst t archive.tzst
# 스트리밍 모드로 테스트
tzst t archive.tzst --streaming
```
### 📊 명령어 참조
| 명령어 | 별칭 | 설명 | 스트리밍 지원 |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | 아카이브 생성 또는 추가 | N/A |
| `x` | `extract` | 전체 경로로 추출 | ✓ `--streaming` |
| `e` | `extract-flat` | 디렉터리 구조 없이 추출 | ✓ `--streaming` |
| `l` | `list` | 아카이브 내용 목록 | ✓ `--streaming` |
| `t` | `test` | 아카이브 무결성 테스트 | ✓ `--streaming` |
### ⚙️ CLI 옵션
- `-v, --verbose`: 상세 출력 활성화
- `-o, --output DIR`: 출력 디렉터리 지정 (추출 명령어)
- `-l, --level LEVEL`: 압축 레벨 1-22 설정 (생성 명령어)
- `--streaming`: 메모리 효율적 처리를 위한 스트리밍 모드 활성화
- `--filter FILTER`: 추출을 위한 보안 필터 (data/tar/fully_trusted)
- `--no-atomic`: 원자적 파일 작업 비활성화 (권장하지 않음)
### 🔒 보안 필터
```bash
# 최대 보안으로 추출 (기본값)
tzst x archive.tzst --filter data
# 표준 tar 호환성으로 추출
tzst x archive.tzst --filter tar
# 완전 신뢰 모드로 추출 (위험 - 신뢰할 수 있는 아카이브 전용)
tzst x archive.tzst --filter fully_trusted
```
**🔐 보안 필터 옵션:**
- `data` (기본값): 가장 안전. 위험한 파일, 절대 경로, 추출 디렉터리 외부 경로 차단
- `tar`: 표준 tar 호환성. 절대 경로 및 디렉터리 순회 차단
- `fully_trusted`: 보안 제한 없음. 완전히 신뢰할 수 있는 아카이브에서만 사용
## 🐍 Python API
### 📦 TzstArchive 클래스
```python
from tzst import TzstArchive
# 새 아카이브 생성
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# 기존 아카이브 읽기
with TzstArchive("archive.tzst", "r") as archive:
# 내용 목록
contents = archive.list(verbose=True)
# 보안 필터 적용 추출
archive.extract("file.txt", "output/", filter="data")
# 무결성 테스트
is_valid = archive.test()
# 대용량 아카이브에 스트리밍 모드 사용
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ 중요한 제한 사항:**
- **❌ 추가 모드 미지원**: 여러 아카이브 생성 또는 전체 아카이브 재생성 필요
### 🎯 편의 함수
#### 📁 create_archive()
```python
from tzst import create_archive
# 원자적 작업으로 생성 (기본값)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# 보안 추출 (기본값: 'data' 필터)
extract_archive("backup.tzst", "restore/")
# 특정 파일 추출
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# 디렉터리 구조 평탄화
extract_archive("backup.tzst", "restore/", flatten=True)
# 대용량 아카이브에 스트리밍 사용
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# 간단한 목록
files = list_archive("backup.tzst")
# 상세 목록
files = list_archive("backup.tzst", verbose=True)
# 대용량 아카이브에 스트리밍 사용
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# 기본 무결성 테스트
if test_archive("backup.tzst"):
print("아카이브가 유효합니다")
# 스트리밍으로 테스트
if test_archive("large_backup.tzst", streaming=True):
print("대용량 아카이브가 유효합니다")
```
## 🔧 고급 기능
### 📂 파일 확장자
라이브러리는 지능적인 정규화로 파일 확장자를 자동 처리합니다:
- `.tzst` - tar+zstandard 아카이브의 기본 확장자
- `.tar.zst` - 대체 표준 확장자
- 기존 아카이브 열 때 자동 감지
- 아카이브 생성 시 자동 확장자 추가
```python
# 모두 유효한 아카이브 생성
create_archive("backup.tzst", files) # backup.tzst 생성
create_archive("backup.tar.zst", files) # backup.tar.zst 생성
create_archive("backup", files) # backup.tzst 생성
create_archive("backup.txt", files) # backup.tzst 생성 (정규화됨)
```
### 🗜️ 압축 레벨
Zstandard 압축 레벨 범위: 1 (가장 빠름) ~ 22 (최대 압축):
- **레벨 1-3**: 빠른 압축, 파일 크기 큼
- **레벨 3** (기본값): 속도와 압축률의 균형
- **레벨 10-15**: 더 나은 압축, 느림
- **레벨 20-22**: 최대 압축, 매우 느림
### 🌊 스트리밍 모드
대용량 아카이브의 메모리 효율적 처리를 위해 스트리밍 모드 사용:
**✅ 장점:**
- 메모리 사용량 현저히 감소
- 메모리에 맞지 않는 대용량 아카이브 처리 성능 향상
- 리소스 자동 정리
**🎯 사용 시기:**
- 100MB 이상의 아카이브
- 메모리가 제한된 환경
- 대용량 파일이 많은 아카이브 처리
```python
# 예제: 대용량 백업 아카이브 처리
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# 메모리 효율적 작업
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ 원자적 작업
모든 파일 생성 작업은 기본적으로 원자적 파일 작업을 사용합니다:
- 임시 파일에 먼저 생성 후 원자적 이동
- 프로세스 중단 시 자동 정리
- 손상되거나 불완전한 아카이브 위험 없음
- 크로스 플랫폼 호환성
```python
# 기본적으로 원자적 작업 활성화
create_archive("important.tzst", files) # 중단으로부터 안전
# 필요한 경우 비활성화 가능 (권장하지 않음)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 오류 처리
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("아카이브 압축 해제 실패")
except TzstFileNotFoundError:
print("아카이브 파일을 찾을 수 없음")
except KeyboardInterrupt:
print("사용자에 의해 작업 중단됨")
# 자동으로 정리됨
```
## 🚀 성능 및 비교
### 💡 성능 팁
1. **🗜️ 압축 레벨**: 대부분의 경우 레벨 3이 최적
2. **🌊 스트리밍**: 100MB 이상 아카이브에 사용
3. **📦 일괄 작업**: 단일 세션에서 여러 파일 추가
4. **📄 파일 유형**: 이미 압축된 파일은 추가 압축이 거의 안됨
### 🆚 다른 도구와 비교
**vs tar + gzip:**
- ✅ 더 나은 압축률
- ⚡ 더 빠른 압축 해제
- 🔄 현대적인 알고리즘
**vs tar + xz:**
- 🚀 현저히 빠른 압축
- 📊 유사한 압축률
- ⚖️ 더 나은 속도/압축률 균형
**vs zip:**
- 🗜️ 더 나은 압축
- 🔐 Unix 권한 및 메타데이터 보존
- 🌊 더 나은 스트리밍 지원
## 📋 요구 사항
- 🐍 Python 3.12 이상
- 📦 zstandard >= 0.19.0
## 🛠️ 개발
### 🚀 개발 환경 설정
최신 Python 패키징 표준 사용:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 테스트 실행
```bash
# 커버리지 포함 테스트 실행
pytest --cov=tzst --cov-report=html
# 또는 간단한 명령어 사용 (커버리지 설정은 pyproject.toml에 있음)
pytest
```
### ✨ 코드 품질
```bash
# 코드 품질 확인
ruff check src tests
# 코드 포맷팅
ruff format src tests
```
## 🤝 기여
기여를 환영합니다! 다음 사항을 위해 [기여 가이드](CONTRIBUTING.md)를 읽어주세요:
- 개발 설정 및 프로젝트 구조
- 코드 스타일 가이드라인 및 모범 사례
- 테스트 요구 사항 및 테스트 작성 방법
- 풀 리퀘스트 프로세스 및 리뷰 워크플로
### 🚀 기여자 빠른 시작
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 환영하는 기여 유형
- 🐛 **버그 수정** - 기존 기능의 문제 해결
- ✨ **기능** - 라이브러리에 새로운 기능 추가
- 📚 **문서** - 문서 개선 또는 추가
- 🧪 **테스트** - 테스트 커버리지 추가 또는 개선
- ⚡ **성능** - 기존 코드 최적화
- 🔒 **보안** - 보안 취약점 해결
## 🙏 감사의 말
- 우수한 압축 알고리즘을 제공한 [Meta Zstandard](https://github.com/facebook/zstd)
- Python 바인딩을 제공한 [python-zstandard](https://github.com/indygreg/python-zstandard)
- 영감과 피드백을 준 Python 커뮤니티
## 📄 라이선스
저작권 &copy; 2025 [시 쉬](https://xi-xu.me). 모든 권리 보유.
[BSD 3-Clause](LICENSE) 라이선스로 사용이 허가되었습니다.
+124 -102
View File
@@ -1,36 +1,73 @@
# tzst
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
**tzst** is a next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability. Built exclusively for Python 3.12+, this enterprise-grade solution combines atomic operations, streaming efficiency, and a meticulously crafted API to redefine how developers handle `.tzst`/`.tar.zst` archives in production environments.
**🇺🇸 English** | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
## Features
**tzst** is a next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability. Built exclusively for Python 3.12+, this enterprise-grade solution combines atomic operations, streaming efficiency, and a meticulously crafted API to redefine how developers handle `.tzst`/`.tar.zst` archives in production environments. 🚀
- **High Compression**: Zstandard compression for excellent compression ratios and speed
- **Tar Compatibility**: Creates standard tar archives compressed with Zstandard
- **Command Line Interface**: Intuitive CLI with streaming support and comprehensive options
- **Python API**: Clean, Pythonic API for programmatic use
- **Cross-Platform**: Works on Windows, macOS, and Linux
- **Multiple Extensions**: Supports both `.tzst` and `.tar.zst` extensions
- **Memory Efficient**: Streaming mode for handling large archives with minimal memory usage
- **Atomic Operations**: Safe file operations with automatic cleanup on interruption
- **Secure by Default**: Uses the 'data' filter for maximum security during extraction
- **Enhanced Error Handling**: Clear error messages with helpful alternatives
## ✨ Features
## Installation
- **🗜️ High Compression**: Zstandard compression for excellent compression ratios and speed
- **📁 Tar Compatibility**: Creates standard tar archives compressed with Zstandard
- **💻 Command Line Interface**: Intuitive CLI with streaming support and comprehensive options
- **🐍 Python API**: Clean, Pythonic API for programmatic use
- **🌍 Cross-Platform**: Works on Windows, macOS, and Linux
- **📂 Multiple Extensions**: Supports both `.tzst` and `.tar.zst` extensions
- **💾 Memory Efficient**: Streaming mode for handling large archives with minimal memory usage
- **⚡ Atomic Operations**: Safe file operations with automatic cleanup on interruption
- **🔒 Secure by Default**: Uses the 'data' filter for maximum security during extraction
- **🚨 Enhanced Error Handling**: Clear error messages with helpful alternatives
### From PyPI
## 📥 Installation
### From GitHub Releases
Download standalone executables that don't require Python installation:
#### Supported Platforms
| Platform | Architecture | File |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{version}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{version}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{version}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{version}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{version}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{version}-macos-arm64.zip` |
#### 🛠️ Installation Steps
1. **📥 Download** the appropriate archive for your platform from the [latest releases page](https://github.com/xixu-me/tzst/releases/latest)
2. **📦 Extract** the archive to get the `tzst` executable (or `tzst.exe` on Windows)
3. **📂 Move** the executable to a directory in your PATH:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: Add the directory containing `tzst.exe` to your PATH environment variable
4. **✅ Verify** installation: `tzst --help`
#### 🎯 Benefits of Binary Installation
- ✅ **No Python required** - Standalone executable
- ✅ **Faster startup** - No Python interpreter overhead
- ✅ **Easy deployment** - Single file distribution
- ✅ **Consistent behavior** - Bundled dependencies
### 📦 From PyPI
```bash
pip install tzst
```
### From Source
### 🔧 From Source
```bash
git clone https://github.com/xixu-me/tzst.git
@@ -38,9 +75,9 @@ cd tzst
pip install .
```
### Development Installation
### 🚀 Development Installation
This project uses [Hatch](https://hatch.pypa.io/) as the build system:
This project uses modern Python packaging standards:
```bash
git clone https://github.com/xixu-me/tzst.git
@@ -48,34 +85,27 @@ cd tzst
pip install -e .[dev]
```
Alternatively, with [Hatch](https://hatch.pypa.io/) installed:
## 🚀 Quick Start
### 💻 Command Line Usage
> **Note**: Download the [standalone binary](#from-github-releases) for the best performance and no Python dependency. Alternatively, use `uvx tzst` for running without installation. See [uv documentation](https://docs.astral.sh/uv/) for details.
```bash
hatch env create
hatch shell
```
## Quick Start
### Command Line Usage
> **Recommended**: Use `uvx tzst` for running without installation and better performance. See [uv documentation](https://docs.astral.sh/uv/) for details.
```bash
# Create an archive
# 📁 Create an archive
tzst a archive.tzst file1.txt file2.txt directory/
# Extract an archive
# 📤 Extract an archive
tzst x archive.tzst
# List archive contents
# 📋 List archive contents
tzst l archive.tzst
# Test archive integrity
# 🧪 Test archive integrity
tzst t archive.tzst
```
### Python API Usage
### 🐍 Python API Usage
```python
from tzst import create_archive, extract_archive, list_archive
@@ -92,11 +122,11 @@ for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## Command Line Interface
## 💻 Command Line Interface
### Archive Operations
### 📁 Archive Operations
#### Create Archive
#### ➕ Create Archive
```bash
# Basic usage
@@ -110,7 +140,7 @@ tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### Extract Archive
#### 📤 Extract Archive
```bash
# Extract with full directory structure
@@ -129,7 +159,7 @@ tzst e archive.tzst -o output/
tzst x archive.tzst --streaming -o output/
```
#### List Contents
#### 📋 List Contents
```bash
# Simple listing
@@ -142,7 +172,7 @@ tzst l archive.tzst -v
tzst l archive.tzst --streaming -v
```
#### Test Integrity
#### 🧪 Test Integrity
```bash
# Test archive integrity
@@ -152,7 +182,7 @@ tzst t archive.tzst
tzst t archive.tzst --streaming
```
### Command Reference
### 📊 Command Reference
| Command | Aliases | Description | Streaming Support |
|---------|---------|-------------|-------------------|
@@ -162,7 +192,7 @@ tzst t archive.tzst --streaming
| `l` | `list` | List archive contents | ✓ `--streaming` |
| `t` | `test` | Test archive integrity | ✓ `--streaming` |
### CLI Options
### ⚙️ CLI Options
- `-v, --verbose`: Enable verbose output
- `-o, --output DIR`: Specify output directory (extract commands)
@@ -171,7 +201,7 @@ tzst t archive.tzst --streaming
- `--filter FILTER`: Security filter for extraction (data/tar/fully_trusted)
- `--no-atomic`: Disable atomic file operations (not recommended)
### Security Filters
### 🔒 Security Filters
```bash
# Extract with maximum security (default)
@@ -184,15 +214,15 @@ tzst x archive.tzst --filter tar
tzst x archive.tzst --filter fully_trusted
```
**Security Filter Options:**
**🔐 Security Filter Options:**
- `data` (default): Most secure. Blocks dangerous files, absolute paths, and paths outside extraction directory
- `tar`: Standard tar compatibility. Blocks absolute paths and directory traversal
- `fully_trusted`: No security restrictions. Only use with completely trusted archives
## Python API
## 🐍 Python API
### TzstArchive Class
### 📦 TzstArchive Class
```python
from tzst import TzstArchive
@@ -218,13 +248,13 @@ with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**Important Limitations:**
**⚠️ Important Limitations:**
- **Append Mode Not Supported**: Create multiple archives or recreate the entire archive instead
- **❌ Append Mode Not Supported**: Create multiple archives or recreate the entire archive instead
### Convenience Functions
### 🎯 Convenience Functions
#### create_archive()
#### 📁 create_archive()
```python
from tzst import create_archive
@@ -237,7 +267,7 @@ create_archive(
)
```
#### extract_archive()
#### 📤 extract_archive()
```python
from tzst import extract_archive
@@ -255,7 +285,7 @@ extract_archive("backup.tzst", "restore/", flatten=True)
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### list_archive()
#### 📋 list_archive()
```python
from tzst import list_archive
@@ -270,7 +300,7 @@ files = list_archive("backup.tzst", verbose=True)
files = list_archive("large_backup.tzst", streaming=True)
```
#### test_archive()
#### 🧪 test_archive()
```python
from tzst import test_archive
@@ -284,9 +314,9 @@ if test_archive("large_backup.tzst", streaming=True):
print("Large archive is valid")
```
## Advanced Features
## 🔧 Advanced Features
### File Extensions
### 📂 File Extensions
The library automatically handles file extensions with intelligent normalization:
@@ -303,7 +333,7 @@ create_archive("backup", files) # Creates backup.tzst
create_archive("backup.txt", files) # Creates backup.tzst (normalized)
```
### Compression Levels
### 🗜️ Compression Levels
Zstandard compression levels range from 1 (fastest) to 22 (best compression):
@@ -312,17 +342,17 @@ Zstandard compression levels range from 1 (fastest) to 22 (best compression):
- **Level 10-15**: Better compression, slower
- **Level 20-22**: Maximum compression, much slower
### Streaming Mode
### 🌊 Streaming Mode
Use streaming mode for memory-efficient processing of large archives:
**Benefits:**
**✅ Benefits:**
- Significantly reduced memory usage
- Better performance for archives that don't fit in memory
- Automatic cleanup of resources
**When to Use:**
**🎯 When to Use:**
- Archives larger than 100MB
- Limited memory environments
@@ -340,7 +370,7 @@ contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### Atomic Operations
### ⚡ Atomic Operations
All file creation operations use atomic file operations by default:
@@ -357,7 +387,7 @@ create_archive("important.tzst", files) # Safe from interruption
create_archive("test.tzst", files, use_temp_file=False)
```
### Error Handling
### 🚨 Error Handling
```python
from tzst import TzstArchive
@@ -381,45 +411,45 @@ except KeyboardInterrupt:
# Cleanup handled automatically
```
## Performance and Comparison
## 🚀 Performance and Comparison
### Performance Tips
### 💡 Performance Tips
1. **Compression levels**: Level 3 is optimal for most use cases
2. **Streaming**: Use for archives larger than 100MB
3. **Batch operations**: Add multiple files in single session
4. **File types**: Already compressed files won't compress much further
1. **🗜️ Compression levels**: Level 3 is optimal for most use cases
2. **🌊 Streaming**: Use for archives larger than 100MB
3. **📦 Batch operations**: Add multiple files in single session
4. **📄 File types**: Already compressed files won't compress much further
### vs Other Tools
### 🆚 vs Other Tools
**vs tar + gzip:**
- Better compression ratios
- Faster decompression
- Modern algorithm
- ✅ Better compression ratios
- ⚡ Faster decompression
- 🔄 Modern algorithm
**vs tar + xz:**
- Significantly faster compression
- Similar compression ratios
- Better speed/compression trade-off
- 🚀 Significantly faster compression
- 📊 Similar compression ratios
- ⚖️ Better speed/compression trade-off
**vs zip:**
- Better compression
- Preserves Unix permissions and metadata
- Better streaming support
- 🗜️ Better compression
- 🔐 Preserves Unix permissions and metadata
- 🌊 Better streaming support
## Requirements
## 📋 Requirements
- Python 3.12 or higher
- zstandard >= 0.19.0
- 🐍 Python 3.12 or higher
- 📦 zstandard >= 0.19.0
## Development
## 🛠️ Development
### Setting up Development Environment
### 🚀 Setting up Development Environment
This project uses **Hatch** as the build system:
This project uses modern Python packaging standards:
```bash
git clone https://github.com/xixu-me/tzst.git
@@ -427,25 +457,17 @@ cd tzst
pip install -e .[dev]
```
Or with Hatch:
### 🧪 Running Tests
```bash
pip install hatch
hatch env create
hatch shell
```
### Running Tests
```bash
# Using pytest
# Run tests with coverage
pytest --cov=tzst --cov-report=html
# Using Hatch
hatch run pytest --cov=tzst --cov-report=html
# Or use the simpler command (coverage settings are in pyproject.toml)
pytest
```
### Code Quality
### ✨ Code Quality
```bash
# Check code quality
@@ -455,7 +477,7 @@ ruff check src tests
ruff format src tests
```
## Contributing
## 🤝 Contributing
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) for:
@@ -464,7 +486,7 @@ We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md)
- Testing requirements and writing tests
- Pull request process and review workflow
### Quick Start for Contributors
### 🚀 Quick Start for Contributors
```bash
git clone https://github.com/xixu-me/tzst.git
@@ -473,7 +495,7 @@ pip install -e .[dev]
python -m pytest tests/
```
### Types of Contributions Welcome
### 🎯 Types of Contributions Welcome
- 🐛 **Bug fixes** - Fix issues in existing functionality
- ✨ **Features** - Add new capabilities to the library
@@ -482,13 +504,13 @@ python -m pytest tests/
- ⚡ **Performance** - Optimize existing code
- 🔒 **Security** - Address security vulnerabilities
## Acknowledgments
## 🙏 Acknowledgments
- [Meta Zstandard](https://github.com/facebook/zstd) for the excellent compression algorithm
- [python-zstandard](https://github.com/indygreg/python-zstandard) for Python bindings
- The Python community for inspiration and feedback
## License
## 📄 License
Copyright &copy; 2025 [Xi Xu](https://xi-xu.me). All rights reserved.
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | **🇧🇷 português**
**tzst** é uma biblioteca Python de próxima geração projetada para gerenciamento moderno de arquivos, aproveitando a compressão Zstandard de ponta para oferecer desempenho, segurança e confiabilidade superiores. Construída exclusivamente para Python 3.12+, esta solução corporativa combina operações atômicas, eficiência de streaming e uma API meticulosamente elaborada para redefinir como os desenvolvedores lidam com arquivos `.tzst`/`.tar.zst` em ambientes de produção. 🚀
## ✨ Recursos
- **🗜️ Alta Compressão**: Compressão Zstandard para excelentes taxas de compressão e velocidade
- **📁 Compatibilidade com Tar**: Cria arquivos tar padrão comprimidos com Zstandard
- **💻 Interface de Linha de Comando**: CLI intuitiva com suporte a streaming e opções abrangentes
- **🐍 API Python**: API limpa e pythônica para uso programático
- **🌍 Multiplataforma**: Funciona no Windows, macOS e Linux
- **📂 Múltiplas Extensões**: Suporta tanto extensões `.tzst` quanto `.tar.zst`
- **💾 Eficiente em Memória**: Modo streaming para lidar com grandes arquivos com uso mínimo de memória
- **⚡ Operações Atômicas**: Operações de arquivo seguras com limpeza automática em caso de interrupção
- **🔒 Seguro por Padrão**: Usa o filtro 'data' para máxima segurança durante a extração
- **🚨 Tratamento de Erros Aprimorado**: Mensagens de erro claras com alternativas úteis
## 📥 Instalação
### Dos Releases do GitHub
Baixe executáveis independentes que não requerem instalação do Python:
#### Plataformas Suportadas
| Plataforma | Arquitetura | Arquivo |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{versão}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{versão}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{versão}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{versão}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{versão}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{versão}-macos-arm64.zip` |
#### 🛠️ Passos de Instalação
1. **📥 Baixe** o arquivo apropriado para sua plataforma da [página de releases mais recentes](https://github.com/xixu-me/tzst/releases/latest)
2. **📦 Extraia** o arquivo para obter o executável `tzst` (ou `tzst.exe` no Windows)
3. **📂 Mova** o executável para um diretório em seu PATH:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: Adicione o diretório contendo `tzst.exe` à sua variável de ambiente PATH
4. **✅ Verifique** a instalação: `tzst --help`
#### 🎯 Benefícios da Instalação Binária
- ✅ **Python não é necessário** - Executável independente
- ✅ **Inicialização mais rápida** - Sem overhead do interpretador Python
- ✅ **Implantação fácil** - Distribuição de arquivo único
- ✅ **Comportamento consistente** - Dependências incluídas
### 📦 Do PyPI
```bash
pip install tzst
```
### 🔧 Do Código Fonte
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 Instalação para Desenvolvimento
Este projeto usa padrões modernos de empacotamento Python:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 Início Rápido
### 💻 Uso da Linha de Comando
> **Nota**: Baixe o [binário independente](#dos-releases-do-github) para melhor desempenho e sem dependência do Python. Alternativamente, use `uvx tzst` para executar sem instalação. Veja a [documentação do uv](https://docs.astral.sh/uv/) para detalhes.
```bash
# 📁 Criar um arquivo
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 Extrair um arquivo
tzst x archive.tzst
# 📋 Listar conteúdo do arquivo
tzst l archive.tzst
# 🧪 Testar integridade do arquivo
tzst t archive.tzst
```
### 🐍 Uso da API Python
```python
from tzst import create_archive, extract_archive, list_archive
# Criar um arquivo
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# Extrair um arquivo
extract_archive("archive.tzst", "output_directory/")
# Listar conteúdo do arquivo
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 Interface de Linha de Comando
### 📁 Operações de Arquivo
#### ➕ Criar Arquivo
```bash
# Uso básico
tzst a archive.tzst file1.txt file2.txt
# Com nível de compressão (1-22, padrão: 3)
tzst a archive.tzst files/ -l 15
# Comandos alternativos
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 Extrair Arquivo
```bash
# Extrair com estrutura completa de diretórios
tzst x archive.tzst
# Extrair para diretório específico
tzst x archive.tzst -o output/
# Extrair arquivos específicos
tzst x archive.tzst file1.txt dir/file2.txt
# Extrair sem estrutura de diretórios (plano)
tzst e archive.tzst -o output/
# Usar modo streaming para grandes arquivos
tzst x archive.tzst --streaming -o output/
```
#### 📋 Listar Conteúdo
```bash
# Listagem simples
tzst l archive.tzst
# Listagem detalhada com informações
tzst l archive.tzst -v
# Usar modo streaming para grandes arquivos
tzst l archive.tzst --streaming -v
```
#### 🧪 Testar Integridade
```bash
# Testar integridade do arquivo
tzst t archive.tzst
# Testar com modo streaming
tzst t archive.tzst --streaming
```
### 📊 Referência de Comandos
| Comando | Aliases | Descrição | Suporte a Streaming |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Criar ou adicionar ao arquivo | N/A |
| `x` | `extract` | Extrair com caminhos completos | ✓ `--streaming` |
| `e` | `extract-flat` | Extrair sem estrutura de diretórios | ✓ `--streaming` |
| `l` | `list` | Listar conteúdo do arquivo | ✓ `--streaming` |
| `t` | `test` | Testar integridade do arquivo | ✓ `--streaming` |
### ⚙️ Opções da CLI
- `-v, --verbose`: Ativar saída detalhada
- `-o, --output DIR`: Especificar diretório de saída (comandos de extração)
- `-l, --level LEVEL`: Definir nível de compressão 1-22 (comando de criação)
- `--streaming`: Ativar modo streaming para processamento eficiente em memória
- `--filter FILTER`: Filtro de segurança para extração (data/tar/fully_trusted)
- `--no-atomic`: Desativar operações de arquivo atômicas (não recomendado)
### 🔒 Filtros de Segurança
```bash
# Extrair com máxima segurança (padrão)
tzst x archive.tzst --filter data
# Extrair com compatibilidade tar padrão
tzst x archive.tzst --filter tar
# Extrair com confiança total (perigoso - apenas para arquivos confiáveis)
tzst x archive.tzst --filter fully_trusted
```
**🔐 Opções de Filtro de Segurança:**
- `data` (padrão): Mais seguro. Bloqueia arquivos perigosos, caminhos absolutos e caminhos fora do diretório de extração
- `tar`: Compatibilidade tar padrão. Bloqueia caminhos absolutos e travessia de diretórios
- `fully_trusted`: Sem restrições de segurança. Use apenas com arquivos completamente confiáveis
## 🐍 API Python
### 📦 Classe TzstArchive
```python
from tzst import TzstArchive
# Criar um novo arquivo
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Ler um arquivo existente
with TzstArchive("archive.tzst", "r") as archive:
# Listar conteúdo
contents = archive.list(verbose=True)
# Extrair com filtro de segurança
archive.extract("file.txt", "output/", filter="data")
# Testar integridade
is_valid = archive.test()
# Para grandes arquivos, usar modo streaming
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ Limitações Importantes:**
- **❌ Modo de anexação não suportado**: Crie múltiplos arquivos ou recrie o arquivo inteiro em vez disso
### 🎯 Funções de Conveniência
#### 📁 create_archive()
```python
from tzst import create_archive
# Criar com operações atômicas (padrão)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# Extrair com segurança (padrão: filtro 'data')
extract_archive("backup.tzst", "restore/")
# Extrair arquivos específicos
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# Achatar estrutura de diretórios
extract_archive("backup.tzst", "restore/", flatten=True)
# Usar streaming para grandes arquivos
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# Listagem simples
files = list_archive("backup.tzst")
# Listagem detalhada
files = list_archive("backup.tzst", verbose=True)
# Streaming para grandes arquivos
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# Teste básico de integridade
if test_archive("backup.tzst"):
print("Arquivo é válido")
# Testar com streaming
if test_archive("large_backup.tzst", streaming=True):
print("Grande arquivo é válido")
```
## 🔧 Recursos Avançados
### 📂 Extensões de Arquivo
A biblioteca automaticamente lida com extensões de arquivo com normalização inteligente:
- `.tzst` - Extensão primária para arquivos tar+zstandard
- `.tar.zst` - Extensão padrão alternativa
- Detecção automática ao abrir arquivos existentes
- Adição automática de extensão ao criar arquivos
```python
# Todos estes criam arquivos válidos
create_archive("backup.tzst", files) # Cria backup.tzst
create_archive("backup.tar.zst", files) # Cria backup.tar.zst
create_archive("backup", files) # Cria backup.tzst
create_archive("backup.txt", files) # Cria backup.tzst (normalizado)
```
### 🗜️ Níveis de Compressão
Os níveis de compressão Zstandard variam de 1 (mais rápido) a 22 (melhor compressão):
- **Nível 1-3**: Compressão rápida, arquivos maiores
- **Nível 3** (padrão): Bom equilíbrio entre velocidade e compressão
- **Nível 10-15**: Melhor compressão, mais lento
- **Nível 20-22**: Compressão máxima, muito mais lento
### 🌊 Modo Streaming
Use o modo streaming para processamento eficiente em memória de grandes arquivos:
**✅ Benefícios:**
- Uso de memória significativamente reduzido
- Melhor desempenho para arquivos que não cabem na memória
- Limpeza automática de recursos
**🎯 Quando usar:**
- Arquivos maiores que 100MB
- Ambientes com memória limitada
- Processamento de arquivos com muitos arquivos grandes
```python
# Exemplo: Processando um grande arquivo de backup
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# Operações eficientes em memória
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ Operações Atômicas
Todas as operações de criação de arquivo usam operações de arquivo atômicas por padrão:
- Arquivos criados em arquivos temporários primeiro, depois movidos atomicamente
- Limpeza automática se o processo for interrompido
- Nenhum risco de arquivos corrompidos ou incompletos
- Compatibilidade multiplataforma
```python
# Operações atômicas habilitadas por padrão
create_archive("important.tzst", files) # Seguro contra interrupção
# Pode ser desabilitado se necessário (não recomendado)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 Tratamento de Erros
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Falha ao descomprimir arquivo")
except TzstFileNotFoundError:
print("Arquivo de arquivo não encontrado")
except KeyboardInterrupt:
print("Operação interrompida pelo usuário")
# Limpeza é tratada automaticamente
```
## 🚀 Desempenho e Comparação
### 💡 Dicas de Desempenho
1. **🗜️ Níveis de compressão**: Nível 3 é ótimo para a maioria dos casos de uso
2. **🌊 Streaming**: Use para arquivos maiores que 100MB
3. **📦 Operações em lote**: Adicione múltiplos arquivos em uma única sessão
4. **📄 Tipos de arquivo**: Arquivos já comprimidos não comprimirão muito mais
### 🆚 vs Outras Ferramentas
**vs tar + gzip:**
- ✅ Melhores taxas de compressão
- ⚡ Descompressão mais rápida
- 🔄 Algoritmo moderno
**vs tar + xz:**
- 🚀 Compressão significativamente mais rápida
- 📊 Taxas de compressão similares
- ⚖️ Melhor compromisso velocidade/compressão
**vs zip:**
- 🗜️ Melhor compressão
- 🔐 Preserva permissões Unix e metadados
- 🌊 Melhor suporte a streaming
## 📋 Requisitos
- 🐍 Python 3.12 ou superior
- 📦 zstandard >= 0.19.0
## 🛠️ Desenvolvimento
### 🚀 Configurando Ambiente de Desenvolvimento
Este projeto usa padrões modernos de empacotamento Python:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 Executando Testes
```bash
# Executar testes com cobertura
pytest --cov=tzst --cov-report=html
# Ou usar o comando mais simples (configurações de cobertura estão em pyproject.toml)
pytest
```
### ✨ Qualidade do Código
```bash
# Verificar qualidade do código
ruff check src tests
# Formatar código
ruff format src tests
```
## 🤝 Contribuindo
Nós recebemos contribuições! Por favor, leia nosso [Guia de Contribuição](CONTRIBUTING.md) para:
- Configuração de desenvolvimento e estrutura do projeto
- Diretrizes de estilo de código e melhores práticas
- Requisitos de teste e escrita de testes
- Processo de pull request e fluxo de revisão
### 🚀 Início Rápido para Colaboradores
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 Tipos de Contribuições Bem-vindas
- 🐛 **Correções de bugs** - Corrigir problemas na funcionalidade existente
- ✨ **Recursos** - Adicionar novas capacidades à biblioteca
- 📚 **Documentação** - Melhorar ou adicionar documentação
- 🧪 **Testes** - Adicionar ou melhorar cobertura de testes
- ⚡ **Desempenho** - Otimizar código existente
- 🔒 **Segurança** - Abordar vulnerabilidades de segurança
## 🙏 Agradecimentos
- [Meta Zstandard](https://github.com/facebook/zstd) pelo excelente algoritmo de compressão
- [python-zstandard](https://github.com/indygreg/python-zstandard) pelas ligações Python
- A comunidade Python pela inspiração e feedback
## 📄 Licença
Direitos autorais &copy; 2025 [Xi Xu](https://xi-xu.me). Todos os direitos reservados.
Licenciado sob a licença [BSD 3-Clause](LICENSE).
+517
View File
@@ -0,0 +1,517 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | [🇨🇳 汉语](./README.zh.md) | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | **🇷🇺 русский** | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** — это библиотека Python нового поколения, разработанная для современного управления архивами, использующая передовое сжатие Zstandard для обеспечения превосходной производительности, безопасности и надёжности. Созданная исключительно для Python 3.12+, это корпоративное решение объединяет атомарные операции, эффективность потоковой передачи и тщательно разработанный API для переосмысления того, как разработчики работают с архивами `.tzst`/`.tar.zst` в производственных средах. 🚀
## ✨ Особенности
- **🗜️ Высокое сжатие**: Сжатие Zstandard для отличных коэффициентов сжатия и скорости
- **📁 Совместимость с Tar**: Создаёт стандартные tar-архивы, сжатые с помощью Zstandard
- **💻 Интерфейс командной строки**: Интуитивный CLI с поддержкой потоковой передачи и всесторонними опциями
- **🐍 Python API**: Чистый, pythonic API для программного использования
- **🌍 Кроссплатформенность**: Работает на Windows, macOS и Linux
- **📂 Множественные расширения**: Поддерживает как `.tzst`, так и `.tar.zst` расширения
- **💾 Эффективность памяти**: Режим потоковой передачи для обработки больших архивов с минимальным использованием памяти
- **⚡ Атомарные операции**: Безопасные файловые операции с автоматической очисткой при прерывании
- **🔒 Безопасность по умолчанию**: Использует фильтр 'data' для максимальной безопасности при извлечении
- **🚨 Улучшенная обработка ошибок**: Чёткие сообщения об ошибках с полезными альтернативами
## 📥 Установка
### Из релизов GitHub
Скачайте автономные исполняемые файлы, которые не требуют установки Python:
#### Поддерживаемые платформы
| Платформа | Архитектура | Файл |
|----------|-------------|------|
| **🐧 Linux** | x86_64 | `tzst-v{версия}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{версия}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{версия}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{версия}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{версия}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{версия}-macos-arm64.zip` |
#### 🛠️ Шаги установки
1. **📥 Скачайте** подходящий архив для вашей платформы со [страницы последних релизов](https://github.com/xixu-me/tzst/releases/latest)
2. **📦 Извлеките** архив, чтобы получить исполняемый файл `tzst` (или `tzst.exe` на Windows)
3. **📂 Переместите** исполняемый файл в директорию в вашем PATH:
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **🪟 Windows**: Добавьте директорию, содержащую `tzst.exe`, в переменную окружения PATH
4. **✅ Проверьте** установку: `tzst --help`
#### 🎯 Преимущества бинарной установки
- ✅ **Python не требуется** - Автономный исполняемый файл
- ✅ **Быстрый запуск** - Нет накладных расходов интерпретатора Python
- ✅ **Лёгкое развёртывание** - Распространение одним файлом
- ✅ **Последовательное поведение** - Встроенные зависимости
### 📦 Из PyPI
```bash
pip install tzst
```
### 🔧 Из исходного кода
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 Установка для разработки
Этот проект использует современные стандарты упаковки Python:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 Быстрый старт
### 💻 Использование командной строки
> **Примечание**: Скачайте [автономный бинарный файл](#из-релизов-github) для лучшей производительности и отсутствия зависимости от Python. Альтернативно, используйте `uvx tzst` для запуска без установки. Смотрите [документацию uv](https://docs.astral.sh/uv/) для деталей.
```bash
# 📁 Создать архив
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 Извлечь архив
tzst x archive.tzst
# 📋 Список содержимого архива
tzst l archive.tzst
# 🧪 Проверить целостность архива
tzst t archive.tzst
```
### 🐍 Использование Python API
```python
from tzst import create_archive, extract_archive, list_archive
# Создать архив
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# Извлечь архив
extract_archive("archive.tzst", "output_directory/")
# Список содержимого архива
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 Интерфейс командной строки
### 📁 Операции с архивами
#### ➕ Создать архив
```bash
# Базовое использование
tzst a archive.tzst file1.txt file2.txt
# С уровнем сжатия (1-22, по умолчанию: 3)
tzst a archive.tzst files/ -l 15
# Альтернативные команды
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 Извлечь архив
```bash
# Извлечь с полной структурой директорий
tzst x archive.tzst
# Извлечь в определённую директорию
tzst x archive.tzst -o output/
# Извлечь определённые файлы
tzst x archive.tzst file1.txt dir/file2.txt
# Извлечь без структуры директорий (плоско)
tzst e archive.tzst -o output/
# Использовать режим потоковой передачи для больших архивов
tzst x archive.tzst --streaming -o output/
```
#### 📋 Список содержимого
```bash
# Простой список
tzst l archive.tzst
# Подробный список с деталями
tzst l archive.tzst -v
# Использовать режим потоковой передачи для больших архивов
tzst l archive.tzst --streaming -v
```
#### 🧪 Проверка целостности
```bash
# Проверить целостность архива
tzst t archive.tzst
# Проверить с режимом потоковой передачи
tzst t archive.tzst --streaming
```
### 📊 Справочник команд
| Команда | Псевдонимы | Описание | Поддержка потоковой передачи |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Создать или добавить в архив | N/A |
| `x` | `extract` | Извлечь с полными путями | ✓ `--streaming` |
| `e` | `extract-flat` | Извлечь без структуры директорий | ✓ `--streaming` |
| `l` | `list` | Список содержимого архива | ✓ `--streaming` |
| `t` | `test` | Проверить целостность архива | ✓ `--streaming` |
### ⚙️ Опции CLI
- `-v, --verbose`: Включить подробный вывод
- `-o, --output DIR`: Указать выходную директорию (команды извлечения)
- `-l, --level LEVEL`: Установить уровень сжатия 1-22 (команда создания)
- `--streaming`: Включить режим потоковой передачи для эффективной обработки памяти
- `--filter FILTER`: Фильтр безопасности для извлечения (data/tar/fully_trusted)
- `--no-atomic`: Отключить атомарные файловые операции (не рекомендуется)
### 🔒 Фильтры безопасности
```bash
# Извлечь с максимальной безопасностью (по умолчанию)
tzst x archive.tzst --filter data
# Извлечь со стандартной совместимостью tar
tzst x archive.tzst --filter tar
# Извлечь с полным доверием (опасно - только для доверенных архивов)
tzst x archive.tzst --filter fully_trusted
```
**🔐 Опции фильтра безопасности:**
- `data` (по умолчанию): Наиболее безопасно. Блокирует опасные файлы, абсолютные пути и пути вне директории извлечения
- `tar`: Стандартная совместимость tar. Блокирует абсолютные пути и обход директорий
- `fully_trusted`: Никаких ограничений безопасности. Используйте только с полностью доверенными архивами
## 🐍 Python API
### 📦 Класс TzstArchive
```python
from tzst import TzstArchive
# Создать новый архив
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Прочитать существующий архив
with TzstArchive("archive.tzst", "r") as archive:
# Список содержимого
contents = archive.list(verbose=True)
# Извлечь с фильтром безопасности
archive.extract("file.txt", "output/", filter="data")
# Проверить целостность
is_valid = archive.test()
# Для больших архивов используйте режим потоковой передачи
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ Важные ограничения:**
- **❌ Режим добавления не поддерживается**: Создавайте множественные архивы или пересоздавайте весь архив вместо этого
### 🎯 Удобные функции
#### 📁 create_archive()
```python
from tzst import create_archive
# Создать с атомарными операциями (по умолчанию)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# Извлечь с безопасностью (по умолчанию: фильтр 'data')
extract_archive("backup.tzst", "restore/")
# Извлечь определённые файлы
extract_archive("backup.tzst", "restore/", members=["config.txt"])
# Сплющить структуру директорий
extract_archive("backup.tzst", "restore/", flatten=True)
# Использовать потоковую передачу для больших архивов
extract_archive("large_backup.tzst", "restore/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# Простой список
files = list_archive("backup.tzst")
# Подробный список
files = list_archive("backup.tzst", verbose=True)
# Потоковая передача для больших архивов
files = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# Базовая проверка целостности
if test_archive("backup.tzst"):
print("Архив действителен")
# Проверка с потоковой передачей
if test_archive("large_backup.tzst", streaming=True):
print("Большой архив действителен")
```
## 🔧 Продвинутые возможности
### 📂 Расширения файлов
Библиотека автоматически обрабатывает расширения файлов с интеллектуальной нормализацией:
- `.tzst` - Основное расширение для архивов tar+zstandard
- `.tar.zst` - Альтернативное стандартное расширение
- Автоопределение при открытии существующих архивов
- Автоматическое добавление расширения при создании архивов
```python
# Все это создаёт действительные архивы
create_archive("backup.tzst", files) # Создаёт backup.tzst
create_archive("backup.tar.zst", files) # Создаёт backup.tar.zst
create_archive("backup", files) # Создаёт backup.tzst
create_archive("backup.txt", files) # Создаёт backup.tzst (нормализовано)
```
### 🗜️ Уровни сжатия
Уровни сжатия Zstandard варьируются от 1 (самый быстрый) до 22 (лучшее сжатие):
- **Уровень 1-3**: Быстрое сжатие, большие файлы
- **Уровень 3** (по умолчанию): Хороший баланс скорости и сжатия
- **Уровень 10-15**: Лучшее сжатие, медленнее
- **Уровень 20-22**: Максимальное сжатие, намного медленнее
### 🌊 Режим потоковой передачи
Используйте режим потоковой передачи для эффективной обработки больших архивов в памяти:
**✅ Преимущества:**
- Значительно сниженное использование памяти
- Лучшая производительность для архивов, которые не помещаются в память
- Автоматическая очистка ресурсов
**🎯 Когда использовать:**
- Архивы больше 100MB
- Среды с ограниченной памятью
- Обработка архивов с множеством больших файлов
```python
# Пример: Обработка большого архива резервной копии
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# Операции, эффективные по памяти
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore/", streaming=True)
```
### ⚡ Атомарные операции
Все операции создания файлов используют атомарные файловые операции по умолчанию:
- Архивы создаются сначала во временных файлах, затем атомарно перемещаются
- Автоматическая очистка при прерывании процесса
- Никакого риска повреждённых или неполных архивов
- Кроссплатформенная совместимость
```python
# Атомарные операции включены по умолчанию
create_archive("important.tzst", files) # Безопасно от прерывания
# Может быть отключено при необходимости (не рекомендуется)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 Обработка ошибок
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Не удалось распаковать архив")
except TzstFileNotFoundError:
print("Файл архива не найден")
except KeyboardInterrupt:
print("Операция прервана пользователем")
# Очистка обрабатывается автоматически
```
## 🚀 Производительность и сравнение
### 💡 Советы по производительности
1. **🗜️ Уровни сжатия**: Уровень 3 оптимален для большинства случаев использования
2. **🌊 Потоковая передача**: Используйте для архивов больше 100MB
3. **📦 Пакетные операции**: Добавляйте множественные файлы в одной сессии
4. **📄 Типы файлов**: Уже сжатые файлы не будут сжиматься намного дальше
### 🆚 против других инструментов
**против tar + gzip:**
- ✅ Лучшие коэффициенты сжатия
- ⚡ Быстрее распаковка
- 🔄 Современный алгоритм
**против tar + xz:**
- 🚀 Значительно быстрее сжатие
- 📊 Похожие коэффициенты сжатия
- ⚖️ Лучший компромисс скорость/сжатие
**против zip:**
- 🗜️ Лучшее сжатие
- 🔐 Сохраняет разрешения Unix и метаданные
- 🌊 Лучшая поддержка потоковой передачи
## 📋 Требования
- 🐍 Python 3.12 или выше
- 📦 zstandard >= 0.19.0
## 🛠️ Разработка
### 🚀 Настройка среды разработки
Этот проект использует современные стандарты упаковки Python:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 Запуск тестов
```bash
# Запустить тесты с покрытием
pytest --cov=tzst --cov-report=html
# Или использовать более простую команду (настройки покрытия в pyproject.toml)
pytest
```
### ✨ Качество кода
```bash
# Проверить качество кода
ruff check src tests
# Форматировать код
ruff format src tests
```
## 🤝 Вклад
Мы приветствуем вклады! Пожалуйста, прочитайте наше [Руководство по вкладу](CONTRIBUTING.md) для:
- Настройки разработки и структуры проекта
- Руководящих принципов стиля кода и лучших практик
- Требований к тестированию и написанию тестов
- Процесса pull request'ов и рабочего процесса обзора
### 🚀 Быстрый старт для участников
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 Типы приветствуемых вкладов
- 🐛 **Исправления ошибок** - Исправить проблемы в существующей функциональности
- ✨ **Возможности** - Добавить новые возможности в библиотеку
- 📚 **Документация** - Улучшить или добавить документацию
- 🧪 **Тесты** - Добавить или улучшить покрытие тестами
- ⚡ **Производительность** - Оптимизировать существующий код
- 🔒 **Безопасность** - Устранить уязвимости безопасности
## 🙏 Благодарности
- [Meta Zstandard](https://github.com/facebook/zstd) за отличный алгоритм сжатия
- [python-zstandard](https://github.com/indygreg/python-zstandard) за связи Python
- Сообществу Python за вдохновение и обратную связь
## 📄 Лицензия
Авторские права &copy; 2025 [Си Сюй](https://xi-xu.me). Все права защищены.
Лицензировано под лицензией [BSD 3-Clause](LICENSE).
+513
View File
@@ -0,0 +1,513 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/xixu-me/tzst/refs/heads/main/docs/_static/tzst-logo.png" width="300">
</h1><br>
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://tzst.xi-xu.me)
[🇺🇸 English](./README.md) | **🇨🇳 汉语** | [🇪🇸 español](./README.es.md) | [🇯🇵 日本語](./README.ja.md) | [🇦🇪 العربية](./README.ar.md) | [🇷🇺 русский](./README.ru.md) | [🇩🇪 Deutsch](./README.de.md) | [🇫🇷 français](./README.fr.md) | [🇰🇷 한국어](./README.ko.md) | [🇧🇷 português](./README.pt.md)
**tzst** 是一个面向现代归档管理的新一代 Python 库,利用前沿的 Zstandard 压缩技术,提供卓越的性能、安全性和可靠性。专为 Python 3.12+ 打造,这个企业级解决方案结合原子操作、流式处理效率和精心设计的 API,重新定义了开发者在生产环境中处理 `.tzst`/`.tar.zst` 归档文件的方式。🚀
## ✨ 功能特性
- **🗜️ 高效压缩**:采用 Zstandard 压缩算法,实现优异的压缩率和速度
- **📁 Tar 兼容性**:创建符合标准的 tar 归档并使用 Zstandard 压缩
- **💻 命令行界面**:直观的 CLI,支持流式处理和全面选项
- **🐍 Python API**:简洁、符合 Python 风格的编程接口
- **🌍 跨平台支持**:兼容 Windows、macOS 和 Linux
- **📂 多扩展名支持**:同时支持 `.tzst` 和 `.tar.zst` 扩展名
- **💾 内存高效**:流模式可高效处理大型归档文件
- **⚡ 原子操作**:安全的文件操作,中断时自动清理
- **🔒 默认安全**:提取时使用 'data' 过滤器确保最高安全性
- **🚨 增强的错误处理**:清晰的错误信息和实用建议
## 📥 安装指南
### 从 GitHub Releases 安装
下载无需 Python 环境的独立可执行文件:
#### 支持平台
| 平台 | 架构 | 文件 |
|------|------|------|
| **🐧 Linux** | x86_64 | `tzst-v{版本}-linux-x86_64.zip` |
| **🐧 Linux** | ARM64 | `tzst-v{版本}-linux-aarch64.zip` |
| **🪟 Windows** | x64 | `tzst-v{版本}-windows-amd64.zip` |
| **🪟 Windows** | ARM64 | `tzst-v{版本}-windows-arm64.zip` |
| **🍎 macOS** | Intel | `tzst-v{版本}-macos-x86_64.zip` |
| **🍎 macOS** | Apple Silicon | `tzst-v{版本}-macos-arm64.zip` |
#### 🛠️ 安装步骤
1. **📥 下载**:从[最新发布页面](https://github.com/xixu-me/tzst/releases/latest)下载适合您平台的压缩包
2. **📦 解压**:解压获取 `tzst` 可执行文件(Windows 为 `tzst.exe`)
3. **📂 移动**:将可执行文件添加到 PATH 环境变量:
- **🐧 Linux/macOS**:`sudo mv tzst /usr/local/bin/`
- **🪟 Windows**:将包含 `tzst.exe` 的目录添加到 PATH
4. **✅ 验证**:运行 `tzst --help` 确认安装成功
#### 🎯 二进制安装优势
- ✅ **无需 Python** - 独立可执行文件
- ✅ **启动更快** - 无 Python 解释器开销
- ✅ **易于部署** - 单文件分发
- ✅ **行为一致** - 依赖项已打包
### 📦 通过 PyPI 安装
```bash
pip install tzst
```
### 🔧 从源码安装
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
### 🚀 开发环境安装
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
## 🚀 快速开始
### 💻 命令行使用
> **注意**:下载[独立二进制文件](#从-github-releases-安装)可获得最佳性能且无需 Python 环境。也可使用 `uvx tzst` 免安装运行,详见 [uv 文档](https://docs.astral.sh/uv/)。
```bash
# 📁 创建归档
tzst a archive.tzst file1.txt file2.txt directory/
# 📤 提取归档
tzst x archive.tzst
# 📋 列出归档内容
tzst l archive.tzst
# 🧪 测试归档完整性
tzst t archive.tzst
```
### 🐍 Python API 使用
```python
from tzst import create_archive, extract_archive, list_archive
# 创建归档
create_archive("archive.tzst", ["file1.txt", "file2.txt", "directory/"])
# 提取归档
extract_archive("archive.tzst", "output_dir/")
# 列出归档内容
contents = list_archive("archive.tzst", verbose=True)
for item in contents:
print(f"{item['name']}: {item['size']} bytes")
```
## 💻 命令行接口
### 📁 归档操作
#### ➕ 创建归档
```bash
# 基本用法
tzst a archive.tzst file1.txt file2.txt
# 指定压缩级别 (1-22, 默认: 3)
tzst a archive.tzst files/ -l 15
# 等效命令
tzst add archive.tzst files/
tzst create archive.tzst files/
```
#### 📤 提取归档
```bash
# 完整目录结构提取
tzst x archive.tzst
# 提取到指定目录
tzst x archive.tzst -o output_dir/
# 提取特定文件
tzst x archive.tzst file1.txt dir/file2.txt
# 扁平化提取(无目录结构)
tzst e archive.tzst -o output_dir/
# 大文件使用流模式
tzst x archive.tzst --streaming -o output_dir/
```
#### 📋 列出内容
```bash
# 简单列表
tzst l archive.tzst
# 详细列表
tzst l archive.tzst -v
# 大文件使用流模式
tzst l archive.tzst --streaming -v
```
#### 🧪 测试完整性
```bash
# 测试归档完整性
tzst t archive.tzst
# 流模式测试
tzst t archive.tzst --streaming
```
### 📊 命令参考
| 命令 | 等效命令 | 描述 | 是否支持流模式 |
|------|----------|------|----------------|
| `a` | `add`, `create` | 创建或添加文件到归档 | 不支持 |
| `x` | `extract` | 完整路径提取 | ✓ `--streaming` |
| `e` | `extract-flat` | 扁平化提取 | ✓ `--streaming` |
| `l` | `list` | 列出归档内容 | ✓ `--streaming` |
| `t` | `test` | 测试归档完整性 | ✓ `--streaming` |
### ⚙️ CLI 选项
- `-v, --verbose`:启用详细输出
- `-o, --output DIR`:指定输出目录(提取命令)
- `-l, --level LEVEL`:设置压缩级别 1-22(创建命令)
- `--streaming`:启用流模式实现内存高效处理
- `--filter FILTER`:提取安全过滤器(data/tar/fully_trusted)
- `--no-atomic`:禁用原子文件操作(不推荐)
### 🔒 安全过滤器
```bash
# 最高安全性提取(默认)
tzst x archive.tzst --filter data
# 标准tar兼容性提取
tzst x archive.tzst --filter tar
# 完全信任模式(危险 - 仅适用于可信归档)
tzst x archive.tzst --filter fully_trusted
```
**🔐 安全过滤器选项:**
- `data` (默认):最安全。阻止危险文件、绝对路径和提取目录外路径
- `tar`:标准 tar 兼容性。阻止绝对路径和目录遍历
- `fully_trusted`:无安全限制。仅适用于完全可信的归档
## 🐍 Python API
### 📦 TzstArchive 类
```python
from tzst import TzstArchive
# 创建新归档
with TzstArchive("archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# 读取现有归档
with TzstArchive("archive.tzst", "r") as archive:
# 列出内容
contents = archive.list(verbose=True)
# 安全提取
archive.extract("file.txt", "output/", filter="data")
# 测试完整性
is_valid = archive.test()
# 大文件使用流模式
with TzstArchive("large_archive.tzst", "r", streaming=True) as archive:
archive.extract(path="output/")
```
**⚠️ 重要限制:**
- **❌ 不支持追加模式**:需创建新归档或重建整个归档
### 🎯 便捷函数
#### 📁 create_archive()
```python
from tzst import create_archive
# 原子操作创建(默认)
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
#### 📤 extract_archive()
```python
from tzst import extract_archive
# 安全提取(默认:'data'过滤器)
extract_archive("backup.tzst", "restore_dir/")
# 提取特定文件
extract_archive("backup.tzst", "restore_dir/", members=["config.txt"])
# 扁平化提取
extract_archive("backup.tzst", "restore_dir/", flatten=True)
# 大文件使用流模式
extract_archive("large_backup.tzst", "restore_dir/", streaming=True)
```
#### 📋 list_archive()
```python
from tzst import list_archive
# 简单列表
file_list = list_archive("backup.tzst")
# 详细列表
file_details = list_archive("backup.tzst", verbose=True)
# 大文件使用流模式
large_list = list_archive("large_backup.tzst", streaming=True)
```
#### 🧪 test_archive()
```python
from tzst import test_archive
# 基本完整性测试
if test_archive("backup.tzst"):
print("Archive is valid")
# 流模式测试
if test_archive("large_backup.tzst", streaming=True):
print("Large archive is valid")
```
## 🔧 高级功能
### 📂 文件扩展名
库自动处理文件扩展名并智能标准化:
- `.tzst` - tar + zstandard 归档主扩展名
- `.tar.zst` - 替代标准扩展名
- 打开现有归档时自动检测
- 创建归档时自动添加扩展名
```python
# 以下创建方式均有效
create_archive("backup.tzst", files) # 创建 backup.tzst
create_archive("backup.tar.zst", files) # 创建 backup.tar.zst
create_archive("backup", files) # 创建 backup.tzst
create_archive("backup.txt", files) # 创建 backup.tzst (标准化)
```
### 🗜️ 压缩级别
Zstandard 压缩级别范围从 1(最快)到 22(最佳压缩):
- **级别 1-3**:快速压缩,文件较大
- **级别 3**(默认):速度与压缩率的良好平衡
- **级别 10-15**:更好的压缩率,速度较慢
- **级别 20-22**:最高压缩率,速度显著变慢
### 🌊 流模式
使用流模式实现大归档文件的内存高效处理:
**✅ 优势:**
- 显著降低内存使用
- 对内存无法容纳的大文件性能更好
- 资源自动清理
**🎯 适用场景:**
- 大于 100MB 的归档文件
- 内存有限的环境
- 处理包含多个大文件的归档
```python
# 示例:处理大型备份归档
from tzst import extract_archive, list_archive, test_archive
large_archive = "backup_500gb.tzst"
# 内存高效操作
is_valid = test_archive(large_archive, streaming=True)
contents = list_archive(large_archive, streaming=True, verbose=True)
extract_archive(large_archive, "restore_dir/", streaming=True)
```
### ⚡ 原子操作
所有文件创建操作默认使用原子操作:
- 归档先在临时文件创建,然后原子移动
- 进程中断时自动清理
- 无损坏或不完整归档风险
- 跨平台兼容
```python
# 默认启用原子操作
create_archive("important.tzst", files) # 中断时安全
# 可禁用(不推荐)
create_archive("test.tzst", files, use_temp_file=False)
```
### 🚨 错误处理
```python
from tzst import TzstArchive
from tzst.exceptions import (
TzstError,
TzstArchiveError,
TzstCompressionError,
TzstDecompressionError,
TzstFileNotFoundError
)
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract()
except TzstDecompressionError:
print("Failed to decompress archive")
except TzstFileNotFoundError:
print("Archive file not found")
except KeyboardInterrupt:
print("Operation interrupted by user")
# Cleanup handled automatically
```
## 🚀 性能与对比
### 💡 性能优化建议
1. **🗜️ 压缩级别**:级别 3 适用于大多数场景
2. **🌊 流模式**:归档大于 100MB 时使用
3. **📦 批量操作**:单次会话添加多个文件
4. **📄 文件类型**:已压缩文件不会进一步压缩
### 🆚 与其他工具对比
**对比 tar + gzip:**
- ✅ 更好的压缩率
- ⚡ 更快的解压速度
- 🔄 现代算法
**对比 tar + xz:**
- 🚀 显著更快的压缩速度
- 📊 相似的压缩率
- ⚖️ 更好的速度/压缩率平衡
**对比 zip:**
- 🗜️ 更好的压缩率
- 🔐 保留 Unix 权限和元数据
- 🌊 更好的流处理支持
## 📋 系统要求
- 🐍 Python 3.12 或更高版本
- 📦 zstandard >= 0.19.0
## 🛠️ 开发指南
### 🚀 设置开发环境
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
### 🧪 运行测试
```bash
# 带覆盖率的测试
pytest --cov=tzst --cov-report=html
# 简化命令 (覆盖配置在 pyproject.toml)
pytest
```
### ✨ 代码质量
```bash
# 代码检查
ruff check src tests
# 代码格式化
ruff format src tests
```
## 🤝 贡献指南
欢迎贡献!请阅读[贡献指南](CONTRIBUTING.md)了解:
- 开发设置和项目结构
- 代码风格指南和最佳实践
- 测试要求和编写测试
- PR流程和审核规范
### 🚀 贡献者快速入门
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
python -m pytest tests/
```
### 🎯 欢迎贡献类型
- 🐛 **缺陷修复** - 修复现有功能问题
- ✨ **新功能** - 扩展库的功能
- 📚 **文档** - 改进或新增文档
- 🧪 **测试** - 增加或改进测试覆盖
- ⚡ **性能** - 优化现有代码
- 🔒 **安全** - 修复安全漏洞
## 🙏 致谢
- [Meta Zstandard](https://github.com/facebook/zstd) 提供的优秀压缩算法
- [python-zstandard](https://github.com/indygreg/python-zstandard) 的 Python 绑定
- Python 社区的宝贵反馈和启发
## 📄 许可证
版权所有 &copy; 2025 [Xi Xu](https://xi-xu.me)。保留所有权利。
采用 [BSD 3-Clause](LICENSE) 许可证授权。
+1 -1
View File
@@ -1,6 +1,6 @@
# Sphinx build outputs
_build/
_build_simple/
_build_*/
# Sphinx auto-generated files
_autosummary/
+76
View File
@@ -0,0 +1,76 @@
---
myst:
html_meta:
description: "Page not found - tzst documentation. Return to the main documentation or search for what you're looking for."
keywords: "404, page not found, tzst documentation, error"
og:title: "Page Not Found - tzst Documentation"
og:description: "The requested page could not be found. Visit the tzst documentation homepage or use the search feature."
twitter:title: "Page Not Found - tzst Documentation"
twitter:description: "The requested page could not be found. Visit the tzst documentation homepage or use the search feature."
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# 404 - Page Not Found
## Oops! The page you're looking for doesn't exist
The URL you requested could not be found in the tzst documentation. This might happen if:
- The page has been moved or renamed
- You followed a broken link
- There's a typo in the URL
- The page has been removed
## Where would you like to go?
### Popular Pages
- **{doc}`index`** - Documentation homepage
- **{doc}`quickstart`** - Get started with tzst
- **{doc}`performance`** - Learn about performance optimizations
- **{doc}`examples`** - Practical examples and use cases
- **{doc}`api/index`** - Complete API reference
- **{doc}`development`** - Contributing to tzst
- **{ref}`genindex`** - Index of all documented items
### Quick Navigation
- **Installation Guide** - {ref}`installation`
- **Basic Usage** - {ref}`basic-usage`
- **Security Features** - {ref}`security-and-filtering`
- **Error Handling** - {ref}`error-handling`
## Search Documentation
Use the search box in the top navigation to find what you're looking for, or browse through these sections:
### Core Features
- **{doc}`api/core`** - Core TzstArchive class and functions
- **{doc}`api/cli`** - Command-line interface
- **{doc}`api/exceptions`** - Exception handling
### Examples & Tutorials
- **Archive Creation** - {ref}`basic-archive-operations`
- **Security Best Practices** - {ref}`security-and-filtering`
- **Integration Examples** - {ref}`integration-examples`
- **Performance Optimization** - {ref}`performance-optimization`
## Additional Resources
- [GitHub Repository](https://github.com/xixu-me/tzst) - Source code and issue tracker
- [PyPI Package](https://pypi.org/project/tzst/) - Download and installation
- [Release Notes](https://github.com/xixu-me/tzst/releases) - Latest updates and changes
## Report an Issue
If you believe this is a broken link within our documentation, please [report it on GitHub](https://github.com/xixu-me/tzst/issues).
---
**Need help?** Check our {doc}`quickstart` guide or browse the {doc}`examples` for common use cases.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

+42
View File
@@ -0,0 +1,42 @@
{% extends "!layout.html" %} {% block extrahead %} {{ super() }}
<!-- Additional SEO and social meta tags -->
<meta name="application-name" content="tzst" />
<meta name="generator" content="Sphinx {{ sphinx_version }}" />
<!-- Schema.org markup for search engines -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "tzst",
"description": "A Python library for creating and extracting tar.zst archives with high performance and comprehensive features",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Cross-platform",
"programmingLanguage": "Python",
"license": "https://opensource.org/licenses/MIT",
"url": "https://tzst.xi-xu.me/",
"downloadUrl": "https://pypi.org/project/tzst/",
"softwareVersion": "{{ version }}",
"author": {
"@type": "Person",
"name": "Xi Xu"
},
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
</script>
<!-- Canonical URL for better SEO -->
{% if pagename != 'index' %}
<link rel="canonical" href="https://tzst.xi-xu.me/{{ pagename }}.html" />
{% else %}
<link rel="canonical" href="https://tzst.xi-xu.me/" />
{% endif %}
<!-- Preconnect to external domains for performance -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://cdnjs.cloudflare.com" />
{% endblock %}
+179 -1
View File
@@ -1,14 +1,53 @@
---
myst:
html_meta:
description: "tzst CLI API - Command-line interface functions and utilities for tar.zst archive operations"
keywords: "tzst CLI API, command line interface, Python CLI, tar.zst commands"
og:title: "tzst CLI API Reference"
og:description: "CLI API documentation for tzst - Command-line interface functions and utilities"
twitter:title: "tzst CLI API Reference"
twitter:description: "CLI API documentation for tzst - Command-line interface functions and utilities"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# CLI API
The command-line interface module provides functions for the tzst CLI tool.
The command-line interface module provides comprehensive functionality for the tzst CLI tool, including argument parsing, command execution, and interactive features.
```{eval-rst}
.. automodule:: tzst.cli
:members:
:undoc-members:
:show-inheritance:
:no-index:
```
## Overview
The tzst CLI provides a powerful command-line interface for archive operations with intuitive commands and comprehensive options. The interface is designed for both interactive use and scripting, with robust error handling and user-friendly output.
### Core Commands
| Command | Aliases | Description | Streaming Support |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Create or add to archive | N/A |
| `x` | `extract` | Extract with full paths | `--streaming` |
| `e` | `extract-flat` | Extract without directory structure | `--streaming` |
| `l` | `list` | List archive contents | `--streaming` |
| `t` | `test` | Test archive integrity | `--streaming` |
### Key Features
- **Intuitive Commands**: Simple, memorable command aliases (a, x, e, l, t)
- **Streaming Support**: Memory-efficient processing for large archives
- **Interactive Conflict Resolution**: User-friendly prompts for handling file conflicts
- **Comprehensive Options**: Fine-grained control over compression, extraction, and security
- **Cross-Platform**: Consistent behavior across Windows, macOS, and Linux
## Main Functions
### main
@@ -17,12 +56,118 @@ The command-line interface module provides functions for the tzst CLI tool.
.. autofunction:: tzst.cli.main
```
The main entry point for the CLI application. Handles argument parsing, command execution, and comprehensive error reporting.
**Key Features:**
- Robust argument validation and error handling
- Support for all archive operations
- Consistent exit codes for scripting
- User-friendly error messages
**Exit Codes:**
- `0`: Success
- `1`: General error (file not found, archive corruption, etc.)
- `2`: Argument parsing error
- `130`: Interrupted by user (Ctrl+C)
### create_parser
```{eval-rst}
.. autofunction:: tzst.cli.create_parser
```
Creates and configures the comprehensive argument parser for the CLI interface.
**Supported Arguments:**
- **Global**: `--version`, `--help`
- **Archive Creation**: `-l/--level`, `--no-atomic`
- **Extraction**: `-o/--output`, `--streaming`, `--filter`, `--conflict-resolution`
- **Listing**: `-v/--verbose`, `--streaming`
- **Testing**: `--streaming`
## Command Handlers
The CLI implements dedicated command handlers for each operation, providing specialized functionality and error handling.
### Archive Creation Commands
#### cmd_add
Creates new archives from files and directories with configurable compression and atomic operations.
**Features:**
- Configurable compression levels (1-22)
- Atomic file operations (default) for safe creation
- Recursive directory processing
- Path validation and normalization
**Usage Examples:**
```bash
# Basic archive creation
tzst a backup.tzst documents/ photos/
# High compression with atomic disabled
tzst a backup.tzst files/ -l 15 --no-atomic
```
### Extraction Commands
#### cmd_extract_full
Extracts archives preserving complete directory structure with advanced conflict resolution.
**Features:**
- Preserves full directory paths
- Multiple conflict resolution strategies
- Security filters for safe extraction
- Selective file extraction
- Streaming mode for large archives
#### cmd_extract_flat
Extracts archives flattening all files to a single directory, useful for consolidating files.
**Features:**
- Flattens directory structure
- Automatic conflict resolution for filename collisions
- Preserves file content while simplifying structure
- Same security and streaming features as full extraction
### Management Commands
#### cmd_list
Lists archive contents with optional detailed information and streaming support.
**Features:**
- Simple or verbose listing modes
- Human-readable file sizes
- Modification timestamps
- Streaming mode for memory efficiency
#### cmd_test
Tests archive integrity and validity with comprehensive error reporting.
**Features:**
- Complete archive validation
- Streaming mode support
- Detailed error reporting
- Exit codes for automated testing
#### cmd_version
Displays version information and system details.
## Utility Functions
### print_banner
@@ -31,14 +176,47 @@ The command-line interface module provides functions for the tzst CLI tool.
.. autofunction:: tzst.cli.print_banner
```
Displays the application banner with version and copyright information.
### format_size
```{eval-rst}
.. autofunction:: tzst.cli.format_size
```
Formats file sizes in a human-readable format (bytes, KB, MB, GB).
### validate_compression_level
```{eval-rst}
.. autofunction:: tzst.cli.validate_compression_level
```
Validates compression level arguments and converts them to integers.
## Interactive Features
The CLI includes interactive conflict resolution for file extraction conflicts, allowing users to choose how to handle existing files during extraction operations.
### Conflict Resolution Options
- **Replace**: Overwrite the existing file
- **Skip**: Keep the existing file, skip extraction
- **Replace All**: Apply replace to all subsequent conflicts
- **Skip All**: Apply skip to all subsequent conflicts
- **Auto-rename All**: Automatically rename conflicting files
- **Exit**: Stop extraction process
### Security Considerations
The CLI implements multiple security filters for safe extraction:
- **`data` filter** (default): Safest option, blocks potentially dangerous archive members
- **`tar` filter**: Preserves more tar features while maintaining basic security
- **`fully_trusted` filter**: No restrictions, use only with completely trusted archives
### Performance Options
- **Streaming Mode**: Use `--streaming` for memory-efficient processing of large archives (>100MB)
- **Compression Levels**: Choose from 1 (fastest) to 22 (maximum compression)
- **Atomic Operations**: Default behavior uses temporary files for safe archive creation
+96 -3
View File
@@ -1,17 +1,34 @@
---
myst:
html_meta:
description: "tzst Core API - TzstArchive class and convenience functions for tar.zst archive operations"
keywords: "tzst core API, TzstArchive, Python archive class, tar.zst functions"
og:title: "tzst Core API Reference"
og:description: "Core API documentation for tzst - TzstArchive class and convenience functions"
twitter:title: "tzst Core API Reference"
twitter:description: "Core API documentation for tzst - TzstArchive class and convenience functions"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# Core API
The core module provides the main functionality for working with tzst archives.
The core module provides the main functionality for working with tzst archives, including the primary `TzstArchive` class and high-level convenience functions.
```{eval-rst}
.. automodule:: tzst.core
:members:
:undoc-members:
:show-inheritance:
:no-index:
```
## TzstArchive Class
The main class for handling `.tzst`/`.tar.zst` archives.
The main class for handling `.tzst`/`.tar.zst` archives with comprehensive functionality for creation, extraction, and manipulation.
```{eval-rst}
.. autoclass:: tzst.TzstArchive
@@ -21,9 +38,32 @@ The main class for handling `.tzst`/`.tar.zst` archives.
:special-members: __init__, __enter__, __exit__
```
### Key Features
- **Context Manager Support**: Use with `with` statements for automatic resource management
- **Multiple Access Modes**: Read ('r'), write ('w'), and append ('a') modes
- **Streaming Support**: Memory-efficient processing for large archives
- **Security Features**: Built-in protection against path traversal attacks
- **Flexible Extraction**: Support for selective extraction and conflict resolution
### Usage Examples
```python
# Create a new archive
with TzstArchive("backup.tzst", "w", compression_level=6) as archive:
archive.add("important_file.txt")
archive.add("documents/", recursive=True)
# Read an existing archive
with TzstArchive("backup.tzst", "r") as archive:
contents = archive.list(verbose=True)
is_valid = archive.test()
archive.extractall("restore/")
```
## Convenience Functions
High-level functions for common archive operations.
High-level functions for common archive operations without needing to instantiate the `TzstArchive` class directly.
### create_archive
@@ -31,20 +71,73 @@ High-level functions for common archive operations.
.. autofunction:: tzst.create_archive
```
Creates a new tzst archive from the specified files and directories.
**Key Features:**
- Configurable compression levels (1-22)
- Atomic creation using temporary files
- Automatic path validation and normalization
- Support for both files and directories
### extract_archive
```{eval-rst}
.. autofunction:: tzst.extract_archive
```
Extracts files from a tzst archive with advanced options for handling conflicts and filtering.
**Key Features:**
- Selective extraction with member filtering
- Multiple conflict resolution strategies
- Flatten option to extract all files to a single directory
- Streaming mode for memory efficiency
- Security filters to prevent path traversal attacks
### list_archive
```{eval-rst}
.. autofunction:: tzst.list_archive
```
Lists the contents of a tzst archive with optional detailed information.
**Returns:**
- List of dictionaries containing file information
- Each entry includes name, size, modification time, and type
- Verbose mode provides additional metadata
### test_archive
```{eval-rst}
.. autofunction:: tzst.test_archive
```
Tests the integrity of a tzst archive to verify it can be successfully decompressed.
**Returns:**
- `True` if the archive is valid and can be extracted
- `False` if the archive is corrupted or cannot be processed
## Enums and Supporting Classes
### ConflictResolution
Enumeration for handling file conflicts during extraction:
- `REPLACE`: Overwrite existing files
- `SKIP`: Skip existing files
- `REPLACE_ALL`: Overwrite all existing files without prompting
- `SKIP_ALL`: Skip all existing files without prompting
- `AUTO_RENAME`: Automatically rename conflicting files
- `AUTO_RENAME_ALL`: Automatically rename all conflicting files
- `ASK`: Prompt user for each conflict (interactive mode)
- `EXIT`: Stop extraction on first conflict
### ConflictResolutionState
State management class for tracking conflict resolution decisions during batch operations.
+253 -3
View File
@@ -1,22 +1,272 @@
# Exceptions
---
myst:
html_meta:
description: "Complete reference for tzst exception classes and error handling. Learn about TzstError, TzstArchiveError, and other custom exceptions for robust archive operations."
keywords: "tzst exceptions, Python exceptions, error handling, TzstError, TzstArchiveError, archive errors, compression errors"
og:title: "tzst Exceptions API Reference"
og:description: "Complete reference for tzst exception classes and error handling. Learn about TzstError, TzstArchiveError, and other custom exceptions for robust archive operations."
og:type: "article"
twitter:title: "tzst Exceptions API Reference"
twitter:description: "Complete reference for tzst exception classes and error handling. Learn about TzstError, TzstArchiveError, and other custom exceptions for robust archive operations."
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
Custom exception classes used by tzst.
# Exceptions API
Custom exception classes used by tzst for comprehensive error handling and debugging.
```{eval-rst}
.. automodule:: tzst.exceptions
:members:
:undoc-members:
:show-inheritance:
:no-index:
```
## Exception Hierarchy
## Overview
The tzst library provides a comprehensive hierarchy of exceptions to help identify and handle different types of errors that may occur during archive operations. All exceptions inherit from the base `TzstError` class, making it easy to catch all tzst-related errors with a single exception handler.
### Exception Hierarchy
```text
TzstError (base exception)
├── TzstArchiveError (archive operation failures)
├── TzstCompressionError (compression failures)
└── TzstDecompressionError (decompression failures)
```
## Exception Classes
### Base Exception
#### TzstError
```{eval-rst}
.. autoexception:: tzst.exceptions.TzstError
:members:
:show-inheritance:
```
The base exception class for all tzst operations. Catch this exception to handle any tzst-related error in your application.
**Usage:**
```python
from tzst import create_archive, TzstError
try:
create_archive("backup.tzst", ["files/"])
except TzstError as e:
print(f"tzst operation failed: {e}")
```
### Archive Operation Exceptions
#### TzstArchiveError
```{eval-rst}
.. autoexception:: tzst.exceptions.TzstArchiveError
:members:
:show-inheritance:
```
Raised when archive operations fail, such as:
- Archive file cannot be opened or created
- File permissions prevent archive access
- Archive structure is malformed
- Tar operations fail within the archive
- Atomic file operations fail during creation
**Common Scenarios:**
- Invalid archive file path
- Insufficient disk space
- File permission errors
- Corrupt archive structure
### Compression Exceptions
#### TzstCompressionError
```{eval-rst}
.. autoexception:: tzst.exceptions.TzstCompressionError
:members:
:show-inheritance:
```
Raised when compression operations fail, including:
- Invalid compression level is specified
- Disk space is insufficient during compression
- Input data cannot be compressed due to corruption
- Zstandard compression encounters an internal error
**Common Scenarios:**
- Compression level out of range (1-22)
- Insufficient disk space during compression
- Source file corruption
- Zstandard library errors
### Decompression Exceptions
#### TzstDecompressionError
```{eval-rst}
.. autoexception:: tzst.exceptions.TzstDecompressionError
:members:
:show-inheritance:
```
Raised when decompression operations fail, such as:
- Archive file is corrupted or incomplete
- Archive was not created with zstandard compression
- Decompression buffer overflows or underflows
- Archive format is invalid or unsupported
**Common Scenarios:**
- Corrupted or truncated archive files
- Non-zstandard compressed archives
- Invalid tar structure within archive
- Archive format version mismatches
## Error Handling Best Practices
### Basic Error Handling
```python
from tzst import create_archive, TzstArchiveError, TzstCompressionError
try:
create_archive("backup.tzst", ["documents/"])
except TzstCompressionError as e:
print(f"Compression failed: {e}")
except TzstArchiveError as e:
print(f"Archive operation failed: {e}")
```
### Comprehensive Error Handling
```python
from tzst import extract_archive, TzstError
try:
extract_archive("backup.tzst", "restore/")
except TzstError as e:
# Catch any tzst-related error
print(f"Operation failed: {e}")
# Perform cleanup or fallback operations
```
### Specific Exception Handling
```python
from tzst import TzstArchive, TzstDecompressionError, TzstArchiveError
def safe_extract(archive_path, output_dir):
try:
with TzstArchive(archive_path, "r") as archive:
# Test integrity first
if not archive.test():
print("Archive integrity check failed")
return False
# Extract files
archive.extractall(output_dir)
return True
except TzstDecompressionError as e:
print(f"Archive is corrupted or invalid: {e}")
return False
except TzstArchiveError as e:
print(f"Archive operation failed: {e}")
return False
except FileNotFoundError: print(f"Archive file not found: {archive_path}")
return False
except PermissionError:
print(f"Permission denied accessing: {archive_path}")
return False
```
### Logging Integration
```python
import logging
from tzst import test_archive, TzstDecompressionError, TzstError
logger = logging.getLogger(__name__)
def verify_archive(archive_path):
"""Verify archive integrity with comprehensive logging."""
try:
if test_archive(archive_path):
logger.info(f"Archive {archive_path} is valid")
return True
except TzstDecompressionError as e:
logger.error(f"Archive {archive_path} is corrupted: {e}")
except TzstError as e:
logger.error(f"tzst error for {archive_path}: {e}")
except Exception as e:
logger.error(f"Unexpected error testing {archive_path}: {e}")
return False
```
### Error Recovery Patterns
```python
from tzst import create_archive, extract_archive, TzstError
from pathlib import Path
import tempfile
import shutil
def robust_backup_and_restore(source_dir, backup_path, restore_dir):
"""Robust backup with error recovery and validation."""
temp_backup = None
try:
# Create backup with temporary file for atomicity
with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as temp_file:
temp_backup = Path(temp_file.name)
# Create archive
create_archive(temp_backup, [source_dir], compression_level=6)
# Verify archive before moving to final location
if not test_archive(temp_backup):
raise TzstArchiveError("Created archive failed integrity check")
# Move to final location atomically
shutil.move(temp_backup, backup_path)
temp_backup = None # Successfully moved
# Test restoration
extract_archive(backup_path, restore_dir)
print(f"Backup and restore completed successfully")
return True
except TzstError as e:
print(f"tzst operation failed: {e}")
# Cleanup and recovery logic
if restore_dir.exists():
shutil.rmtree(restore_dir)
return False
except Exception as e:
print(f"Unexpected error: {e}")
return False
finally:
# Cleanup temporary files
if temp_backup and temp_backup.exists():
temp_backup.unlink()
```
+83 -6
View File
@@ -1,6 +1,22 @@
---
myst:
html_meta:
description: "Complete tzst API reference - Core functions, CLI tools, and exception handling for tar.zst archives"
keywords: "tzst API, Python API documentation, tar.zst API reference, archive API"
og:title: "tzst API Reference"
og:description: "Complete API reference for tzst - Core functions, CLI tools, and exception handling"
twitter:title: "tzst API Reference"
twitter:description: "Complete API reference for tzst - Core functions, CLI tools, and exception handling"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# API Reference
This section contains the complete API documentation for tzst.
This section contains the complete API documentation for tzst, providing detailed information about classes, functions, and exceptions.
```{toctree}
:maxdepth: 2
@@ -12,13 +28,22 @@ exceptions
## Overview
The tzst library provides both high-level convenience functions and a comprehensive class-based API for working with `.tzst`/`.tar.zst` archives.
The tzst library provides both high-level convenience functions and a comprehensive class-based API for working with `.tzst`/`.tar.zst` archives. The library is designed with security, performance, and ease of use in mind.
### Main Components
- **{doc}`core`**: Core functionality including `TzstArchive` class and convenience functions
- **{doc}`cli`**: Command-line interface functions and utilities
- **{doc}`exceptions`**: Custom exception classes for error handling
- **{doc}`core`**: Core functionality including `TzstArchive` class and convenience functions for archive operations
- **{doc}`cli`**: Command-line interface functions and utilities for batch operations
- **{doc}`exceptions`**: Custom exception classes for comprehensive error handling and debugging
### Architecture Overview
The tzst library follows a layered architecture:
1. **High-Level API**: Convenience functions for common operations
2. **Class-Based API**: `TzstArchive` class for advanced control
3. **CLI Interface**: Command-line tools for interactive and scripted use
4. **Exception System**: Comprehensive error handling for robust applications
### Quick Reference
@@ -33,6 +58,8 @@ The tzst library provides both high-level convenience functions and a comprehens
TzstArchive
```
The main class for archive manipulation with context manager support and comprehensive functionality.
#### Convenience Functions
```{eval-rst}
@@ -45,7 +72,26 @@ The tzst library provides both high-level convenience functions and a comprehens
test_archive
```
#### Exceptions
High-level functions that provide simple interfaces for common archive operations.
#### CLI Functions
```{eval-rst}
.. currentmodule:: tzst.cli
.. autosummary::
:nosignatures:
main
create_parser
print_banner
format_size
validate_compression_level
```
Command-line interface utilities for interactive and batch operations.
#### Exception Classes
```{eval-rst}
.. currentmodule:: tzst.exceptions
@@ -53,6 +99,37 @@ The tzst library provides both high-level convenience functions and a comprehens
.. autosummary::
:nosignatures:
TzstError
TzstArchiveError
TzstCompressionError
TzstDecompressionError
```
Exception hierarchy for comprehensive error handling and debugging support.
## Key Features
### Security First
- Built-in path traversal protection
- Multiple security filter options
- Safe extraction by default
### High Performance
- Zstandard compression with configurable levels
- Streaming support for large archives
- Memory-efficient operations
### Developer Friendly
- Clean, Pythonic API
- Comprehensive error handling
- Context manager support
- Extensive documentation and examples
### Cross-Platform
- Works on Windows, macOS, and Linux
- Consistent behavior across platforms
- Native performance optimizations
+37 -31
View File
@@ -10,10 +10,10 @@ from pathlib import Path
def run_command(cmd, cwd=None):
"""Run a shell command and return the result.""" try:
"""Run a shell command and return the result."""
try:
result = subprocess.run(
cmd, shell=True, check=True, cwd=cwd,
capture_output=True, text=True
cmd, shell=True, check=True, cwd=cwd, capture_output=True, text=True
)
return result.returncode == 0, result.stdout, result.stderr
except subprocess.CalledProcessError as e:
@@ -33,19 +33,21 @@ def build_docs(source_dir, build_dir, watch=False):
print("Starting live reload server...")
print("Visit http://localhost:8000 to view the documentation")
print("Press Ctrl+C to stop the server")
cmd = f"sphinx-autobuild {source_dir} {build_dir} --host 0.0.0.0 --port 8000"
success, stdout, stderr = run_command(cmd)
if not success:
print("Failed to start live reload server.")
print("Make sure sphinx-autobuild is installed: pip install sphinx-autobuild")
print(
"Make sure sphinx-autobuild is installed: pip install sphinx-autobuild"
)
return False
else:
print(f"Building documentation: {source_dir} -> {build_dir}")
cmd = f"python -m sphinx -b html {source_dir} {build_dir}"
success, stdout, stderr = run_command(cmd)
if success:
print("Documentation built successfully!")
index_file = build_dir / "index.html"
@@ -63,13 +65,13 @@ def serve_docs(build_dir, port=8000):
if not build_dir.exists():
print(f"Build directory {build_dir} does not exist. Build the docs first.")
return False
print(f"Serving documentation at http://localhost:{port}")
print("Press Ctrl+C to stop the server")
cmd = f"python -m http.server {port}"
success, stdout, stderr = run_command(cmd, cwd=build_dir)
return success
@@ -77,79 +79,83 @@ def check_dependencies():
"""Check if required dependencies are installed."""
try:
import sphinx
print(f"Sphinx version: {sphinx.__version__}")
except ImportError:
print("Sphinx is not installed. Install with: pip install sphinx")
return False
try:
import tzst
print(f"tzst version: {tzst.__version__}")
except ImportError:
print("tzst package is not installed. Install with: pip install -e ..")
return False
return True
def main():
parser = argparse.ArgumentParser(description="Build and serve tzst documentation")
parser.add_argument(
"command",
"command",
choices=["build", "clean", "serve", "watch", "check"],
help="Command to execute"
help="Command to execute",
)
parser.add_argument(
"--port", "-p",
"--port",
"-p",
type=int,
default=8000,
help="Port for serving documentation (default: 8000)"
help="Port for serving documentation (default: 8000)",
)
parser.add_argument(
"--open", "-o",
"--open",
"-o",
action="store_true",
help="Open documentation in browser after building/serving"
help="Open documentation in browser after building/serving",
)
args = parser.parse_args()
# Get directories
script_dir = Path(__file__).parent
source_dir = script_dir
build_dir = script_dir / "_build"
if args.command == "check":
success = check_dependencies()
sys.exit(0 if success else 1)
elif args.command == "clean":
clean_build(build_dir)
elif args.command == "build":
if not check_dependencies():
sys.exit(1)
success = build_docs(source_dir, build_dir)
if success and args.open:
index_file = build_dir / "index.html"
webbrowser.open(f"file://{index_file.absolute()}")
sys.exit(0 if success else 1)
elif args.command == "watch":
if not check_dependencies():
sys.exit(1)
success = build_docs(source_dir, build_dir, watch=True)
sys.exit(0 if success else 1)
elif args.command == "serve":
success = serve_docs(build_dir, args.port)
if args.open:
webbrowser.open(f"http://localhost:{args.port}")
sys.exit(0 if success else 1)
+38 -3
View File
@@ -23,6 +23,7 @@ extensions = [
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"sphinx.ext.autosummary",
"sphinx.ext.coverage",
"myst_parser",
]
@@ -35,21 +36,55 @@ html_static_path = ["_static"]
html_title = f"tzst {version} Documentation"
html_short_title = "tzst"
# HTML meta tags
html_meta = {
"description": "tzst - A Python library for creating and extracting tar.zst archives with high performance and comprehensive features",
"keywords": "tzst, tar, zstandard, compression, archive, python, extraction, backup",
"author": "Xi Xu",
"robots": "index, follow",
"language": "en",
"viewport": "width=device-width, initial-scale=1.0",
"theme-color": "#2980B9",
"msapplication-TileColor": "#2980B9",
"og:title": "tzst Documentation",
"og:description": "tzst - A Python library for creating and extracting tar.zst archives with high performance and comprehensive features",
"og:type": "website",
"og:url": "https://tzst.xi-xu.me/",
"og:image": "https://tzst.xi-xu.me/_static/tzst-logo.png",
"twitter:card": "summary_large_image",
"twitter:title": "tzst Documentation",
"twitter:description": "tzst - A Python library for creating and extracting tar.zst archives with high performance and comprehensive features",
"twitter:image": "https://tzst.xi-xu.me/_static/tzst-logo.png",
}
# Theme options
html_theme_options = {
"canonical_url": "https://xixu-me.github.io/tzst/",
"canonical_url": "https://tzst.xi-xu.me/",
"logo_only": False,
"display_version": True,
"prev_next_buttons_location": "bottom",
"style_external_links": False,
"style_nav_header_background": "#2980B9",
"collapse_navigation": True,
"sticky_navigation": True,
"navigation_depth": 4,
"includehidden": True,
"includehidden": False,
"titles_only": False,
}
# Additional HTML options
html_favicon = "_static/favicon.ico" # Will show warning until favicon is created
html_logo = "_static/tzst-logo.png" # Will show warning until logo is created
html_use_opensearch = "https://tzst.xi-xu.me/"
# HTML context for custom template variables
html_context = {
"display_github": True,
"github_user": "xixu-me",
"github_repo": "tzst",
"github_version": "main",
"conf_py_path": "/docs/",
}
# -- Extension configuration -------------------------------------------------
autodoc_default_options = {
"members": True,
+353
View File
@@ -0,0 +1,353 @@
# Development Guide
This guide provides comprehensive information for developers contributing to or working with the tzst library.
## Setting up Development Environment
This project uses modern Python packaging standards:
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
```
The development installation includes all necessary tools:
- **pytest** - Testing framework
- **ruff** - Linting and formatting
- **coverage** - Code coverage analysis
- **sphinx** - Documentation generation
## Running Tests
### Basic Test Commands
```bash
# Run all tests
python -m pytest
# Run tests with coverage
pytest --cov=tzst --cov-report=html
# Or use the simpler command (coverage settings are in pyproject.toml)
pytest
# Run with verbose output
python -m pytest -v
# Run specific test file
python -m pytest tests/test_core.py
# Run integration tests only
python -m pytest -m integration
```
### Test Structure
- **Unit tests**: Test individual functions and methods
- **Integration tests**: Test component interactions
- **CLI tests**: Test command-line interface
- **Platform-specific tests**: Test OS-specific functionality
### Writing Tests
1. **Use descriptive test names:**
```python
def test_create_archive_with_compression_level_9():
```
2. **Use fixtures for common test data:**
```python
def test_extract_archive(sample_archive_path, temp_dir):
```
3. **Test edge cases:**
- Empty files
- Large files
- Invalid inputs
- Corrupted archives
4. **Add markers for test categorization:**
```python
@pytest.mark.integration
def test_full_archive_workflow():
```
## Code Quality
### Running Code Style Tools
```bash
# Check code quality
ruff check src tests
# Fix auto-fixable issues
ruff check --fix src tests
# Format code
ruff format src tests
# Check formatting without making changes
ruff format --check src tests
```
### Configuration
Settings are defined in `pyproject.toml`:
- Line length: 88 characters
- Target Python version: 3.12+
- Import sorting with isort
- Quote style: double quotes
### Code Style Guidelines
1. **Follow PEP 8** with project-specific modifications
2. **Use type hints** for all public APIs
3. **Write docstrings** for classes and public methods
4. **Keep functions focused** and reasonably sized
5. **Use meaningful variable names**
6. **Add comments** for complex logic
## Documentation
### Building Documentation
```bash
# Navigate to docs directory
cd docs
# Install documentation dependencies
pip install -r requirements.txt
# Build HTML documentation
make html
# On Windows, use:
make.bat html
# View built documentation
# Open docs/_build/html/index.html in your browser
```
### Documentation Structure
```
docs/
├── index.md # Main documentation landing page
├── quickstart.md # Getting started guide
├── performance.md # Performance guide and comparisons
├── examples.md # Usage examples
├── development.md # This development guide
├── api/ # API reference documentation
│ ├── index.md
│ ├── core.md
│ ├── cli.md
│ └── exceptions.md
├── conf.py # Sphinx configuration
└── requirements.txt # Documentation dependencies
```
### Writing Documentation
- Use **MyST Markdown** format
- Include **code examples** for new features
- Add **cross-references** using proper syntax
- Test all **code snippets** to ensure they work
## Project Structure
```
tzst/
├── src/tzst/ # Main package source code
│ ├── __init__.py # Package initialization and exports
│ ├── __main__.py # CLI entry point
│ ├── cli.py # Command-line interface
│ ├── core.py # Core archive functionality
│ └── exceptions.py # Custom exceptions
├── tests/ # Test suite
│ ├── conftest.py # Pytest configuration and fixtures
│ ├── test_core.py # Core functionality tests
│ ├── test_cli.py # CLI tests
│ └── test_*.py # Additional test modules
├── docs/ # Documentation source
├── .github/ # GitHub workflows and templates
├── pyproject.toml # Project configuration
├── README.md # Project Readme
├── LICENSE # BSD 3-Clause License
└── CONTRIBUTING.md # Contribution guidelines
```
## Contributing Workflow
### 1. Making Changes
#### Types of Contributions
- **Bug fixes**: Fix issues in existing functionality
- **Features**: Add new capabilities to the library
- **Documentation**: Improve or add documentation
- **Tests**: Add or improve test coverage
- **Performance**: Optimize existing code
- **Security**: Address security vulnerabilities
#### Branch Naming
Use descriptive branch names:
- `feature/add-streaming-mode`
- `fix/handle-corrupted-archives`
- `docs/improve-api-documentation`
- `test/add-compression-tests`
### 2. Commit Messages
Follow conventional commit format:
```
type(scope): description
[optional body]
[optional footer]
```
**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `test`: Adding or modifying tests
- `refactor`: Code refactoring
- `perf`: Performance improvements
- `chore`: Build process or auxiliary tool changes
**Examples:**
```
feat(core): add streaming compression support
fix(cli): handle invalid archive paths gracefully
docs(readme): update installation instructions
```
### 3. Pull Request Process
1. **Create a feature branch:**
```bash
git checkout -b feature/your-feature-name
```
2. **Make your changes** following the guidelines above
3. **Add tests** for new functionality
4. **Update documentation** if needed
5. **Run the test suite:**
```bash
python -m pytest
ruff check .
ruff format --check .
```
6. **Commit your changes:**
```bash
git add .
git commit -m "feat: add your feature description"
```
7. **Push to your fork:**
```bash
git push origin feature/your-feature-name
```
8. **Create a pull request** using the provided template
### 4. Pull Request Guidelines
- **Fill out the PR template** completely
- **Link related issues** using keywords (fixes #123)
- **Keep PRs focused** - one feature/fix per PR
- **Ensure all CI checks pass**
- **Respond to review feedback** promptly
## Development Tips
### Performance Considerations
- Use streaming for large files
- Consider memory usage patterns
- Profile code for bottlenecks
- Test with various file sizes
### Security Considerations
- Validate all user inputs
- Use secure defaults (e.g., 'data' filter)
- Handle malicious archives safely
- Be cautious with file paths
### Compatibility
- Support Python 3.12+
- Test on multiple platforms (Windows, macOS, Linux)
- Consider different filesystem behaviors
- Maintain backwards compatibility when possible
## Release Process
Releases are handled by maintainers:
1. Update version in `src/tzst/__init__.py`
2. Create a release tag
3. Automated CI/CD publishes to PyPI
## Getting Help
### Resources
- **Issues**: [GitHub Issues](https://github.com/xixu-me/tzst/issues)
- **Discussions**: Use GitHub Discussions for questions
- **Documentation**: Check the README and code comments
### Reporting Issues
When reporting bugs:
1. **Use the bug report template**
2. **Provide a minimal reproduction case**
3. **Include system information** (OS, Python version)
4. **Attach relevant files** if possible (archives, logs)
### Suggesting Features
When suggesting features:
1. **Use the feature request template**
2. **Explain the use case** and motivation
3. **Consider backwards compatibility**
4. **Provide implementation ideas** if you have them
## Code of Conduct
This project follows the principles of respectful collaboration. Please be kind, constructive, and professional in all interactions.
## Recognition
Contributors are recognized in several ways:
- Listed in release notes for significant contributions
- Mentioned in README acknowledgments
- GitHub contributor statistics
Thank you for contributing to tzst! Your efforts help make this library better for everyone.
+1094 -452
View File
File diff suppressed because it is too large. Load diff
+204 -24
View File
@@ -1,5 +1,29 @@
---
myst:
html_meta:
description: "tzst - Next-generation Python library for tar.zst archives with Zstandard compression. Fast, secure, and reliable archive management."
keywords: "tzst, Python, tar.zst, Zstandard, compression, archive, backup, file management"
og:title: "tzst - Next-Generation Archive Management"
og:description: "Fast, secure, and reliable Python library for tar.zst archives with Zstandard compression"
twitter:title: "tzst - Next-Generation Archive Management"
twitter:description: "Fast, secure, and reliable Python library for tar.zst archives with Zstandard compression"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# tzst Documentation
[![codecov](https://codecov.io/gh/xixu-me/tzst/graph/badge.svg?token=2AIN1559WU)](https://codecov.io/gh/xixu-me/tzst)
[![CodeQL](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
[![CI/CD](https://github.com/xixu-me/tzst/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/tzst)](https://pypi.org/project/tzst/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/tzst)](https://pypi.org/project/tzst/)
[![GitHub License](https://img.shields.io/github/license/xixu-me/tzst)](https://github.com/xixu-me/tzst/blob/main/LICENSE)
[![Sponsor](https://img.shields.io/badge/Sponsor-violet)](https://xi-xu.me/#sponsorships)
Welcome to **tzst**, the next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability.
```{toctree}
@@ -7,55 +31,211 @@ Welcome to **tzst**, the next-generation Python library engineered for modern ar
:caption: Contents:
quickstart
api/index
performance
examples
changelog
api/index
development
genindex
```
```{toctree}
:hidden:
404
README
```
## What is tzst?
**tzst** is a Python library built exclusively for Python 3.12+ that provides enterprise-grade solutions for handling `.tzst`/`.tar.zst` archives. It combines atomic operations, streaming efficiency, and a meticulously crafted API to redefine how developers handle compressed archives in production environments.
**tzst** is a modern Python library built exclusively for Python 3.12+ that provides comprehensive support for creating, extracting, and managing `.tzst` and `.tar.zst` archives. It combines the proven reliability of the tar format with the superior compression efficiency of Zstandard (zstd) to deliver:
- **Superior Performance**: Fast compression and decompression with excellent compression ratios
- **Enterprise-Grade Security**: Safe extraction with built-in protections against path traversal attacks
- **Memory Efficiency**: Streaming mode for handling large archives with minimal memory usage
- **Cross-Platform Compatibility**: Works seamlessly on Windows, macOS, and Linux
- **Developer-Friendly**: Clean, Pythonic API with comprehensive error handling
## Key Features
- **🚀 High Performance**: Leverages Zstandard compression for superior speed and compression ratios
- **🔒 Security First**: Built-in extraction filters protect against malicious archives
- **⚡ Streaming Support**: Memory-efficient handling of large archives
- **🛡️ Atomic Operations**: Ensures data integrity with fail-safe file operations
- **🎯 Modern API**: Clean, intuitive interface designed for Python 3.12+
- **📦 CLI Tools**: Comprehensive command-line interface for everyday tasks
### Advanced Compression
- **Zstandard Compression**: Best-in-class compression algorithm with configurable levels (1-22)
- **Multiple Extensions**: Support for both `.tzst` and `.tar.zst` file extensions
- **Streaming Support**: Memory-efficient processing for large archives
### Security First
- **Safe by Default**: Uses 'data' filter for secure extraction without dangerous path traversal
- **Multiple Filter Options**: Choose from 'data', 'tar', or 'fully_trusted' filters based on your security needs
- **Atomic Operations**: All file operations use temporary files with atomic moves to prevent corruption
### Dual Interfaces
- **Command Line**: Intuitive CLI with comprehensive options for batch operations
- **Python API**: Clean, object-oriented interface for programmatic use
- **Convenience Functions**: High-level functions for common operations
### High Performance
- **Optimized I/O**: Efficient buffering and streaming for large files
- **Conflict Resolution**: Intelligent handling of file conflicts during extraction
- **Cross-Platform**: Native performance on all major operating systems
## Quick Example
```python
from tzst import TzstArchive
from tzst import TzstArchive, create_archive, extract_archive
# Create a new archive
with TzstArchive("backup.tzst", "w", compression_level=5) as archive:
archive.add("documents/")
archive.add("photos/", recursive=True)
# Create an archive
create_archive("backup.tzst", ["documents/", "photos/"], compression_level=5)
# Extract with security
with TzstArchive("backup.tzst", "r") as archive:
archive.extract("documents/", filter="data")
# Extract an archive
extract_archive("backup.tzst", "restore/")
# Work with archives programmatically
with TzstArchive("data.tzst", "r") as archive:
contents = archive.list(verbose=True)
archive.extract("important.txt", "output/")
is_valid = archive.test()
```
## Installation
Install tzst from PyPI:
### From PyPI
```bash
pip install tzst
```
### From GitHub Releases
Download platform-specific standalone executables from [GitHub Releases](https://github.com/xixu-me/tzst/releases) - no Python installation required!
#### Supported Platforms
| Platform | Architecture | File |
|----------|-------------|------|
| **Linux** | x86_64 | `tzst-v{version}-linux-x86_64.zip` |
| **Linux** | ARM64 | `tzst-v{version}-linux-aarch64.zip` |
| **Windows** | x64 | `tzst-v{version}-windows-amd64.zip` |
| **Windows** | ARM64 | `tzst-v{version}-windows-arm64.zip` |
| **macOS** | Intel | `tzst-v{version}-macos-x86_64.zip` |
| **macOS** | Apple Silicon | `tzst-v{version}-macos-arm64.zip` |
#### Installation Steps
1. **Download** the appropriate archive for your platform from the [latest releases page](https://github.com/xixu-me/tzst/releases/latest)
2. **Extract** the archive to get the `tzst` executable (or `tzst.exe` on Windows)
3. **Move** the executable to a directory in your PATH:
- **Linux/macOS**: `sudo mv tzst /usr/local/bin/`
- **Windows**: Add the directory containing `tzst.exe` to your PATH environment variable
4. **Verify** installation: `tzst --help`
#### Benefits of Binary Installation
- **No Python required** - Standalone executable
- **Faster startup** - No Python interpreter overhead
- **Easy deployment** - Single file distribution
- **Consistent behavior** - Bundled dependencies
### Using uvx (No Installation)
Run tzst directly without installation using [uvx](https://docs.astral.sh/uv/):
```bash
uvx tzst --help
uvx tzst a archive.tzst file1.txt file2.txt directory/
uvx tzst x archive.tzst
```
Perfect for one-time usage, testing, CI/CD pipelines, and isolated environments.
### From Source
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
## Getting Started
For a quick introduction to using tzst, see the {doc}`quickstart` guide.
For a quick introduction, see the {doc}`quickstart` guide. For comprehensive usage examples, explore the {doc}`examples` section.
For detailed API documentation, browse the {doc}`api/index` section.
### Installation Options
## Indices and tables
1. **PyPI Installation**: `pip install tzst`
2. **Standalone Binaries**: Download from [GitHub Releases](https://github.com/xixu-me/tzst/releases)
3. **uvx (No Installation)**: Run directly with `uvx tzst`
4. **From Source**: Clone and install from repository
- {ref}`genindex`
- {ref}`modindex`
- {ref}`search`
### API Documentation
Complete API documentation is available in the {doc}`api/index` section, covering:
- {doc}`api/core`: Main classes and functions
- {doc}`api/cli`: Command-line interface
- {doc}`api/exceptions`: Error handling
## Development
For comprehensive development information, see the {doc}`development` guide, which covers:
- Setting up development environment
- Running tests and code quality checks
- Documentation building
- Contributing workflow and guidelines
- Project structure and best practices
### Quick Start
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install -e .[dev]
pytest
```
## Contributing
We welcome contributions! Please read our [Contributing Guide](https://github.com/xixu-me/tzst/blob/main/CONTRIBUTING.md) for:
- Development setup and project structure
- Code style guidelines and best practices
- Testing requirements and writing tests
- Pull request process and review workflow
### Types of Contributions Welcome
- **Bug fixes** - Fix issues in existing functionality
- **Features** - Add new capabilities to the library
- **Documentation** - Improve or add documentation
- **Tests** - Add or improve test coverage
- **Performance** - Optimize existing code
- **Security** - Address security vulnerabilities
## Acknowledgments
- [Meta Zstandard](https://github.com/facebook/zstd) for the excellent compression algorithm
- [python-zstandard](https://github.com/indygreg/python-zstandard) for Python bindings
- The Python community for inspiration and feedback
## License
Copyright © 2025 [Xi Xu](https://xi-xu.me). All rights reserved.
Licensed under the [BSD 3-Clause](https://github.com/xixu-me/tzst/blob/main/LICENSE) license.
## Documentation Guide
1. **{doc}`quickstart`** - Get up and running quickly with basic examples
2. **{doc}`performance`** - Performance optimization guide and comparisons
3. **{doc}`examples`** - Comprehensive usage examples and patterns
4. **{doc}`api/index`** - Complete API reference documentation
5. **{doc}`development`** - Development and contribution guidelines
6. **{ref}`genindex`** - Index of all documented items
## Requirements
- Python 3.12 or higher
- zstandard >= 0.19.0
+283
View File
@@ -0,0 +1,283 @@
---
myst:
html_meta:
description: "tzst Performance Guide - Compression level optimization, performance tips, and comparison with other archive tools"
keywords: "tzst performance, compression benchmarks, tar gzip comparison, archive performance optimization"
og:title: "tzst Performance Guide"
og:description: "Performance optimization tips and comparison with other archive tools for tzst"
twitter:title: "tzst Performance Guide"
twitter:description: "Performance optimization tips and comparison with other archive tools for tzst"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# Performance Guide
This guide covers performance optimization techniques and provides detailed comparisons with other archive tools.
## Performance Tips
### 1. Compression Levels
Choose the right compression level for your use case:
- **Level 1-3**: Fast compression, larger files (good for temporary archives or real-time processing)
- **Level 3** (default): Optimal balance for most use cases
- **Level 6-9**: Higher compression, moderate speed (good for regular backups)
- **Level 15-22**: Maximum compression, slower (for long-term storage or bandwidth-limited scenarios)
```python
from tzst import create_archive
# For temporary files or frequent operations
create_archive("temp.tzst", files, compression_level=1)
# Balanced default (recommended)
create_archive("backup.tzst", files, compression_level=3)
# Long-term storage
create_archive("archive.tzst", files, compression_level=9)
# Maximum compression for critical space savings
create_archive("minimal.tzst", files, compression_level=22)
```
### 2. Streaming
Use streaming mode for archives larger than 100MB:
```python
from tzst import extract_archive, list_archive, test_archive
# Memory-efficient operations for large archives
extract_archive("large-backup.tzst", "restore/", streaming=True)
contents = list_archive("large-backup.tzst", streaming=True)
is_valid = test_archive("large-backup.tzst", streaming=True)
```
**Streaming Benefits:**
- Significantly reduced memory usage
- Better performance for large archives
- Handles archives that don't fit in memory
### 3. Batch Operations
Add multiple files in a single session when possible:
```python
from tzst import TzstArchive
# Efficient: Single archive session
with TzstArchive("backup.tzst", "w") as archive:
archive.add("file1.txt")
archive.add("file2.txt")
archive.add("directory/", recursive=True)
# Less efficient: Multiple separate operations
create_archive("backup1.tzst", ["file1.txt"])
create_archive("backup2.tzst", ["file2.txt"])
```
### 4. File Type Considerations
- Already compressed files (`.jpg`, `.png`, `.mp4`, `.pdf`) won't compress much further
- Text files, source code, and logs compress very well
- Consider compression level based on your data types
## Comparison with Other Tools
### vs tar + gzip
**tzst Advantages:**
- **Better compression ratios**: 10-40% smaller archives
- **Faster decompression**: 2-3x faster extraction
- **Modern algorithm**: Better handling of various file types
- **Streaming support**: Better memory efficiency
**When to use tar + gzip:**
- Legacy system compatibility requirements
- Very old systems without zstd support
### vs tar + xz
**tzst Advantages:**
- **Significantly faster compression**: 3-10x faster creation
- **Faster decompression**: 2-4x faster extraction
- **Better speed/compression trade-off**: Similar compression with much better speed
- **More compression levels**: Fine-grained control (22 levels vs 9)
**When to use tar + xz:**
- Maximum compression is critical and time is not a factor
- Systems that don't support zstd
### vs zip
**tzst Advantages:**
- **Better compression**: 15-30% smaller archives
- **Preserves Unix permissions and metadata**: Full POSIX compatibility
- **Better streaming support**: Memory-efficient for large archives
- **Better directory handling**: Preserves directory structure and timestamps
**When to use zip:**
- Cross-platform compatibility with very old systems
- Individual file access without full extraction is required
- Windows-centric environments with no command-line tools
## Benchmarking Examples
### Compression Level Benchmark
```python
import time
from pathlib import Path
from tzst import create_archive
def benchmark_compression_levels(files, output_prefix="benchmark"):
"""Compare different compression levels."""
levels_to_test = [1, 3, 6, 9, 15, 22]
results = []
for level in levels_to_test:
output_file = f"{output_prefix}_level_{level}.tzst"
# Measure compression time
start_time = time.time()
create_archive(output_file, files, compression_level=level)
compress_time = time.time() - start_time
# Get file size
file_size = Path(output_file).stat().st_size
results.append({
'level': level,
'time': compress_time,
'size': file_size,
'size_mb': file_size / (1024 * 1024)
})
print(f"Level {level}: {compress_time:.2f}s, {file_size/1024/1024:.1f} MB")
return results
# Example usage
files = ["documents/", "projects/"]
results = benchmark_compression_levels(files)
```
### Memory Usage Comparison
```python
import psutil
import os
from tzst import extract_archive
def monitor_memory_usage(func, *args, **kwargs):
"""Monitor memory usage during function execution."""
process = psutil.Process(os.getpid())
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
func(*args, **kwargs)
peak_memory = process.memory_info().rss / 1024 / 1024 # MB
return peak_memory - initial_memory
# Compare streaming vs non-streaming extraction
large_archive = "large-dataset.tzst"
memory_normal = monitor_memory_usage(extract_archive, large_archive, "output1/")
memory_streaming = monitor_memory_usage(extract_archive, large_archive, "output2/", streaming=True)
print(f"Normal extraction: {memory_normal:.1f} MB")
print(f"Streaming extraction: {memory_streaming:.1f} MB")
print(f"Memory savings: {memory_normal - memory_streaming:.1f} MB")
```
## Best Practices
### For Development
```python
# Fast compression for frequent builds
create_archive("build-artifacts.tzst", ["build/"], compression_level=1)
```
### For Backups
```python
# Balanced compression for regular backups
create_archive("daily-backup.tzst", ["data/"], compression_level=6)
```
### For Distribution
```python
# Higher compression for software distribution
create_archive("software-package.tzst", ["app/"], compression_level=9)
```
### For Archival Storage
```python
# Maximum compression for long-term storage
create_archive("archive-2024.tzst", ["historical-data/"], compression_level=22)
```
## Hardware Considerations
### CPU Usage
- Higher compression levels use more CPU but for shorter time periods
- Modern multi-core systems handle zstd compression very efficiently
- Consider system load when choosing compression levels
### Memory Usage
- Streaming mode: ~16-32 MB memory usage regardless of archive size
- Normal mode: Memory usage proportional to archive size
- Use streaming for archives >100 MB or on memory-constrained systems
### Storage
- SSDs benefit from higher compression (less I/O)
- HDDs may prefer lower compression levels (CPU vs I/O trade-off)
- Network storage benefits from higher compression (bandwidth savings)
## Integration with Build Systems
### Makefile Example
```makefile
# Fast compression for development
build-dev:
tzst a build-dev.tzst build/ -l 1
# Production compression
build-prod:
tzst a build-prod.tzst build/ -l 9
# CI/CD artifacts
artifacts:
tzst a artifacts.tzst dist/ logs/ -l 6
```
### GitHub Actions Example
```yaml
- name: Create release archive
run: |
tzst a release-${{ github.ref_name }}.tzst \
build/ docs/ \
--compression-level 9
```
This performance guide helps you choose the right settings for your specific use case and understand how tzst compares to alternative archive tools.
+356 -156
View File
@@ -1,222 +1,422 @@
---
myst:
html_meta:
description: "Quick start guide for tzst - Learn how to install and use the Python tar.zst archive library in minutes"
keywords: "tzst tutorial, Python archive tutorial, tar.zst guide, Zstandard compression guide"
og:title: "tzst Quick Start Guide"
og:description: "Learn how to install and use tzst for Python tar.zst archive management in minutes"
twitter:title: "tzst Quick Start Guide"
twitter:description: "Learn how to install and use tzst for Python tar.zst archive management in minutes"
og:type: "website"
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
og:url: "https://tzst.xi-xu.me/"
twitter:card: "summary_large_image"
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
---
# Quick Start Guide
This guide will help you get started with tzst quickly and efficiently.
This guide will get you up and running with tzst in just a few minutes.
(installation)=
## Installation
Install tzst using pip:
Choose your preferred installation method:
### Option 1: PyPI
```bash
pip install tzst
```
### Option 2: Standalone Binary
Download the appropriate executable from [GitHub Releases](https://github.com/xixu-me/tzst/releases):
| Platform | Architecture | Download |
|----------|--------------|----------|
| **Linux** | x86_64 | `tzst-v{version}-linux-x86_64.zip` |
| **Linux** | ARM64 | `tzst-v{version}-linux-aarch64.zip` |
| **Windows** | x64 | `tzst-v{version}-windows-amd64.zip` |
| **Windows** | ARM64 | `tzst-v{version}-windows-arm64.zip` |
| **macOS** | Intel | `tzst-v{version}-macos-x86_64.zip` |
| **macOS** | Apple Silicon | `tzst-v{version}-macos-arm64.zip` |
Extract the archive and add the executable to your PATH.
### Option 3: Using uvx (No Installation)
Run tzst directly without installation using [uvx](https://docs.astral.sh/uv/):
```bash
uvx tzst --help
uvx tzst a archive.tzst file1.txt file2.txt directory/
uvx tzst x archive.tzst
```
This option is perfect for:
- **One-time usage** - No permanent installation needed
- **Testing** - Try tzst without committing to installation
- **CI/CD pipelines** - Use tzst in automated workflows
- **Isolated environments** - Avoid dependency conflicts
### Option 4: From Source
```bash
git clone https://github.com/xixu-me/tzst.git
cd tzst
pip install .
```
(basic-usage)=
## Basic Usage
### Creating Archives
### Command Line Interface
Use the `TzstArchive` class or convenience functions to create archives:
> **Note**: Download the [standalone binary](installation) for the best performance and no Python dependency. Alternatively, use `uvx tzst` for running without installation. See [uv documentation](https://docs.astral.sh/uv/) for details.
```python
from tzst import TzstArchive, create_archive
# Using TzstArchive class
with TzstArchive("my_archive.tzst", "w", compression_level=5) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Using convenience function
create_archive(
archive_path="backup.tzst",
files=["documents/", "photos/", "config.txt"],
compression_level=10
)
```
### Extracting Archives
Extract archives safely with built-in security filters:
```python
from tzst import TzstArchive, extract_archive
# Using TzstArchive class
with TzstArchive("my_archive.tzst", "r") as archive:
# Extract all files with security filter
archive.extract("output/", filter="data")
# Extract specific files
archive.extract("output/", members=["file.txt"], filter="data")
# Using convenience function
extract_archive("backup.tzst", "restore/")
```
### Listing Archive Contents
View what's inside an archive:
```python
from tzst import TzstArchive, list_archive
# Using TzstArchive class
with TzstArchive("my_archive.tzst", "r") as archive:
contents = archive.list(verbose=True)
for item in contents:
print(f"{item['name']} - {item['size']} bytes")
# Using convenience function
files = list_archive("backup.tzst", verbose=True)
```
### Testing Archive Integrity
Verify that an archive is valid:
```python
from tzst import TzstArchive, test_archive
# Using TzstArchive class
with TzstArchive("my_archive.tzst", "r") as archive:
is_valid = archive.test()
print(f"Archive is {'valid' if is_valid else 'corrupted'}")
# Using convenience function
if test_archive("backup.tzst"):
print("Archive is valid")
```
## Command Line Interface
tzst provides a comprehensive CLI for archive operations:
### Creating Archives
The CLI provides four main operations:
```bash
# Create an archive with multiple files
tzst a backup.tzst documents/ photos/ config.txt
# Create an archive
tzst a archive.tzst file1.txt file2.txt directory/
# Extract an archive
tzst x archive.tzst
# List archive contents
tzst l archive.tzst
# Test archive integrity
tzst t archive.tzst
```
### Command Reference
| Command | Aliases | Description | Streaming Support |
|---------|---------|-------------|-------------------|
| `a` | `add`, `create` | Create or add to archive | N/A |
| `x` | `extract` | Extract with full paths | `--streaming` |
| `e` | `extract-flat` | Extract without directory structure | `--streaming` |
| `l` | `list` | List archive contents | `--streaming` |
| `t` | `test` | Test archive integrity | `--streaming` |
### CLI Options
- `-v, --verbose`: Enable verbose output
- `-o, --output DIR`: Specify output directory (extract commands)
- `-l, --level LEVEL`: Set compression level 1-22 (create command)
- `--streaming`: Enable streaming mode for memory-efficient processing
- `--filter FILTER`: Security filter for extraction (data/tar/fully_trusted)
- `--no-atomic`: Disable atomic file operations (not recommended)
#### Create Archives
```bash
# Create archive with default compression (level 3)
tzst a backup.tzst documents/ photos/
# Create with high compression
tzst a -l 15 backup.tzst large_files/
tzst a backup.tzst documents/ photos/ --compression-level 9
# Create without atomic operations (faster, less safe)
tzst a --no-atomic backup.tzst files/
# Create from current directory
tzst a project.tzst .
# Specify different output location
tzst a /backups/data.tzst /home/user/important/
```
### Extracting Archives
#### Extract Archives
```bash
# Extract all files (default: safe extraction)
# Extract to current directory
tzst x backup.tzst
# Extract to specific directory
tzst x backup.tzst -o restore/
tzst x backup.tzst --output /restore/
# Extract specific files only
tzst x backup.tzst config.txt documents/
tzst x backup.tzst documents/report.pdf photos/vacation.jpg
# Extract with streaming (memory efficient)
tzst x backup.tzst --streaming
# Extract with conflict resolution
tzst x backup.tzst --conflict-resolution skip
```
### Listing Contents
#### List Contents
```bash
# Simple listing
tzst l backup.tzst
# Detailed listing with file info
tzst l backup.tzst -v
tzst l backup.tzst --verbose
# Streaming mode for large archives
tzst l backup.tzst --streaming
# Stream large archives efficiently
tzst l huge-archive.tzst --streaming
```
### Testing Archives
### Python API
```bash
# Test archive integrity
tzst t backup.tzst
# Test with streaming
tzst t backup.tzst --streaming
```
## Security Considerations
tzst includes built-in security features to protect against malicious archives:
### Extraction Filters
Always use appropriate filters when extracting archives from untrusted sources:
- **`data`** (default): Safest option, only extracts regular files and directories
- **`tar`**: Honors most tar features but still secure
- **`fully_trusted`**: No restrictions (only use with completely trusted archives)
#### Quick Start
```python
# Safe extraction (recommended)
archive.extract("output/", filter="data")
from tzst import create_archive, extract_archive, list_archive, test_archive
# Command line
tzst x archive.tzst --filter=data
# Create an archive
create_archive("backup.tzst", ["documents/", "photos/"], compression_level=5)
# Extract an archive
extract_archive("backup.tzst", "restore/")
# List contents
contents = list_archive("backup.tzst", verbose=True)
for item in contents:
print(f"{item['name']} - {item['size']} bytes")
# Test integrity
is_valid = test_archive("backup.tzst")
print(f"Archive is {'valid' if is_valid else 'corrupted'}")
```
### Best Practices
1. **Always use the default `data` filter** for untrusted archives
2. **Enable atomic operations** (default) for data integrity
3. **Use streaming mode** for very large archives to save memory
4. **Validate archives** with `test()` before processing
5. **Specify output directories** explicitly to avoid overwrites
## Performance Tips
### Memory Efficiency
For large archives, use streaming mode:
#### Using the TzstArchive Class
```python
# Streaming mode uses less memory
with TzstArchive("large.tzst", "r", streaming=True) as archive:
archive.extract("output/")
from tzst import TzstArchive
# Create a new archive
with TzstArchive("data.tzst", "w", compression_level=6) as archive:
archive.add("file.txt")
archive.add("directory/", recursive=True)
# Add with custom archive name
archive.add("config/prod.yaml", arcname="config.yaml")
# Read an existing archive
with TzstArchive("data.tzst", "r") as archive:
# List contents
contents = archive.list(verbose=True)
for item in contents:
print(f"{item['name']} - {item['size']} bytes")
# Test integrity
is_valid = archive.test()
print(f"Archive is {'valid' if is_valid else 'corrupted'}")
# Extract specific files
archive.extract("file.txt", "output/")
# Extract all files
archive.extractall("restore/")
```
### Compression Levels
## Advanced Features
Choose appropriate compression levels based on your needs:
- **Level 1-3**: Fast compression, larger files
- **Level 3-6**: Balanced (default: 3)
- **Level 7-15**: Better compression, slower
- **Level 16-22**: Maximum compression, much slower
### Security and Filtering
```python
# Fast compression for temporary files
TzstArchive("temp.tzst", "w", compression_level=1)
from tzst import extract_archive
# Maximum compression for long-term storage
TzstArchive("backup.tzst", "w", compression_level=15)
# Safe extraction with built-in security (default)
extract_archive("untrusted.tzst", "safe-output/", filter="data")
# For trusted archives with special features
extract_archive("trusted.tzst", "output/", filter="tar")
```
### Security Filters
tzst provides three security filter options for extraction:
```python
from tzst import extract_archive
# Extract with maximum security (default)
extract_archive("archive.tzst", "output/", filter="data")
# Extract with standard tar compatibility
extract_archive("archive.tzst", "output/", filter="tar")
# Extract with full trust (dangerous - only for trusted archives)
extract_archive("archive.tzst", "output/", filter="fully_trusted")
```
**Security Filter Options:**
- `data` (default): Most secure. Blocks dangerous files, absolute paths, and paths outside extraction directory
- `tar`: Standard tar compatibility. Blocks absolute paths and directory traversal
- `fully_trusted`: No security restrictions. Only use with completely trusted archives
### Conflict Resolution
```python
from tzst import extract_archive, ConflictResolution
# Skip existing files
extract_archive("archive.tzst", "output/",
conflict_resolution=ConflictResolution.SKIP_ALL)
# Auto-rename conflicting files
extract_archive("archive.tzst", "output/",
conflict_resolution=ConflictResolution.AUTO_RENAME_ALL)
```
### Performance Optimization
```python
from tzst import create_archive, extract_archive
# Create with different compression levels
create_archive("fast.tzst", files, compression_level=1) # Fastest
create_archive("balanced.tzst", files, compression_level=6) # Balanced
create_archive("best.tzst", files, compression_level=22) # Best compression
# Memory-efficient operations for large archives
extract_archive("huge-archive.tzst", "output/", streaming=True)
```
### Streaming Mode
For large archives (>100MB), use streaming mode to reduce memory usage:
```python
# Memory-efficient operations
with TzstArchive("large-archive.tzst", "r", streaming=True) as archive:
contents = archive.list()
archive.extractall("output/")
is_valid = archive.test()
```
**Note**: Streaming mode has limitations - you cannot extract specific files or use random access operations.
### File Extensions
The library automatically handles file extensions with intelligent normalization:
- `.tzst` - Primary extension for tar+zstandard archives
- `.tar.zst` - Alternative standard extension
- Auto-detection when opening existing archives
- Automatic extension addition when creating archives
```python
from tzst import create_archive
# These all create valid archives
create_archive("backup.tzst", files) # Creates backup.tzst
create_archive("backup.tar.zst", files) # Creates backup.tar.zst
create_archive("backup", files) # Creates backup.tzst
create_archive("backup.txt", files) # Creates backup.tzst (normalized)
```
### Atomic Operations
All file creation operations use atomic file operations by default:
- Archives created in temporary files first, then atomically moved
- Automatic cleanup if process is interrupted
- No risk of corrupted or incomplete archives
- Cross-platform compatibility
```python
# Atomic operations enabled by default
create_archive("important.tzst", files) # Safe from interruption
# Can be disabled if needed (not recommended)
create_archive("test.tzst", files, use_temp_file=False)
```
## Error Handling
tzst provides specific exceptions for different error conditions:
```python
from tzst import TzstArchive
from tzst.exceptions import TzstArchiveError, TzstDecompressionError
from tzst import create_archive, TzstArchiveError, TzstCompressionError
try:
with TzstArchive("archive.tzst", "r") as archive:
archive.extract("output/")
create_archive("backup.tzst", ["documents/"])
except TzstCompressionError as e:
print(f"Compression failed: {e}")
except TzstArchiveError as e:
print(f"Archive error: {e}")
except TzstDecompressionError as e:
print(f"Decompression error: {e}")
print(f"Archive operation failed: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
```
## Next Steps
- Explore the complete {doc}`api/index` documentation
- Check out more {doc}`examples` and use cases
- Read about advanced features in the full documentation
- Explore comprehensive {doc}`examples` for real-world scenarios
- Check the {doc}`api/index` for detailed API documentation
- See advanced features like atomic operations and custom filters
- Learn about integration with web frameworks and automation tools
## Read an Existing Archive
```python
with TzstArchive("data.tzst", "r") as archive:
# List contents
contents = archive.list(verbose=True)
# Extract specific file
archive.extract("file.txt", "output/")
# Test integrity
is_valid = archive.test()
# Get raw member information
members = archive.getmembers()
```
## Common Patterns
### Backup Script
```python
#!/usr/bin/env python3
from pathlib import Path
from datetime import datetime
from tzst import create_archive
def create_backup():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"backup_{timestamp}.tzst"
# Backup important directories
directories = ["documents/", "projects/", "config/"]
print(f"Creating backup: {backup_name}")
create_archive(backup_name, directories, compression_level=6)
print(f"Backup created: {Path(backup_name).stat().st_size / 1024 / 1024:.1f} MB")
if __name__ == "__main__":
create_backup()
```
### Archive Verification
```python
from tzst import test_archive, list_archive
def verify_archive(archive_path):
print(f"Verifying {archive_path}...")
# Test integrity
if not test_archive(archive_path):
print("Archive is corrupted!")
return False
# List contents
contents = list_archive(archive_path, verbose=True)
total_size = sum(item['size'] for item in contents if item['is_file'])
file_count = sum(1 for item in contents if item['is_file'])
print(f"Archive is valid")
print(f"Files: {file_count}")
print(f"Total size: {total_size / 1024 / 1024:.1f} MB")
return True
```
## Further Learning
- Explore {doc}`examples` for more advanced usage patterns
- Check {doc}`performance` for detailed performance guidance
- Refer to the {doc}`api/index` for complete API documentation
+1
View File
@@ -3,6 +3,7 @@ sphinx>=7.1.0
sphinx-rtd-theme>=2.0.0
myst-parser>=3.0.0
sphinxcontrib-napoleon>=0.7
linkify-it-py>=2.0.0
# Additional Sphinx extensions
sphinx-autobuild>=2021.3.14
+11 -10
View File
@@ -24,7 +24,14 @@ classifiers = [
dependencies = ["zstandard>=0.19.0,<1.0.0"]
[project.optional-dependencies]
dev = ["pytest>=7.0.0", "pytest-cov>=4.0.0", "ruff>=0.1.0"]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"ruff>=0.1.0",
"pre-commit>=3.6.0",
"build>=1.0.0",
"twine>=4.0.0",
]
[project.urls]
Homepage = "https://github.com/xixu-me/tzst"
@@ -42,13 +49,7 @@ path = "src/tzst/__init__.py"
packages = ["src/tzst"]
[tool.hatch.build.targets.sdist]
include = [
"src",
"tests",
"README.md",
"LICENSE",
"CONTRIBUTING.md",
]
include = ["src", "tests", "README.md", "LICENSE", "CONTRIBUTING.md"]
[tool.pytest.ini_options]
testpaths = ["tests"]
@@ -76,8 +77,8 @@ select = [
"RUF", # ruff specific rules
]
ignore = [
"E501", # line too long
"E501", # line too long - handled by formatter
"C901", # function too complex - accepted for core functionality
]
fixable = ["ALL"]
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
"""Standalone entry point for tzst - used for PyInstaller builds."""
import os
import sys
# Add the src directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from tzst.cli import main
if __name__ == "__main__":
sys.exit(main())
+6 -2
View File
@@ -1,6 +1,10 @@
"""tzst - The next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability."""
"""tzst - The next-generation Python library engineered for modern archive management.
__version__ = "1.1.1"
Leveraging cutting-edge Zstandard compression to deliver superior performance,
security, and reliability.
"""
__version__ = "1.2.5"
from .core import (
TzstArchive,
+127 -2
View File
@@ -6,10 +6,57 @@ from pathlib import Path
from typing import Literal, cast
from . import __version__
from .core import create_archive, extract_archive, list_archive, test_archive
from .core import (
ConflictResolution,
create_archive,
extract_archive,
list_archive,
test_archive,
)
from .exceptions import TzstArchiveError, TzstDecompressionError
def _interactive_conflict_callback(target_path: Path) -> ConflictResolution:
"""Interactive callback for handling file conflicts in CLI.
Args:
target_path: Path of the conflicting file
Returns:
ConflictResolution: User's choice for handling the conflict
"""
print(f"\nFile already exists: {target_path}")
print("Choose an action:")
print(" [R] Replace")
print(" [N] Do not replace (skip)")
print(" [A] Replace all")
print(" [S] Skip all")
print(" [U] Auto-rename all")
print(" [X] Exit")
while True:
try:
choice = input("Enter choice [R/N/A/S/U/X]: ").strip().upper()
if choice == "R":
return ConflictResolution.REPLACE
elif choice == "N":
return ConflictResolution.SKIP
elif choice == "A":
return ConflictResolution.REPLACE_ALL
elif choice == "S":
return ConflictResolution.SKIP_ALL
elif choice == "U":
return ConflictResolution.AUTO_RENAME_ALL
elif choice == "X":
return ConflictResolution.EXIT
else:
print("Invalid choice. Please enter R, N, A, S, U, or X.")
except (EOFError, KeyboardInterrupt):
print("\nOperation cancelled by user")
return ConflictResolution.EXIT
def print_banner() -> None:
"""Print the version and copyright banner.
@@ -299,12 +346,30 @@ def cmd_extract_full(args) -> int:
Literal["data", "tar", "fully_trusted"], getattr(args, "filter", "data")
)
# Handle conflict resolution parameters
conflict_resolution_str = getattr(args, "conflict_resolution", "ask")
interactive_flag = getattr(args, "interactive", False)
# If --interactive is specified, use "ask" regardless of --conflict-resolution
if interactive_flag:
conflict_resolution_str = "ask"
# Convert string to ConflictResolution enum
conflict_resolution = ConflictResolution(conflict_resolution_str)
# Set up interactive callback if needed
interactive_callback = None
if conflict_resolution == ConflictResolution.ASK:
interactive_callback = _interactive_conflict_callback
print(f"Extracting from: {archive_path}")
print(f"Output directory: {output_dir}")
if streaming:
print("Using streaming mode (memory efficient)")
if filter_type != "data":
print(f"Using security filter: {filter_type}")
if conflict_resolution != ConflictResolution.REPLACE:
print(f"Conflict resolution: {conflict_resolution.value}")
extract_archive(
archive_path,
@@ -313,6 +378,8 @@ def cmd_extract_full(args) -> int:
flatten=False,
streaming=streaming,
filter=filter_type,
conflict_resolution=conflict_resolution,
interactive_callback=interactive_callback,
)
print("Extraction completed successfully")
return 0
@@ -377,10 +444,28 @@ def cmd_extract_flat(args) -> int:
Literal["data", "tar", "fully_trusted"], getattr(args, "filter", "data")
)
# Handle conflict resolution parameters
conflict_resolution_str = getattr(args, "conflict_resolution", "ask")
interactive_flag = getattr(args, "interactive", False)
# If --interactive is specified, use "ask" regardless of --conflict-resolution
if interactive_flag:
conflict_resolution_str = "ask"
# Convert string to ConflictResolution enum
conflict_resolution = ConflictResolution(conflict_resolution_str)
# Set up interactive callback if needed
interactive_callback = None
if conflict_resolution == ConflictResolution.ASK:
interactive_callback = _interactive_conflict_callback
print(f"Extracting from: {archive_path}")
print(f"Output directory: {output_dir}")
if filter_type != "data":
print(f"Using security filter: {filter_type}")
if conflict_resolution != ConflictResolution.REPLACE:
print(f"Conflict resolution: {conflict_resolution.value}")
extract_archive(
archive_path,
@@ -389,6 +474,8 @@ def cmd_extract_flat(args) -> int:
flatten=True,
streaming=streaming,
filter=filter_type,
conflict_resolution=conflict_resolution,
interactive_callback=interactive_callback,
)
print("Extraction completed successfully")
return 0
@@ -637,7 +724,7 @@ security note:
never use --filter=fully_trusted unless you completely trust the archive source
documentation:
https://github.com/xixu-me/tzst#readme
https://tzst.xi-xu.me
"""
parser = argparse.ArgumentParser(
prog="tzst",
@@ -704,6 +791,25 @@ documentation:
"'fully_trusted' honors all metadata"
),
)
parser_extract.add_argument(
"--conflict-resolution",
choices=[
"replace",
"skip",
"replace_all",
"skip_all",
"auto_rename",
"auto_rename_all",
"ask",
],
default="ask",
help=(
"How to handle file conflicts during extraction (default: ask). "
"'ask' prompts for each conflict, 'replace' overwrites existing files, "
"'skip' skips existing files, 'auto_rename' creates new names. "
"Adding '_all' applies the action to all subsequent conflicts."
),
)
parser_extract.set_defaults(func=cmd_extract_full)
# Extract flat command
@@ -734,6 +840,25 @@ documentation:
"'fully_trusted' honors all metadata"
),
)
parser_extract_flat.add_argument(
"--conflict-resolution",
choices=[
"replace",
"skip",
"replace_all",
"skip_all",
"auto_rename",
"auto_rename_all",
"ask",
],
default="ask",
help=(
"How to handle file conflicts during extraction (default: ask). "
"'ask' prompts for each conflict, 'replace' overwrites existing files, "
"'skip' skips existing files, 'auto_rename' creates new names. "
"Adding '_all' applies the action to all subsequent conflicts."
),
)
parser_extract_flat.set_defaults(func=cmd_extract_flat)
# List command
+263 -8
View File
@@ -6,6 +6,7 @@ import tarfile
import tempfile
import time
from collections.abc import Callable, Sequence
from enum import Enum
from pathlib import Path
from typing import BinaryIO
@@ -14,6 +15,125 @@ import zstandard as zstd
from .exceptions import TzstArchiveError, TzstDecompressionError
class ConflictResolution(Enum):
"""Enum for conflict resolution strategies."""
REPLACE = "replace"
SKIP = "skip"
REPLACE_ALL = "replace_all"
SKIP_ALL = "skip_all"
AUTO_RENAME = "auto_rename"
AUTO_RENAME_ALL = "auto_rename_all"
EXIT = "exit"
ASK = "ask"
class ConflictResolutionState:
"""State management for conflict resolution during extraction."""
def __init__(self, initial_resolution: ConflictResolution | None = None):
self.continue_extraction = True
self.global_resolution = initial_resolution
# If initial resolution is EXIT, set continue_extraction to False
if initial_resolution == ConflictResolution.EXIT:
self.continue_extraction = False
@property
def current_resolution(self) -> ConflictResolution | None:
"""Get the current resolution state."""
return self.global_resolution
def should_continue(self) -> bool:
"""Check if extraction should continue."""
return self.continue_extraction
@property
def apply_to_all(self) -> bool:
"""Check if the current resolution applies to all future conflicts."""
return self.global_resolution in (
ConflictResolution.REPLACE_ALL,
ConflictResolution.SKIP_ALL,
ConflictResolution.AUTO_RENAME_ALL,
)
def update_resolution(self, resolution: ConflictResolution) -> None:
"""Update the global resolution state."""
if resolution == ConflictResolution.EXIT:
self.continue_extraction = False
self.global_resolution = resolution
elif resolution in (
ConflictResolution.REPLACE_ALL,
ConflictResolution.SKIP_ALL,
ConflictResolution.AUTO_RENAME_ALL,
):
self.global_resolution = resolution
def _get_unique_filename(file_path: Path) -> Path:
"""Generate a unique filename by appending a number if the file exists."""
if not file_path.exists():
return file_path
parent = file_path.parent
stem = file_path.stem
suffix = file_path.suffix
counter = 1
while True:
new_name = f"{stem}_{counter}{suffix}"
new_path = parent / new_name
if not new_path.exists():
return new_path
counter += 1
def _handle_file_conflict(
target_path: Path,
resolution: ConflictResolution | str,
interactive_callback: Callable[[Path], ConflictResolution] | None = None,
) -> tuple[ConflictResolution, Path | None]:
"""
Handle file conflicts during extraction.
Args:
target_path: The path where a conflict occurred
resolution: The conflict resolution strategy
interactive_callback: Optional callback for interactive resolution
Returns:
Tuple of (actual_resolution, final_path)"""
# Convert string resolution to enum if needed
if isinstance(resolution, str):
try:
resolution = ConflictResolution(resolution)
except ValueError:
# Invalid string, fallback to ASK for interactive handling
resolution = ConflictResolution.ASK
if resolution == ConflictResolution.ASK:
if interactive_callback:
resolution = interactive_callback(target_path)
else:
# No callback provided, default to REPLACE for consistency with tests
resolution = ConflictResolution.REPLACE
if resolution in (ConflictResolution.REPLACE, ConflictResolution.REPLACE_ALL):
return resolution, target_path
elif resolution in (ConflictResolution.SKIP, ConflictResolution.SKIP_ALL):
return resolution, None
elif resolution in (
ConflictResolution.AUTO_RENAME,
ConflictResolution.AUTO_RENAME_ALL,
):
unique_path = _get_unique_filename(target_path)
return resolution, unique_path
elif resolution == ConflictResolution.EXIT:
return resolution, None
else:
# Unknown resolution, default to REPLACE for robustness
return ConflictResolution.REPLACE, target_path
class TzstArchive:
"""A class for handling .tzst/.tar.zst archives."""
@@ -620,6 +740,8 @@ def extract_archive(
flatten: bool = False,
streaming: bool = False,
filter: str | Callable | None = "data",
conflict_resolution: ConflictResolution | str = ConflictResolution.REPLACE,
interactive_callback: Callable[[Path], ConflictResolution] | None = None,
) -> None:
"""
Extract files from a .tzst archive.
@@ -631,21 +753,31 @@ def extract_archive(
flatten: If True, extract without directory structure
streaming: If True, use streaming mode (memory efficient for large archives)
filter: Extraction filter for security. Can be:
- 'data': Safe filter for cross-platform data archives (default, recommended)
- 'data': Safe filter for cross-platform data archives (default)
- 'tar': Honor most tar features but block dangerous ones
- 'fully_trusted': Honor all metadata (use only for trusted archives)
- None: Use default behavior (may show deprecation warning in Python 3.12+)
- None: Use default behavior (may show deprecation warning)
- callable: Custom filter function
conflict_resolution: How to handle file conflicts during extraction
interactive_callback: Function to call for interactive conflict resolution
Warning:
Never extract archives from untrusted sources without proper filtering.
The 'data' filter is recommended for most use cases as it prevents
Never extract archives from untrusted sources without proper filtering. The 'data' filter is recommended for most use cases as it prevents
dangerous security issues like path traversal attacks.
See Also:
See Also:
:meth:`TzstArchive.extract`: Method for extracting from an open archive
"""
with TzstArchive(archive_path, "r", streaming=streaming) as archive:
# Convert string resolution to enum if needed
if isinstance(conflict_resolution, str):
try:
conflict_resolution = ConflictResolution(conflict_resolution)
except ValueError:
conflict_resolution = ConflictResolution.REPLACE
state = ConflictResolutionState(conflict_resolution)
if flatten:
# Extract files without directory structure
extract_dir = Path(extract_path)
@@ -657,20 +789,143 @@ def extract_archive(
member_list = archive.getmembers()
for member in member_list:
if not state.should_continue():
break
if member.isfile():
# Extract to flat directory
filename = Path(member.name).name
target_path = extract_dir / filename
# Handle conflicts
if target_path.exists():
current_resolution = (
state.global_resolution or conflict_resolution
)
actual_resolution, final_path = _handle_file_conflict(
target_path, current_resolution, interactive_callback
)
state.update_resolution(actual_resolution)
if actual_resolution in (
ConflictResolution.SKIP,
ConflictResolution.SKIP_ALL,
):
continue
elif actual_resolution == ConflictResolution.EXIT:
break
target_path = final_path
fileobj = archive.extractfile(member)
if fileobj:
with open(extract_dir / filename, "wb") as f:
with open(target_path, "wb") as f:
f.write(fileobj.read())
else:
# Extract with full directory structure
if members:
for member in members:
archive.extract(member, extract_path, filter=filter)
if not state.should_continue():
break
target_path = Path(extract_path) / member
# Handle conflicts
if target_path.exists():
current_resolution = (
state.global_resolution or conflict_resolution
)
actual_resolution, final_path = _handle_file_conflict(
target_path, current_resolution, interactive_callback
)
state.update_resolution(actual_resolution)
if actual_resolution in (
ConflictResolution.SKIP,
ConflictResolution.SKIP_ALL,
):
continue
elif actual_resolution == ConflictResolution.EXIT:
break
# For AUTO_RENAME, we need to adjust the member path
if actual_resolution in (
ConflictResolution.AUTO_RENAME,
ConflictResolution.AUTO_RENAME_ALL,
):
# Create parent directories for renamed file
final_path.parent.mkdir(parents=True, exist_ok=True)
# Extract to temporary location, then move
temp_extract_path = Path(tempfile.mkdtemp())
try:
archive.extract(
member, temp_extract_path, filter=filter
)
temp_file = temp_extract_path / member
temp_file.rename(final_path)
finally:
# Clean up temp directory
import shutil
shutil.rmtree(temp_extract_path, ignore_errors=True)
else:
archive.extract(member, extract_path, filter=filter)
else:
archive.extract(member, extract_path, filter=filter)
else:
archive.extract(path=extract_path, filter=filter)
# For extractall, we need a different approach
# We'll extract to a temp location and handle conflicts file by file
temp_extract_path = Path(tempfile.mkdtemp())
try:
archive.extractall(temp_extract_path, filter=filter)
# Move files with conflict resolution
for temp_file in temp_extract_path.rglob("*"):
if not state.should_continue():
break
if temp_file.is_file():
rel_path = temp_file.relative_to(temp_extract_path)
target_path = Path(extract_path) / rel_path
# Create parent directories
target_path.parent.mkdir(
parents=True, exist_ok=True
) # Handle conflicts
if target_path.exists():
current_resolution = (
state.global_resolution or conflict_resolution
)
actual_resolution, final_path = _handle_file_conflict(
target_path,
current_resolution,
interactive_callback,
)
state.update_resolution(actual_resolution)
if actual_resolution in (
ConflictResolution.SKIP,
ConflictResolution.SKIP_ALL,
):
continue
elif actual_resolution == ConflictResolution.EXIT:
break
target_path = final_path
# Handle file replacement on Windows
if target_path and target_path.exists():
if actual_resolution in (
ConflictResolution.REPLACE,
ConflictResolution.REPLACE_ALL,
):
target_path.unlink() # Remove existing file
if target_path:
temp_file.rename(target_path)
finally:
# Clean up temp directory
import shutil
shutil.rmtree(temp_extract_path, ignore_errors=True)
def list_archive(
+134 -1
View File
@@ -17,6 +17,7 @@ from tzst.cli import (
)
@pytest.mark.cli
class TestUtilityFunctions:
"""Test CLI utility functions."""
@@ -892,7 +893,7 @@ class TestCLIRealWorldScenarios:
# Test error operation returns non-zero
result = main(["l", "nonexistent_archive.tzst"])
assert result != 0 # Error should return non-zero
assert result != 0
class TestCLISecurityFilterParsing:
@@ -2289,3 +2290,135 @@ class TestCLIListingFunctionsCoverage:
assert "2 files" in captured.out
assert "2 directories" in captured.out
assert "300.0 B" in captured.out # Total size of files
class TestCLIEdgeCasesExtended:
"""Additional edge case tests for CLI functionality."""
def test_validate_files_os_error_handling(self, temp_dir):
"""Test OSError handling in validate_files function."""
from unittest.mock import patch
from tzst.cli import _validate_files
# Create a test file
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Mock Path.exists to raise OSError
with patch("pathlib.Path.exists", side_effect=OSError("Permission denied")):
# Should handle OSError gracefully and continue
try:
_validate_files([test_file])
except OSError:
pass # Expected to be caught and handled
def test_windows_specific_cli_functionality(self, temp_dir):
"""Test Windows-specific CLI functionality."""
import sys
if sys.platform != "win32":
pytest.skip("Windows-specific test")
# Test Windows reserved names
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test with Windows path separators
windows_style_path = str(archive_path).replace("/", "\\")
result = main(["l", windows_style_path])
assert result == 0
def test_compression_level_boundary_values(self, temp_dir):
"""Test compression level boundary values."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content for compression")
# Test minimum compression level
archive_path_min = temp_dir / "test_min.tzst"
result = main(["a", str(archive_path_min), str(test_file), "-c", "1"])
assert result == 0
# Test maximum compression level
archive_path_max = temp_dir / "test_max.tzst"
result = main(["a", str(archive_path_max), str(test_file), "-c", "22"])
assert result == 0
def test_output_directory_creation_edge_cases(self, temp_dir):
"""Test output directory creation edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test extraction to nested directory that doesn't exist
nested_extract_dir = temp_dir / "level1" / "level2" / "level3"
result = main(["x", str(archive_path), "-o", str(nested_extract_dir)])
assert result == 0
# Verify directory was created
assert nested_extract_dir.exists()
def test_special_file_handling_edge_cases(self, temp_dir):
"""Test special file handling edge cases."""
# Create files with special characteristics
empty_file = temp_dir / "empty.txt"
empty_file.touch()
whitespace_file = temp_dir / "whitespace.txt"
whitespace_file.write_text(" \n\t\n ")
binary_file = temp_dir / "binary.bin"
binary_file.write_bytes(b"\x00\x01\x02\x03\x04\x05")
archive_path = temp_dir / "special.tzst"
# Create archive with special files
result = main(
[
"a",
str(archive_path),
str(empty_file),
str(whitespace_file),
str(binary_file),
]
)
assert result == 0
# Extract and verify
extract_dir = temp_dir / "extracted"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
def test_performance_edge_cases(self, temp_dir):
"""Test performance-related edge cases."""
# Create many small files
files = []
for i in range(20): # Create 20 small files
file_path = temp_dir / f"small_{i:03d}.txt"
file_path.write_text(f"Content of file {i}")
files.append(file_path)
archive_path = temp_dir / "many_files.tzst"
# Create archive with many files
file_args = [str(f) for f in files]
result = main(["a", str(archive_path), *file_args])
assert result == 0
# Test listing (should handle many files efficiently)
result = main(["l", str(archive_path)])
assert result == 0
# Test extraction
extract_dir = temp_dir / "extracted_many"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
+11
View File
@@ -7,6 +7,17 @@ from pathlib import Path
import pytest
def pytest_configure(config):
"""Configure pytest with custom markers."""
config.addinivalue_line("markers", "unit: Unit tests")
config.addinivalue_line("markers", "integration: Integration tests")
config.addinivalue_line("markers", "cli: CLI interface tests")
config.addinivalue_line("markers", "platform: Platform-specific tests")
config.addinivalue_line("markers", "windows: Windows-specific tests")
config.addinivalue_line("markers", "unix: Unix/Linux-specific tests")
config.addinivalue_line("markers", "slow: Slow running tests")
@pytest.fixture
def temp_dir():
"""Create a temporary directory for tests."""
-405
View File
@@ -1,405 +0,0 @@
"""Tests to cover missing lines in CLI and improve overall coverage."""
import sys
from unittest.mock import patch
import pytest
from tzst.cli import _validate_files, main
class TestCLIMissingLines:
"""Test specific missing lines in CLI for improved coverage."""
def test_validate_files_os_error_handling(self, temp_dir):
"""Test OSError handling in validate_files function."""
# Create a test file
test_file = temp_dir / "test.txt"
test_file.write_text("test content") # Mock Path.exists to raise OSError
with patch(
"pathlib.Path.exists", side_effect=OSError("Permission denied")
): # Should handle OSError gracefully and continue
try:
_validate_files([test_file])
except OSError:
pass # Expected to be caught and handled
def test_main_function_edge_cases(self, temp_dir):
"""Test main function edge cases for missing line coverage."""
# Test with minimal arguments that might hit edge cases
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst" # Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test version command through main
with patch("sys.exit"):
try:
main(["--version"])
except SystemExit:
pass
# Test help command variations
with patch("sys.exit"):
try:
main(["--help"])
except SystemExit:
pass
def test_command_line_argument_edge_cases(self, temp_dir):
"""Test command line argument edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Test with various argument combinations that might hit missing lines
archive_path = temp_dir / "test.tzst"
# Create archive with specific compression level
result = main(["a", str(archive_path), str(test_file), "-c", "1"])
assert result == 0
# Test list with streaming
result = main(["l", str(archive_path), "--streaming"])
assert result == 0 # Test extract with specific options
extract_dir = temp_dir / "extracted"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
def test_error_handling_edge_cases(self, temp_dir):
"""Test error handling edge cases in CLI."""
# Test with invalid archive path
invalid_path = temp_dir / "nonexistent" / "test.tzst"
result = main(["l", str(invalid_path)])
assert result == 1
# Test with invalid compression level - should return argparse error code 2
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
result = main(["a", str(archive_path), str(test_file), "-c", "50"])
assert result == 2
def test_filter_option_edge_cases(self, temp_dir):
"""Test filter option edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test extract with different filters
for filter_type in ["data", "tar", "fully_trusted"]:
extract_dir = temp_dir / f"extracted_{filter_type}"
result = main(
[
"x",
str(archive_path),
"-o",
str(extract_dir),
"--filter",
filter_type,
]
)
assert result == 0
def test_atomic_operation_edge_cases(self, temp_dir):
"""Test atomic operation edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Test with --no-atomic flag
result = main(["a", str(archive_path), str(test_file), "--no-atomic"])
assert result == 0
# Verify archive was created
assert archive_path.exists()
def test_verbose_output_edge_cases(self, temp_dir, capsys):
"""Test verbose output edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst" # Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test verbose list
result = main(["l", str(archive_path), "-v"])
assert result == 0
captured = capsys.readouterr()
assert len(captured.out) > 0
def test_command_validation_edge_cases(self):
"""Test command validation edge cases."""
# Test with empty arguments
result = main([])
assert result == 1
# Test with invalid command - should return argparse error code 2
result = main(["invalid_command"])
assert result == 2
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
def test_windows_specific_functionality(self, temp_dir):
"""Test Windows-specific functionality."""
# Test Windows reserved names
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test with Windows path separators
windows_style_path = str(archive_path).replace("/", "\\")
result = main(["l", windows_style_path])
assert result == 0
def test_streaming_mode_edge_cases(self, temp_dir):
"""Test streaming mode edge cases."""
# Create a larger file for streaming tests
large_file = temp_dir / "large.txt"
large_file.write_text("x" * 10000) # 10KB file
archive_path = temp_dir / "streaming.tzst"
# Create archive
result = main(["a", str(archive_path), str(large_file)])
assert result == 0
# Test all commands with streaming
result = main(["l", str(archive_path), "--streaming"])
assert result == 0
result = main(["t", str(archive_path), "--streaming"])
assert result == 0
extract_dir = temp_dir / "extracted_streaming"
result = main(["x", str(archive_path), "-o", str(extract_dir), "--streaming"])
assert result == 0
def test_compression_level_boundary_values(self, temp_dir):
"""Test compression level boundary values."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Test minimum compression level
archive_path_min = temp_dir / "min_compression.tzst"
result = main(["a", str(archive_path_min), str(test_file), "-c", "1"])
assert result == 0
# Test maximum compression level
archive_path_max = temp_dir / "max_compression.tzst"
result = main(["a", str(archive_path_max), str(test_file), "-c", "22"])
assert result == 0
def test_output_directory_creation_edge_cases(self, temp_dir):
"""Test output directory creation edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test extraction to nested directory that doesn't exist
nested_extract_dir = temp_dir / "level1" / "level2" / "level3"
result = main(["x", str(archive_path), "-o", str(nested_extract_dir)])
assert result == 0
# Verify directory was created
assert nested_extract_dir.exists()
def test_special_file_handling_edge_cases(self, temp_dir):
"""Test special file handling edge cases."""
# Create files with special characteristics
empty_file = temp_dir / "empty.txt"
empty_file.touch()
binary_file = temp_dir / "binary.bin"
binary_file.write_bytes(b"\x00\x01\x02\x03\xff")
unicode_file = temp_dir / "unicode.txt"
unicode_file.write_text("Hello 世界 🌍", encoding="utf-8")
archive_path = temp_dir / "special.tzst"
# Create archive with special files
result = main(
[
"a",
str(archive_path),
str(empty_file),
str(binary_file),
str(unicode_file),
]
)
assert result == 0
# Test list and extract
result = main(["l", str(archive_path)])
assert result == 0
extract_dir = temp_dir / "extracted_special"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
class TestPlatformSpecificMissingLines:
"""Test platform-specific functionality to improve coverage."""
@pytest.mark.skipif(
sys.platform != "win32", reason="Windows-specific functionality"
)
def test_windows_long_path_edge_cases(self, temp_dir):
"""Test Windows long path handling edge cases."""
# Create a very deep directory structure
deep_dir = temp_dir
for i in range(10):
deep_dir = deep_dir / f"very_long_directory_name_{i}"
deep_dir.mkdir(parents=True, exist_ok=True)
deep_file = deep_dir / "deep_file.txt"
deep_file.write_text("Content in deeply nested file")
archive_path = temp_dir / "deep.tzst"
# Test archiving deep structure
result = main(["a", str(archive_path), str(deep_file)])
assert result == 0
# Test extraction
extract_dir = temp_dir / "extracted_deep"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
@pytest.mark.skipif(
sys.platform != "win32", reason="Windows-specific functionality"
)
def test_windows_reserved_names_edge_cases(self, temp_dir):
"""Test Windows reserved names edge cases."""
# Test with files that have problematic names on Windows
normal_file = temp_dir / "normal.txt"
normal_file.write_text("normal content")
# File with trailing space (problematic on Windows)
space_file = temp_dir / "file_with_space .txt"
space_file.write_text("space content")
archive_path = temp_dir / "reserved.tzst"
# Create archive
result = main(["a", str(archive_path), str(normal_file), str(space_file)])
assert result == 0
def test_unicode_handling_edge_cases(self, temp_dir):
"""Test unicode handling edge cases."""
# Create files with various unicode content
files_to_create = [
("chinese.txt", "你好世界"),
("emoji.txt", "🎉🌟💫"),
("mixed.txt", "Hello 世界! 🌍 Мир"),
("special_chars.txt", "àáâãäåæçèéêë"),
]
created_files = []
for filename, content in files_to_create:
file_path = temp_dir / filename
file_path.write_text(content, encoding="utf-8")
created_files.append(file_path)
archive_path = temp_dir / "unicode.tzst" # Create archive
file_args = [str(f) for f in created_files]
result = main(["a", str(archive_path), *file_args])
assert result == 0
# Test extraction
extract_dir = temp_dir / "extracted_unicode"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
# Verify unicode content is preserved
for filename, original_content in files_to_create:
extracted_file = extract_dir / filename
assert extracted_file.exists()
extracted_content = extracted_file.read_text(encoding="utf-8")
assert extracted_content == original_content
def test_performance_edge_cases(self, temp_dir):
"""Test performance-related edge cases."""
# Create many small files
files = []
for i in range(50): # Create 50 small files
file_path = temp_dir / f"small_{i:03d}.txt"
file_path.write_text(f"Content of file {i}")
files.append(file_path)
archive_path = temp_dir / "many_files.tzst" # Create archive with many files
file_args = [str(f) for f in files]
result = main(["a", str(archive_path), *file_args])
assert result == 0
# Test listing (should handle many files efficiently)
result = main(["l", str(archive_path)])
assert result == 0
# Test extraction
extract_dir = temp_dir / "extracted_many"
result = main(["x", str(archive_path), "-o", str(extract_dir)])
assert result == 0
def test_error_recovery_edge_cases(self, temp_dir):
"""Test error recovery edge cases."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Create archive
result = main(["a", str(archive_path), str(test_file)])
assert result == 0
# Test with readonly archive
archive_path.chmod(0o444) # Make read-only
try:
# Should handle read-only archive gracefully
result = main(["l", str(archive_path)])
assert result == 0
finally:
# Restore write permissions for cleanup
archive_path.chmod(0o644)
def test_cross_platform_compatibility(self, temp_dir):
"""Test cross-platform compatibility features."""
# Create files with various characteristics
text_file = temp_dir / "text.txt"
text_file.write_text("Cross-platform text content\n")
binary_file = temp_dir / "binary.dat"
binary_file.write_bytes(bytes(range(256)))
archive_path = temp_dir / "cross_platform.tzst"
# Create archive
result = main(["a", str(archive_path), str(text_file), str(binary_file)])
assert result == 0
# Test with different compression levels
for level in [1, 11, 22]:
archive_path_level = temp_dir / f"cross_platform_level_{level}.tzst"
result = main(
["a", str(archive_path_level), str(text_file), "-c", str(level)]
)
assert result == 0
# Verify can be read back
result = main(["t", str(archive_path_level)])
assert result == 0
+463
View File
@@ -0,0 +1,463 @@
# filepath: e:\GitHub\tzst\tests\test_conflict_resolution_clean.py
"""Comprehensive tests for conflict resolution functionality."""
from unittest.mock import Mock, patch
from tzst.cli import _interactive_conflict_callback
from tzst.core import (
ConflictResolution,
ConflictResolutionState,
TzstArchive,
_get_unique_filename,
_handle_file_conflict,
create_archive,
extract_archive,
)
class TestConflictResolution:
"""Test conflict resolution enum and basic functionality."""
def test_conflict_resolution_enum_values(self):
"""Test that all ConflictResolution enum values exist."""
assert ConflictResolution.REPLACE.value == "replace"
assert ConflictResolution.SKIP.value == "skip"
assert ConflictResolution.REPLACE_ALL.value == "replace_all"
assert ConflictResolution.SKIP_ALL.value == "skip_all"
assert ConflictResolution.AUTO_RENAME.value == "auto_rename"
assert ConflictResolution.AUTO_RENAME_ALL.value == "auto_rename_all"
assert ConflictResolution.EXIT.value == "exit"
assert ConflictResolution.ASK.value == "ask"
class TestUniqueFilename:
"""Test unique filename generation."""
def test_get_unique_filename_basic(self, temp_dir):
"""Test basic unique filename generation."""
# Create a file
original_file = temp_dir / "test.txt"
original_file.write_text("original")
# Get unique name
unique_path = _get_unique_filename(original_file)
expected_path = temp_dir / "test_1.txt"
assert unique_path == expected_path
assert not unique_path.exists()
def test_get_unique_filename_multiple_conflicts(self, temp_dir):
"""Test unique filename generation with multiple conflicts."""
# Create multiple files
original_file = temp_dir / "test.txt"
conflict1 = temp_dir / "test_1.txt"
conflict2 = temp_dir / "test_2.txt"
original_file.write_text("original")
conflict1.write_text("conflict1")
conflict2.write_text("conflict2")
# Get unique name
unique_path = _get_unique_filename(original_file)
expected_path = temp_dir / "test_3.txt"
assert unique_path == expected_path
assert not unique_path.exists()
def test_get_unique_filename_no_extension(self, temp_dir):
"""Test unique filename generation for files without extension."""
# Create a file without extension
original_file = temp_dir / "README"
original_file.write_text("readme content")
# Get unique name
unique_path = _get_unique_filename(original_file)
expected_path = temp_dir / "README_1"
assert unique_path == expected_path
assert not unique_path.exists()
def test_get_unique_filename_empty_stem(self, temp_dir):
"""Test unique filename generation for files with empty stem."""
# Create a file with empty stem (just extension)
original_file = temp_dir / ".gitignore"
original_file.write_text("git ignore")
# Get unique name
unique_path = _get_unique_filename(original_file)
expected_path = temp_dir / ".gitignore_1"
assert unique_path == expected_path
assert not unique_path.exists()
class TestHandleFileConflict:
"""Test file conflict handling function."""
def test_handle_file_conflict_replace(self, temp_dir):
"""Test REPLACE conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.REPLACE, None
)
assert resolution == ConflictResolution.REPLACE
assert final_path == target_path
def test_handle_file_conflict_skip(self, temp_dir):
"""Test SKIP conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.SKIP, None
)
assert resolution == ConflictResolution.SKIP
assert final_path is None
def test_handle_file_conflict_replace_all(self, temp_dir):
"""Test REPLACE_ALL conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.REPLACE_ALL, None
)
assert resolution == ConflictResolution.REPLACE_ALL
assert final_path == target_path
def test_handle_file_conflict_skip_all(self, temp_dir):
"""Test SKIP_ALL conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.SKIP_ALL, None
)
assert resolution == ConflictResolution.SKIP_ALL
assert final_path is None
def test_handle_file_conflict_auto_rename(self, temp_dir):
"""Test AUTO_RENAME conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.AUTO_RENAME, None
)
assert resolution == ConflictResolution.AUTO_RENAME
assert final_path == temp_dir / "existing_1.txt"
assert not final_path.exists()
def test_handle_file_conflict_auto_rename_all(self, temp_dir):
"""Test AUTO_RENAME_ALL conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.AUTO_RENAME_ALL, None
)
assert resolution == ConflictResolution.AUTO_RENAME_ALL
assert final_path == temp_dir / "existing_1.txt"
assert not final_path.exists()
def test_handle_file_conflict_exit(self, temp_dir):
"""Test EXIT conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.EXIT, None
)
assert resolution == ConflictResolution.EXIT
assert final_path is None
def test_handle_file_conflict_ask_with_callback(self, temp_dir):
"""Test ASK conflict resolution with callback."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
mock_callback = Mock(return_value=ConflictResolution.REPLACE)
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.ASK, mock_callback
)
assert resolution == ConflictResolution.REPLACE
assert final_path == target_path
mock_callback.assert_called_once_with(target_path)
def test_handle_file_conflict_ask_no_callback(self, temp_dir):
"""Test ASK conflict resolution without callback defaults to REPLACE."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, ConflictResolution.ASK, None
)
assert resolution == ConflictResolution.REPLACE
assert final_path == target_path
def test_handle_file_conflict_string_resolution_valid(self, temp_dir):
"""Test string-based conflict resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(target_path, "skip", None)
assert resolution == ConflictResolution.SKIP
assert final_path is None
def test_handle_file_conflict_string_resolution_invalid(self, temp_dir):
"""Test invalid string conflict resolution defaults to REPLACE."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
resolution, final_path = _handle_file_conflict(
target_path, "invalid_resolution", None
)
assert resolution == ConflictResolution.REPLACE
assert final_path == target_path
def test_handle_file_conflict_unknown_resolution(self, temp_dir):
"""Test unknown conflict resolution defaults to REPLACE."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing")
# Pass something that's not a valid enum value
resolution, final_path = _handle_file_conflict(
target_path, "completely_unknown", None
)
assert resolution == ConflictResolution.REPLACE
assert final_path == target_path
class TestConflictResolutionState:
"""Test conflict resolution state management."""
def test_initial_state(self):
"""Test initial state with different resolutions."""
state1 = ConflictResolutionState(ConflictResolution.REPLACE)
assert state1.current_resolution == ConflictResolution.REPLACE
assert state1.should_continue() is True
state2 = ConflictResolutionState(ConflictResolution.EXIT)
assert state2.current_resolution == ConflictResolution.EXIT
assert state2.should_continue() is False
def test_update_resolution_replace_all(self):
"""Test updating to REPLACE_ALL."""
state = ConflictResolutionState(ConflictResolution.ASK)
state.update_resolution(ConflictResolution.REPLACE_ALL)
assert state.current_resolution == ConflictResolution.REPLACE_ALL
assert state.should_continue() is True
def test_update_resolution_skip_all(self):
"""Test updating to SKIP_ALL."""
state = ConflictResolutionState(ConflictResolution.ASK)
state.update_resolution(ConflictResolution.SKIP_ALL)
assert state.current_resolution == ConflictResolution.SKIP_ALL
assert state.should_continue() is True
def test_update_resolution_auto_rename_all(self):
"""Test updating to AUTO_RENAME_ALL."""
state = ConflictResolutionState(ConflictResolution.ASK)
state.update_resolution(ConflictResolution.AUTO_RENAME_ALL)
assert state.current_resolution == ConflictResolution.AUTO_RENAME_ALL
assert state.should_continue() is True
def test_update_resolution_exit(self):
"""Test updating to EXIT."""
state = ConflictResolutionState(ConflictResolution.ASK)
state.update_resolution(ConflictResolution.EXIT)
assert state.current_resolution == ConflictResolution.EXIT
assert state.should_continue() is False
def test_update_resolution_normal(self):
"""Test updating to normal resolution doesn't change state."""
state = ConflictResolutionState(ConflictResolution.ASK)
state.update_resolution(ConflictResolution.REPLACE)
assert state.current_resolution == ConflictResolution.ASK
assert state.should_continue() is True
class TestExtractArchiveConflictResolution:
"""Test extract_archive with conflict resolution."""
def test_extract_with_replace_conflict_resolution(self, temp_dir):
"""Test extraction with REPLACE conflict resolution."""
# Create archive
archive_path = temp_dir / "test.tzst"
source_file = temp_dir / "source.txt"
source_file.write_text("archive content")
create_archive(archive_path, [source_file])
# Create extract directory with conflicting file
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
conflict_file = extract_dir / "source.txt"
conflict_file.write_text("existing content")
# Extract with REPLACE resolution
extract_archive(
archive_path, extract_dir, conflict_resolution=ConflictResolution.REPLACE
)
# Verify file was replaced
assert conflict_file.read_text() == "archive content"
def test_extract_with_skip_conflict_resolution(self, temp_dir):
"""Test extraction with SKIP conflict resolution."""
# Create archive
archive_path = temp_dir / "test.tzst"
source_file = temp_dir / "source.txt"
source_file.write_text("archive content")
create_archive(archive_path, [source_file])
# Create extract directory with conflicting file
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
conflict_file = extract_dir / "source.txt"
conflict_file.write_text("existing content")
# Extract with SKIP resolution
extract_archive(
archive_path, extract_dir, conflict_resolution=ConflictResolution.SKIP
)
# Verify file was not replaced
assert conflict_file.read_text() == "existing content"
def test_extract_with_interactive_callback(self, temp_dir):
"""Test extraction with interactive callback."""
# Create archive
archive_path = temp_dir / "test.tzst"
source_file = temp_dir / "source.txt"
source_file.write_text("archive content")
create_archive(archive_path, [source_file])
# Create extract directory with conflicting file
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
conflict_file = extract_dir / "source.txt"
conflict_file.write_text("existing content")
mock_callback = Mock(return_value=ConflictResolution.REPLACE)
# Extract with interactive callback
extract_archive(
archive_path,
extract_dir,
conflict_resolution=ConflictResolution.ASK,
interactive_callback=mock_callback,
)
# Verify callback was called and file was replaced
mock_callback.assert_called_once()
assert conflict_file.read_text() == "archive content"
class TestTzstArchiveConflictResolution:
"""Test extract_archive function with conflict resolution (corrected)."""
def test_extract_archive_with_conflict_resolution(self, temp_dir):
"""Test extract_archive function with conflict resolution parameter."""
# Create archive
source_file = temp_dir / "source.txt"
source_file.write_text("archive content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(source_file), arcname="source.txt")
# Create conflicting file in extract directory
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
conflict_file = extract_dir / "source.txt"
conflict_file.write_text("existing content")
# Extract with REPLACE resolution using extract_archive function
extract_archive(
archive_path, extract_dir, conflict_resolution=ConflictResolution.REPLACE
)
# Should have replaced the file
assert conflict_file.read_text() == "archive content"
class TestInteractiveConflictCallback:
"""Test interactive conflict callback functionality."""
@patch("builtins.input")
def test_interactive_callback_replace(self, mock_input, temp_dir):
"""Test interactive callback with replace choice."""
mock_input.return_value = "r"
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.REPLACE
@patch("builtins.input")
def test_interactive_callback_skip(self, mock_input, temp_dir):
"""Test interactive callback with skip choice."""
mock_input.return_value = "n"
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.SKIP
@patch("builtins.input")
def test_interactive_callback_exit(self, mock_input, temp_dir):
"""Test interactive callback with exit choice."""
mock_input.return_value = "x"
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.EXIT
@patch("builtins.input")
def test_interactive_callback_invalid_then_valid(self, mock_input, temp_dir):
"""Test interactive callback with invalid then valid choice."""
mock_input.side_effect = ["invalid", "r"]
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.REPLACE
assert mock_input.call_count == 2
@patch("builtins.input")
def test_interactive_callback_keyboard_interrupt(self, mock_input, temp_dir):
"""Test interactive callback with KeyboardInterrupt."""
mock_input.side_effect = KeyboardInterrupt()
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.EXIT
@patch("builtins.input")
def test_interactive_callback_eof_error(self, mock_input, temp_dir):
"""Test interactive callback with EOFError."""
mock_input.side_effect = EOFError()
file_path = temp_dir / "test.txt"
result = _interactive_conflict_callback(file_path)
assert result == ConflictResolution.EXIT
-216
View File
@@ -1,216 +0,0 @@
"""Tests to exercise conftest.py fixtures and improve coverage."""
from tzst import create_archive, extract_archive, list_archive
from tzst.core import TzstArchive
class TestConftestFixtures:
"""Test all conftest.py fixtures to improve coverage."""
def test_comprehensive_test_files_fixture(self, comprehensive_test_files, temp_dir):
"""Test the comprehensive_test_files fixture."""
# Ensure we have the expected file types
assert len(comprehensive_test_files) >= 9
file_names = [f.name for f in comprehensive_test_files]
# Check for specific files that should be created
assert "empty_file.txt" in file_names
assert "whitespace_only.txt" in file_names
assert "newlines_only.txt" in file_names
assert "null_bytes.bin" in file_names
assert "large_file.txt" in file_names
assert "binary_data.bin" in file_names
assert "file with spaces.txt" in file_names
assert "unicode_content.txt" in file_names
assert "deepest_file.txt" in file_names
# Create archive with these comprehensive test files
archive_path = temp_dir / "comprehensive.tzst"
file_paths = [str(f) for f in comprehensive_test_files if f.is_file()]
create_archive(archive_path, file_paths)
# Verify archive was created and contains expected files
assert archive_path.exists()
contents = list_archive(archive_path)
assert len(contents) >= 9
def test_platform_specific_files_fixture(self, platform_specific_files, temp_dir):
"""Test the platform_specific_files fixture."""
# This fixture may return empty list on Windows, non-empty on Unix
# Just ensure it doesn't crash and returns a list
assert isinstance(platform_specific_files, list)
if platform_specific_files:
# If we have platform-specific files, create an archive with them
archive_path = temp_dir / "platform_specific.tzst"
file_paths = [str(f) for f in platform_specific_files if f.is_file()]
if file_paths:
create_archive(archive_path, file_paths)
assert archive_path.exists()
def test_compression_test_files_fixture(self, compression_test_files, temp_dir):
"""Test the compression_test_files fixture."""
assert len(compression_test_files) == 2
file_names = [f.name for f in compression_test_files]
assert "highly_compressible.txt" in file_names
assert "poorly_compressible.bin" in file_names
# Test different compression levels with these files
for level in [1, 11, 22]:
archive_path = temp_dir / f"compression_level_{level}.tzst"
with TzstArchive(
archive_path, mode="w", compression_level=level
) as archive:
for file_path in compression_test_files:
if file_path.is_file():
archive.add(str(file_path), arcname=file_path.name)
assert archive_path.exists()
contents = list_archive(archive_path)
assert len(contents) == 2
def test_combined_fixtures_workflow(
self, comprehensive_test_files, compression_test_files, temp_dir
):
"""Test using multiple fixtures together."""
all_files = comprehensive_test_files + compression_test_files
file_paths = [str(f) for f in all_files if f.is_file()]
# Create archive with all files
archive_path = temp_dir / "combined.tzst"
create_archive(archive_path, file_paths)
# Extract and verify
extract_dir = temp_dir / "extracted"
extract_archive(archive_path, extract_dir)
# Verify archive was created and extracted directory exists
assert archive_path.exists()
assert extract_dir.exists()
# Count files instead of checking exact names (due to nested structure)
extracted_files = list(extract_dir.rglob("*"))
extracted_file_count = len([f for f in extracted_files if f.is_file()])
original_file_count = len([f for f in all_files if f.is_file()])
# Should have extracted at least some files
assert extracted_file_count > 0
assert extracted_file_count <= original_file_count
def test_unicode_content_file_handling(self, comprehensive_test_files, temp_dir):
"""Test handling of unicode content specifically."""
unicode_files = [f for f in comprehensive_test_files if "unicode" in f.name]
assert len(unicode_files) >= 1
unicode_file = unicode_files[0]
content = unicode_file.read_text(encoding="utf-8")
assert "世界" in content
assert "🌍" in content
# Create archive and verify unicode handling
archive_path = temp_dir / "unicode.tzst"
create_archive(archive_path, [str(unicode_file)])
# Extract and verify content is preserved
extract_dir = temp_dir / "extracted_unicode"
extract_archive(archive_path, extract_dir)
extracted_file = extract_dir / unicode_file.name
extracted_content = extracted_file.read_text(encoding="utf-8")
assert extracted_content == content
def test_special_character_filenames(self, comprehensive_test_files, temp_dir):
"""Test files with special characters in names."""
special_files = [f for f in comprehensive_test_files if " " in f.name]
assert len(special_files) >= 1
archive_path = temp_dir / "special_chars.tzst"
file_paths = [str(f) for f in special_files if f.is_file()]
create_archive(archive_path, file_paths)
contents = list_archive(archive_path)
assert any(" " in item["name"] for item in contents)
def test_deeply_nested_structure(self, comprehensive_test_files, temp_dir):
"""Test deeply nested directory structure."""
nested_files = [f for f in comprehensive_test_files if "deepest" in f.name]
assert len(nested_files) >= 1
nested_file = nested_files[0]
assert "nested" in str(nested_file.parent)
# Create archive maintaining directory structure
archive_path = temp_dir / "nested.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(
str(nested_file), arcname=str(nested_file.relative_to(temp_dir))
)
contents = list_archive(archive_path)
assert any("nested" in item["name"] for item in contents)
def test_empty_and_whitespace_files(self, comprehensive_test_files, temp_dir):
"""Test empty and whitespace-only files."""
empty_files = [
f
for f in comprehensive_test_files
if "empty" in f.name or "whitespace" in f.name or "newlines" in f.name
]
assert len(empty_files) >= 3
archive_path = temp_dir / "empty_whitespace.tzst"
file_paths = [str(f) for f in empty_files if f.is_file()]
create_archive(archive_path, file_paths)
# Extract and verify these special cases are handled
extract_dir = temp_dir / "extracted_empty"
extract_archive(archive_path, extract_dir)
for file_path in empty_files:
if file_path.is_file():
extracted_file = extract_dir / file_path.name
assert extracted_file.exists()
def test_binary_data_handling(self, comprehensive_test_files, temp_dir):
"""Test binary files with null bytes and binary data."""
binary_files = [f for f in comprehensive_test_files if f.suffix == ".bin"]
assert len(binary_files) >= 2
archive_path = temp_dir / "binary.tzst"
file_paths = [str(f) for f in binary_files if f.is_file()]
create_archive(archive_path, file_paths)
# Extract and verify binary content is preserved
extract_dir = temp_dir / "extracted_binary"
extract_archive(archive_path, extract_dir)
for file_path in binary_files:
if file_path.is_file():
extracted_file = extract_dir / file_path.name
assert extracted_file.exists()
# Verify binary content is identical
original_content = file_path.read_bytes()
extracted_content = extracted_file.read_bytes()
assert original_content == extracted_content
def test_large_file_handling(self, comprehensive_test_files, temp_dir):
"""Test large file handling."""
large_files = [f for f in comprehensive_test_files if "large" in f.name]
assert len(large_files) >= 1
large_file = large_files[0]
# Verify it's actually large
assert large_file.stat().st_size > 100000 # Should be > 100KB
archive_path = temp_dir / "large.tzst"
create_archive(archive_path, [str(large_file)])
# Test with streaming mode
archive_path_streaming = temp_dir / "large_streaming.tzst"
with TzstArchive(archive_path_streaming, mode="w", streaming=True) as archive:
archive.add(str(large_file), arcname=large_file.name)
# Both archives should exist
assert archive_path.exists()
assert archive_path_streaming.exists()
+253
View File
@@ -0,0 +1,253 @@
"""Tests for edge cases and error conditions in core.py to improve coverage.
This test file targets the specific missing lines identified in the coverage report,
focusing on error handling, edge cases, and less common code paths.
"""
import tarfile
from unittest.mock import Mock, patch
import pytest
from tzst.core import (
ConflictResolution,
TzstArchive,
_handle_file_conflict,
create_archive,
extract_archive,
)
class TestConflictResolutionEdgeCases:
"""Test edge cases in conflict resolution handling."""
def test_handle_file_conflict_invalid_string_resolution(self, temp_dir):
"""Test _handle_file_conflict with invalid string resolution."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing content")
# Test with invalid string - should fallback to ASK
result_action, result_path = _handle_file_conflict(
target_path, "invalid_resolution", None
) # Should fallback to REPLACE when no interactive callback
assert result_action == ConflictResolution.REPLACE
assert result_path == target_path
def test_handle_file_conflict_ask_with_callback(self, temp_dir):
"""Test ASK resolution with interactive callback."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing content")
# Mock interactive callback that returns REPLACE
mock_callback = Mock(return_value=ConflictResolution.REPLACE)
result_action, result_path = _handle_file_conflict(
target_path, ConflictResolution.ASK, mock_callback
)
assert result_action == ConflictResolution.REPLACE
assert result_path == target_path
mock_callback.assert_called_once_with(target_path)
def test_handle_file_conflict_ask_without_callback(self, temp_dir):
"""Test ASK resolution without interactive callback."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing content")
result_action, result_path = _handle_file_conflict(
target_path, ConflictResolution.ASK, None
) # Should default to REPLACE when no callback available
assert result_action == ConflictResolution.REPLACE
assert result_path == target_path
def test_handle_file_conflict_unknown_resolution(self, temp_dir):
"""Test handling of unknown resolution types."""
target_path = temp_dir / "existing.txt"
target_path.write_text("existing content")
# Create a mock enum value that's not handled
unknown_resolution = Mock()
unknown_resolution.name = "UNKNOWN"
result_action, result_path = _handle_file_conflict(
target_path, unknown_resolution, None
)
# Should default to REPLACE for unknown resolutions
assert result_action == ConflictResolution.REPLACE
assert result_path == target_path
class TestArchiveErrorHandling:
"""Test error handling in archive operations."""
def test_archive_streaming_extraction_error(self, temp_dir):
"""Test extraction error in streaming mode."""
# Create a simple archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
create_archive(archive_path, [test_file])
# Mock tarfile to raise StreamError
with patch("tarfile.open") as mock_open:
mock_tarfile = Mock()
mock_open.return_value.__enter__.return_value = mock_tarfile
mock_tarfile.extractall.side_effect = tarfile.StreamError(
"seeking not allowed"
)
archive = TzstArchive(archive_path, "r", streaming=True)
archive._tarfile = mock_tarfile
archive.streaming = True
with pytest.raises(
RuntimeError, match="Extraction failed in streaming mode"
):
archive.extractall(temp_dir / "extract")
def test_getmembers_archive_not_open(self):
"""Test getmembers when archive is not open."""
archive = TzstArchive("dummy.tzst", "r")
archive._tarfile = None
with pytest.raises(RuntimeError, match="Archive not open"):
archive.getmembers()
def test_getmembers_wrong_mode(self, temp_dir):
"""Test getmembers when archive is not in read mode."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, "w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getmembers()
def test_getnames_archive_not_open(self):
"""Test getnames when archive is not open."""
archive = TzstArchive("dummy.tzst", "r")
archive._tarfile = None
with pytest.raises(RuntimeError, match="Archive not open"):
archive.getnames()
def test_getnames_wrong_mode(self, temp_dir):
"""Test getnames when archive is not in read mode."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, "w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getnames()
class TestCreateArchiveErrorHandling:
"""Test error handling in archive creation."""
def test_create_archive_cleanup_on_error(self, temp_dir):
"""Test archive creation cleans up on error."""
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
# Test that errors during archive creation are properly handled
# Simulate error by trying to create archive with invalid compression level
with pytest.raises(ValueError, match="Invalid compression level"):
create_archive(archive_path, [test_file], compression_level=50)
# Archive should not be created when error occurs
assert not archive_path.exists()
def test_create_archive_common_path_no_common_parent(self, temp_dir):
"""Test create_archive when files have no common parent path."""
file1 = temp_dir / "file1.txt"
file1.write_text("content1")
with patch("os.path.commonpath", side_effect=ValueError("no common path")):
archive_path = temp_dir / "test.tzst"
# Should use parent of first file as fallback
create_archive(archive_path, [file1])
assert archive_path.exists()
class TestExtractArchiveEdgeCases:
"""Test edge cases in archive extraction."""
def test_extract_archive_exit_resolution(self, temp_dir):
"""Test extraction with EXIT conflict resolution."""
# Create archive with multiple files
file1 = temp_dir / "file1.txt"
file1.write_text("content1")
file2 = temp_dir / "file2.txt"
file2.write_text("content2")
archive_path = temp_dir / "test.tzst"
create_archive(archive_path, [file1, file2])
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Create a conflicting file
conflict_file = extract_dir / "file1.txt"
conflict_file.write_text("existing content")
# Extract with EXIT resolution
extract_archive(
archive_path, extract_dir, conflict_resolution=ConflictResolution.EXIT
)
# Should stop on first conflict
assert conflict_file.read_text() == "existing content"
assert not (extract_dir / "file2.txt").exists()
def test_extract_archive_members_with_state_break(self, temp_dir):
"""Test extraction of specific members with state break."""
# Create archive with multiple files
file1 = temp_dir / "file1.txt"
file1.write_text("content1")
file2 = temp_dir / "file2.txt"
file2.write_text("content2")
archive_path = temp_dir / "test.tzst"
create_archive(archive_path, [file1, file2])
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Mock ConflictResolutionState to return False for should_continue
with patch("tzst.core.ConflictResolutionState") as mock_state_class:
mock_state = Mock()
mock_state.should_continue.return_value = False
mock_state.apply_to_all = False
mock_state_class.return_value = mock_state
extract_archive(
archive_path, extract_dir, members=["file1.txt", "file2.txt"]
)
# Should break early due to state.should_continue()
assert mock_state.should_continue.called
def test_extract_archive_with_filter_parameter(self, temp_dir):
"""Test extraction using filter parameter."""
# Create archive with a file
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
create_archive(archive_path, [test_file])
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Mock filter function
def mock_filter(member, path):
return member
# Extract with filter (tests the filter parameter branch)
extract_archive(archive_path, extract_dir, filter=mock_filter)
extracted_file = extract_dir / "test.txt"
assert extracted_file.exists()
assert extracted_file.read_text() == "test content"
-395
View File
@@ -1,395 +0,0 @@
"""Tests to cover missing lines in core.py for improved coverage."""
import tarfile
from unittest.mock import MagicMock, patch
import pytest
from tzst.core import TzstArchive
from tzst.exceptions import TzstArchiveError, TzstDecompressionError
class TestCoreMissingLines:
"""Test specific missing lines in core.py."""
def test_append_mode_error_handling(self, temp_dir):
"""Test append mode error handling (lines 126-137)."""
archive_path = temp_dir / "test.tzst"
# Test append mode raises NotImplementedError
with pytest.raises(
NotImplementedError, match="Append mode is not currently supported"
):
TzstArchive(archive_path, mode="a")
def test_invalid_mode_error_after_open(self, temp_dir):
"""Test invalid mode error in __enter__ method (line 137)."""
archive_path = temp_dir / "test.tzst"
# Create archive instance with invalid mode after validation passes
archive = TzstArchive.__new__(TzstArchive)
archive.filename = archive_path
archive.mode = "invalid" # Set invalid mode after construction
archive.compression_level = 3
archive.streaming = False
archive._tarfile = None
archive._fileobj = None
archive._compressed_stream = None
with pytest.raises(TzstArchiveError, match="Failed to open archive"):
archive.__enter__()
def test_zstd_error_handling_in_open(self, temp_dir):
"""Test zstd error handling during archive opening (lines 133-137)."""
archive_path = temp_dir / "test.tzst"
# Create a file that will cause zstd decompression error
archive_path.write_bytes(b"invalid zstd data")
# Try to open as read mode - should raise TzstDecompressionError
with pytest.raises(TzstDecompressionError, match="Failed to open archive"):
with TzstArchive(archive_path, mode="r"):
pass
def test_generic_error_handling_in_open(self, temp_dir):
"""Test generic error handling during archive opening."""
archive_path = temp_dir / "test.tzst"
# Mock to raise a generic exception (not zstd-related)
with patch("builtins.open", side_effect=PermissionError("Permission denied")):
with pytest.raises(TzstArchiveError, match="Failed to open archive"):
with TzstArchive(archive_path, mode="r"):
pass
def test_close_error_handling(self, temp_dir):
"""Test error handling in close method (lines 146-157)."""
archive_path = temp_dir / "test.tzst"
# Create archive and manually set objects that will raise on close
with TzstArchive(archive_path, mode="w") as archive:
pass
# Now manually create problematic objects
archive = TzstArchive.__new__(TzstArchive)
archive._tarfile = MagicMock()
archive._tarfile.close.side_effect = Exception("Close error")
archive._compressed_stream = MagicMock()
archive._compressed_stream.close.side_effect = Exception("Close error")
archive._fileobj = MagicMock()
archive._fileobj.close.side_effect = Exception("Close error")
# close() should handle exceptions gracefully
archive.close() # Should not raise
assert archive._tarfile is None
assert archive._compressed_stream is None
assert archive._fileobj is None
def test_archive_not_open_for_reading_errors(self, temp_dir):
"""Test RuntimeError for operations on archives not open for reading (lines 186, 188, 192)."""
archive_path = temp_dir / "test.tzst"
# Create archive in write mode
with TzstArchive(archive_path, mode="w") as archive:
# Test getmembers() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getmembers()
# Test getnames() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getnames()
# Test extractfile() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.extractfile("test")
def test_streaming_member_extraction_error(self, temp_dir):
"""Test streaming mode member extraction error (lines 242, 249-254)."""
# Create a test archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Try to extract specific member in streaming mode
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
members = archive.getmembers()
member = members[0]
extract_dir = temp_dir / "extract"
extract_dir.mkdir() # Should raise RuntimeError for specific member extraction in streaming mode
with pytest.raises(
RuntimeError,
match="Extracting specific members is not supported in streaming mode",
):
archive.extract(member=member.name, path=extract_dir)
def test_streaming_extraction_failure_handling(self, temp_dir):
"""Test streaming extraction failure handling (lines 263-272)."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(
str(test_file), arcname="test.txt"
) # Mock tarfile to raise StreamError
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Mock extractall to raise StreamError with streaming-related message
with patch.object(
archive._tarfile,
"extractall",
side_effect=tarfile.StreamError("seeking not supported"),
):
with pytest.raises(
RuntimeError, match="Extraction failed in streaming mode"
):
archive.extract(path=extract_dir)
def test_extractfile_not_open_error(self, temp_dir):
"""Test extractfile when archive is not open (line 307)."""
archive_path = temp_dir / "test.tzst"
# Create closed archive
archive = TzstArchive(archive_path, mode="r")
# Don't open it
with pytest.raises(RuntimeError, match="Archive not open"):
archive.extractfile("test")
def test_extractfile_write_mode_error(self, temp_dir):
"""Test extractfile in write mode (already covered but ensuring line coverage)."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.extractfile("test")
def test_add_method_not_open_error(self, temp_dir):
"""Test add method when archive is not open (line 325, 327)."""
archive_path = temp_dir / "test.tzst"
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="w")
with pytest.raises(RuntimeError, match="Archive not open"):
archive.add(str(test_file))
def test_add_method_read_mode_error(self, temp_dir):
"""Test add method in read mode (line 327)."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Try to add to archive in read mode
with TzstArchive(archive_path, mode="r") as archive:
with pytest.raises(RuntimeError, match="Archive not open for writing"):
archive.add(str(test_file))
def test_file_not_found_in_add(self, temp_dir):
"""Test file not found error in add method (line 373)."""
archive_path = temp_dir / "test.tzst"
missing_file = temp_dir / "missing.txt"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(FileNotFoundError):
archive.add(str(missing_file))
def test_add_method_generic_error_handling(self, temp_dir):
"""Test generic error handling in add method (line 375)."""
archive_path = temp_dir / "test.tzst"
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
with TzstArchive(archive_path, mode="w") as archive:
# Mock add to raise generic exception
with patch.object(
archive._tarfile,
"add",
side_effect=PermissionError("Permission denied"),
):
with pytest.raises(TzstArchiveError, match="Failed to add"):
archive.add(str(test_file))
def test_test_method_not_open_error(self, temp_dir):
"""Test test method when archive is not open (line 390-391)."""
archive_path = temp_dir / "test.tzst"
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="r")
with pytest.raises(RuntimeError, match="Archive not open"):
archive.test()
def test_test_method_write_mode_error(self, temp_dir):
"""Test test method in write mode (line 391)."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.test()
def test_test_method_streaming_mode_info(self, temp_dir):
"""Test test method streaming mode information (line 427)."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Test in streaming mode - should provide different behavior info
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
# This should work but may have streaming-specific behavior
result = archive.test()
assert isinstance(result, bool)
def test_list_method_not_open_error(self, temp_dir):
"""Test list method when archive is not open (line 454-455)."""
archive_path = temp_dir / "test.tzst"
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="r")
with pytest.raises(RuntimeError, match="Archive not open"):
list(archive.list())
def test_list_method_write_mode_error(self, temp_dir):
"""Test list method in write mode (line 455)."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
list(archive.list())
def test_context_manager_exception_handling(self, temp_dir):
"""Test context manager exception handling (lines 502-504)."""
archive_path = temp_dir / "test.tzst"
# Store references for cleanup
fileobj = None
compressed_stream = None
tarfile_obj = None
# Test that close exceptions are suppressed during context manager exit
with patch("tzst.core.TzstArchive.close", side_effect=Exception("Close error")):
try:
with TzstArchive(archive_path, mode="w") as archive:
# Store references to underlying objects for manual cleanup
fileobj = archive._fileobj
compressed_stream = archive._compressed_stream
tarfile_obj = archive._tarfile
raise ValueError("Test exception")
except ValueError:
pass # Expected - the original exception should not be masked
finally:
# Manually clean up since mocked close() failed
try:
if tarfile_obj:
tarfile_obj.close()
except Exception:
pass
try:
if compressed_stream:
compressed_stream.close()
except Exception:
pass
try:
if fileobj:
fileobj.close()
except Exception:
pass
# Ensure the file is removed to prevent permission errors
try:
if archive_path.exists():
archive_path.unlink()
except (PermissionError, OSError):
pass
# The close exception should be suppressed by __exit__
def test_streaming_mode_directory_creation_error(self, temp_dir):
"""Test directory creation error in streaming mode (line 521)."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
# Mock path creation to fail
extract_dir = temp_dir / "extract"
with patch("pathlib.Path.mkdir", side_effect=OSError("Permission denied")):
with pytest.raises(OSError):
archive.extractall(path=extract_dir)
def test_list_verbose_mode_edge_cases(self, temp_dir):
"""Test list method verbose mode edge cases (lines 573, 588-589)."""
# Create archive with special files
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Create a directory
test_dir = temp_dir / "test_dir"
test_dir.mkdir()
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
archive.add(str(test_dir), arcname="test_dir")
with TzstArchive(archive_path, mode="r") as archive:
# Test verbose listing
items = list(archive.list(verbose=True))
assert len(items) >= 2
# Should have both file and directory entries
file_items = [item for item in items if item.get("is_file", False)]
dir_items = [item for item in items if item.get("is_dir", False)]
assert len(file_items) >= 1
assert len(dir_items) >= 1
def test_extractall_with_members_parameter(self, temp_dir):
"""Test extractall with members parameter for selective extraction."""
# Create archive with multiple files
test_file1 = temp_dir / "test1.txt"
test_file1.write_text("content1")
test_file2 = temp_dir / "test2.txt"
test_file2.write_text("content2")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file1), arcname="test1.txt")
archive.add(str(test_file2), arcname="test2.txt")
# Extract only specific members
with TzstArchive(archive_path, mode="r") as archive:
members = archive.getmembers()
first_member = members[0]
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Extract only first member
archive.extractall(path=extract_dir, members=[first_member])
# Verify only one file was extracted
extracted_files = list(extract_dir.glob("*.txt"))
assert len(extracted_files) == 1
-374
View File
@@ -1,374 +0,0 @@
"""Tests to cover missing lines in core.py for improved coverage."""
import tarfile
from unittest.mock import MagicMock, patch
import pytest
from tzst.core import TzstArchive
from tzst.exceptions import TzstArchiveError, TzstDecompressionError
class TestCoreMissingLines:
"""Test specific missing lines in core.py."""
def test_append_mode_error_handling(self, temp_dir):
"""Test append mode error handling (lines 126-137)."""
archive_path = temp_dir / "test.tzst"
# Test append mode raises NotImplementedError
with pytest.raises(
NotImplementedError, match="Append mode is not currently supported"
):
TzstArchive(archive_path, mode="a")
def test_invalid_mode_error_after_open(self, temp_dir):
"""Test invalid mode error in __enter__ method (line 137)."""
archive_path = temp_dir / "test.tzst"
# Create archive instance with invalid mode after validation passes
archive = TzstArchive.__new__(TzstArchive)
archive.filename = archive_path
archive.mode = "invalid" # Set invalid mode after construction
archive.compression_level = 3
archive.streaming = False
archive._tarfile = None
archive._fileobj = None
archive._compressed_stream = None
with pytest.raises(TzstArchiveError, match="Failed to open archive"):
archive.__enter__()
def test_zstd_error_handling_in_open(self, temp_dir):
"""Test zstd error handling during archive opening (lines 133-137)."""
archive_path = temp_dir / "test.tzst"
# Create a file that will cause zstd decompression error
archive_path.write_bytes(b"invalid zstd data")
# Try to open as read mode - should raise TzstDecompressionError
with pytest.raises(TzstDecompressionError, match="Failed to open archive"):
with TzstArchive(archive_path, mode="r"):
pass
def test_generic_error_handling_in_open(self, temp_dir):
"""Test generic error handling during archive opening."""
archive_path = temp_dir / "test.tzst"
# Mock to raise a generic exception (not zstd-related)
with patch("builtins.open", side_effect=PermissionError("Permission denied")):
with pytest.raises(TzstArchiveError, match="Failed to open archive"):
with TzstArchive(archive_path, mode="r"):
pass
def test_close_error_handling(self, temp_dir):
"""Test error handling in close method (lines 146-157)."""
archive_path = temp_dir / "test.tzst"
# Create archive and manually set objects that will raise on close
with TzstArchive(archive_path, mode="w") as archive:
pass
# Now manually create problematic objects
archive = TzstArchive.__new__(TzstArchive)
archive._tarfile = MagicMock()
archive._tarfile.close.side_effect = Exception("Close error")
archive._compressed_stream = MagicMock()
archive._compressed_stream.close.side_effect = Exception("Close error")
archive._fileobj = MagicMock()
archive._fileobj.close.side_effect = Exception("Close error")
# close() should handle exceptions gracefully
archive.close() # Should not raise
assert archive._tarfile is None
assert archive._compressed_stream is None
assert archive._fileobj is None
def test_archive_not_open_for_reading_errors(self, temp_dir):
"""Test RuntimeError for operations on archives not open for reading."""
archive_path = temp_dir / "test.tzst"
# Create archive in write mode
with TzstArchive(archive_path, mode="w") as archive:
# Test getmembers() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getmembers()
# Test getnames() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.getnames()
# Test extractfile() on write mode
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.extractfile("test")
def test_streaming_member_extraction_error(self, temp_dir):
"""Test streaming mode member extraction error."""
# Create a test archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Try to extract specific member in streaming mode
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Should raise RuntimeError for specific member extraction in streaming mode
with pytest.raises(
RuntimeError,
match="Extracting specific members is not supported in streaming mode",
):
archive.extract(member="test.txt", path=extract_dir)
def test_streaming_extraction_failure_handling(self, temp_dir):
"""Test streaming extraction failure handling."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Mock tarfile to raise StreamError
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Mock extract to raise StreamError with streaming-related message
with patch.object(
archive._tarfile,
"extractall",
side_effect=tarfile.StreamError("seeking not supported"),
):
with pytest.raises(
RuntimeError, match="Extraction failed in streaming mode"
):
archive.extract(path=extract_dir)
def test_extractfile_not_open_error(self, temp_dir):
"""Test extractfile when archive is not open."""
archive_path = temp_dir / "test.tzst"
# Create closed archive
archive = TzstArchive(archive_path, mode="r")
# Don't open it
with pytest.raises(RuntimeError, match="Archive not open"):
archive.extractfile("test")
def test_extractfile_write_mode_error(self, temp_dir):
"""Test extractfile in write mode."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.extractfile("test")
def test_add_method_not_open_error(self, temp_dir):
"""Test add method when archive is not open."""
archive_path = temp_dir / "test.tzst"
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="w")
with pytest.raises(RuntimeError, match="Archive not open"):
archive.add(str(test_file))
def test_add_method_read_mode_error(self, temp_dir):
"""Test add method in read mode."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Try to add to archive in read mode
with TzstArchive(archive_path, mode="r") as archive:
with pytest.raises(RuntimeError, match="Archive not open for writing"):
archive.add(str(test_file))
def test_file_not_found_in_add(self, temp_dir):
"""Test file not found error in add method."""
archive_path = temp_dir / "test.tzst"
missing_file = temp_dir / "missing.txt"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(FileNotFoundError):
archive.add(str(missing_file))
def test_add_method_generic_error_handling(self, temp_dir):
"""Test generic error handling in add method."""
archive_path = temp_dir / "test.tzst"
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
with TzstArchive(archive_path, mode="w") as archive:
# Mock add to raise generic exception
with patch.object(
archive._tarfile,
"add",
side_effect=PermissionError("Permission denied"),
):
with pytest.raises(TzstArchiveError, match="Failed to add"):
archive.add(str(test_file))
def test_test_method_not_open_error(self, temp_dir):
"""Test test method when archive is not open."""
archive_path = temp_dir / "test.tzst"
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="r")
with pytest.raises(RuntimeError, match="Archive not open"):
archive.test()
def test_test_method_write_mode_error(self, temp_dir):
"""Test test method in write mode."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
archive.test()
def test_test_method_streaming_mode_info(self, temp_dir):
"""Test test method streaming mode information."""
# Create archive first
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
# Test in streaming mode - should provide different behavior info
with TzstArchive(archive_path, mode="r", streaming=True) as archive:
# This should work but may have streaming-specific behavior
result = archive.test()
assert isinstance(result, bool)
def test_list_method_not_open_error(self, temp_dir):
"""Test list method when archive is not open."""
archive_path = temp_dir / "test.tzst"
# Create archive but don't open it
archive = TzstArchive(archive_path, mode="r")
with pytest.raises(RuntimeError, match="Archive not open"):
list(archive.list())
def test_list_method_write_mode_error(self, temp_dir):
"""Test list method in write mode."""
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
with pytest.raises(RuntimeError, match="Archive not open for reading"):
list(archive.list())
def test_context_manager_exception_handling(self, temp_dir):
"""Test context manager exception handling."""
archive_path = temp_dir / "test.tzst"
# Store references for cleanup
fileobj = None
compressed_stream = None
tarfile_obj = None
# Test that close exceptions are suppressed during context manager exit
with patch("tzst.core.TzstArchive.close", side_effect=Exception("Close error")):
try:
with TzstArchive(archive_path, mode="w") as archive:
# Store references to underlying objects for manual cleanup
fileobj = archive._fileobj
compressed_stream = archive._compressed_stream
tarfile_obj = archive._tarfile
raise ValueError("Test exception")
except ValueError:
pass # Expected - the original exception should not be masked
finally:
# Manually clean up since mocked close() failed
try:
if tarfile_obj:
tarfile_obj.close()
except Exception:
pass
try:
if compressed_stream:
compressed_stream.close()
except Exception:
pass
try:
if fileobj:
fileobj.close()
except Exception:
pass
# Ensure the file is removed to prevent permission errors
try:
if archive_path.exists():
archive_path.unlink()
except (PermissionError, OSError):
pass
# The close exception should be suppressed by __exit__
def test_list_verbose_mode_edge_cases(self, temp_dir):
"""Test list method verbose mode edge cases."""
# Create archive with special files
test_file = temp_dir / "test.txt"
test_file.write_text("test content")
# Create a directory
test_dir = temp_dir / "test_dir"
test_dir.mkdir()
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file), arcname="test.txt")
archive.add(str(test_dir), arcname="test_dir")
with TzstArchive(archive_path, mode="r") as archive:
# Test verbose listing
items = list(archive.list(verbose=True))
assert len(items) >= 2
# Should have both file and directory entries
file_items = [item for item in items if item.get("is_file", False)]
dir_items = [item for item in items if item.get("is_dir", False)]
assert len(file_items) >= 1
assert len(dir_items) >= 1
def test_extract_with_members_parameter(self, temp_dir):
"""Test extract with specific member for selective extraction."""
# Create archive with multiple files
test_file1 = temp_dir / "test1.txt"
test_file1.write_text("content1")
test_file2 = temp_dir / "test2.txt"
test_file2.write_text("content2")
archive_path = temp_dir / "test.tzst"
with TzstArchive(archive_path, mode="w") as archive:
archive.add(str(test_file1), arcname="test1.txt")
archive.add(str(test_file2), arcname="test2.txt")
# Extract only specific member
with TzstArchive(archive_path, mode="r") as archive:
extract_dir = temp_dir / "extract"
extract_dir.mkdir()
# Extract only first member
archive.extract(member="test1.txt", path=extract_dir)
# Verify only one file was extracted
extracted_files = list(extract_dir.glob("*.txt"))
assert len(extracted_files) == 1
assert extracted_files[0].name == "test1.txt"
+5
View File
@@ -1,8 +1,11 @@
"""Tests for TzstArchive class core functionality."""
import pytest
from tzst import TzstArchive
@pytest.mark.unit
class TestBasicImportAndCreation:
"""Test basic import and creation functionality."""
@@ -17,6 +20,7 @@ class TestBasicImportAndCreation:
assert archive.mode == "r"
@pytest.mark.unit
class TestTzstArchiveBasics:
"""Test basic TzstArchive class functionality."""
@@ -107,6 +111,7 @@ class TestTzstArchiveBasics:
assert "gid" in item
@pytest.mark.unit
class TestTzstArchiveStreamingMode:
"""Test streaming mode functionality."""
+4
View File
@@ -6,6 +6,7 @@ from tzst import create_archive, extract_archive, list_archive
from tzst import test_archive as tzst_test_archive
@pytest.mark.unit
class TestConvenienceFunctions:
"""Test the convenience functions."""
@@ -111,6 +112,7 @@ class TestConvenienceFunctions:
assert extract_dir_streaming.exists()
@pytest.mark.unit
class TestAtomicOperations:
"""Test atomic file operations."""
@@ -154,6 +156,7 @@ class TestAtomicOperations:
assert len(temp_files) == 0
@pytest.mark.unit
class TestCompressionLevels:
"""Test compression level validation and functionality."""
@@ -180,6 +183,7 @@ class TestCompressionLevels:
assert "1" in str(exc_info.value) and "22" in str(exc_info.value)
@pytest.mark.unit
class TestEdgeCaseCoverage:
"""Test edge cases to improve coverage."""
+6 -4
View File
@@ -8,6 +8,7 @@ from tzst import TzstArchive, create_archive, extract_archive
from tzst import test_archive as tzst_test_archive
@pytest.mark.unit
class TestErrorHandling:
"""Test error handling."""
@@ -37,6 +38,7 @@ class TestErrorHandling:
create_archive(archive_path, [fake_file])
@pytest.mark.unit
class TestSecurityFiltering:
"""Test security filtering mechanisms."""
@@ -50,11 +52,11 @@ class TestSecurityFiltering:
# Extract with tar filter
extract_dir = temp_dir / "tar_filtered"
with patch("tzst.core.TzstArchive.extract") as mock_extract:
with patch("tzst.core.TzstArchive.extractall") as mock_extractall:
extract_archive(archive_path, extract_dir, filter="tar")
# Verify filter was passed
call_args = mock_extract.call_args
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "tar"
def test_data_filter_extraction(self, sample_files, temp_dir):
@@ -67,11 +69,11 @@ class TestSecurityFiltering:
# Extract with data filter (default for security)
extract_dir = temp_dir / "data_filtered"
with patch("tzst.core.TzstArchive.extract") as mock_extract:
with patch("tzst.core.TzstArchive.extractall") as mock_extractall:
extract_archive(archive_path, extract_dir, filter="data")
# Verify filter was passed
call_args = mock_extract.call_args
call_args = mock_extractall.call_args
assert call_args[1]["filter"] == "data"
def test_invalid_filter_raises_error(self, sample_files, temp_dir):