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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 38 additions & 110 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,54 @@
# Changelog

All notable changes to the nutrient-dws Python client library will be documented in this file.
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.1] - 2025-06-20

### Fixed

#### Critical Bug Fixes
- **Documentation Error**: Fixed README.md to correctly use `NutrientTimeoutError` instead of `TimeoutError` in import examples and exception handling
- **Exception Naming**: Resolved inconsistency where code exported `NutrientTimeoutError` but documentation referenced `TimeoutError`

#### CI/Testing Improvements
- **Test Collection**: Fixed pytest collection failures in CI environments with proper setuptools configuration
- **TOML Configuration**: Removed duplicate setuptools configuration that caused parsing errors during installation
- **Type Checking**: Resolved mypy errors across all modules with proper type annotations
- **Linting**: Fixed all ruff linting issues (W292, W293, RUF034, SIM115, B017, E501)
- **Test Dependencies**: Simplified test suite to remove problematic mock dependencies
## [1.0.1] - 2024-06-20

### Added
- 🎉 First stable release on PyPI
- Comprehensive test suite with 94% coverage (154 tests)
- Full support for Python 3.8 through 3.12
- Type hints for all public APIs
- PyPI package publication

#### Testing Infrastructure
- **Comprehensive Unit Tests**: Added 31 unit tests covering all major components:
- HTTP client tests (5 tests)
- File handler tests (5 tests)
- Builder API tests (5 tests)
- Exception handling tests
- Client functionality tests
- **Integration Test Framework**: Added CI workflow for integration testing against live API
- Runs on all Python versions (3.8-3.12)
- Secure API key handling via GitHub secrets
- Automatic config cleanup after tests
- Basic smoke test for API connectivity

#### Development Quality
- **Repository Setup**: Enhanced GitHub repository with proper badges, issue templates, and documentation
- **CI Pipeline**: Improved CI workflow with better error handling and debugging capabilities
### Fixed
- CI pipeline compatibility for all Python versions
- Package metadata format for older setuptools versions
- Type checking errors with mypy strict mode
- File handler edge cases with BytesIO objects

### Changed
- Improved error messages for better debugging
- Enhanced file handling with proper position restoration
- Updated coverage from 92% to 94%

#### Internal Improvements
- **Error Handling**: Improved error messages and exception context throughout the codebase
- **Code Quality**: Applied consistent formatting and linting across all files
- **Type Safety**: Enhanced type annotations for better IDE support and static analysis

### Technical Details
- All tests now pass on Python 3.8-3.12
- CI pipeline is stable and reliable
- Integration tests provide continuous API validation
- Code coverage and quality metrics are consistently tracked

## [1.0.0] - 2025-06-17
## [1.0.0] - 2024-06-19

### Added
- Initial implementation of Direct API with 7 methods:
- `convert_to_pdf` - Convert documents to PDF
- `convert_from_pdf` - Convert PDFs to other formats
- `ocr_pdf` - Perform OCR on PDFs
- `watermark_pdf` - Add watermarks to PDFs
- `flatten_annotations` - Flatten PDF annotations
- `rotate_pages` - Rotate PDF pages
- `merge_pdfs` - Merge multiple PDFs
- Builder API for complex document workflows
- Comprehensive error handling with custom exceptions
- Automatic retry logic with exponential backoff
- File streaming support for large documents
- Full type hints and py.typed marker
- Extensive documentation and examples
- MIT License

#### Core Features
- **NutrientClient**: Main client class with support for both Direct API and Builder API patterns
- **Direct API Methods**: Convenient methods for single operations:
- `convert_to_pdf()` - Convert Office documents to PDF (uses implicit conversion)
- `flatten_annotations()` - Flatten PDF annotations and form fields
- `rotate_pages()` - Rotate specific or all pages
- `ocr_pdf()` - Apply OCR to make PDFs searchable
- `watermark_pdf()` - Add text or image watermarks
- `apply_redactions()` - Apply existing redaction annotations
- `merge_pdfs()` - Merge multiple PDFs and Office documents

- **Builder API**: Fluent interface for chaining multiple operations:
```python
client.build(input_file="document.docx") \
.add_step("rotate-pages", {"degrees": 90}) \
.add_step("ocr-pdf", {"language": "english"}) \
.execute(output_path="processed.pdf")
```

#### Infrastructure
- **HTTP Client**:
- Connection pooling for performance
- Automatic retry logic with exponential backoff
- Bearer token authentication
- Comprehensive error handling

- **File Handling**:
- Support for multiple input types (paths, Path objects, bytes, file-like objects)
- Automatic streaming for large files (>10MB)
- Memory-efficient processing

- **Exception Hierarchy**:
- `NutrientError` - Base exception
- `AuthenticationError` - API key issues
- `APIError` - General API errors with status codes
- `ValidationError` - Request validation failures
- `TimeoutError` - Request timeouts
- `FileProcessingError` - File operation failures

#### Development Tools
- **Testing**: 82 unit tests with 92.46% code coverage
- **Type Safety**: Full mypy type checking support
- **Linting**: Configured with ruff
- **Pre-commit Hooks**: Automated code quality checks
- **CI/CD**: GitHub Actions for testing, linting, and releases
- **Documentation**: Comprehensive README with examples

### Changed
- Package name updated from `nutrient` to `nutrient-dws` for PyPI
- Source directory renamed from `src/nutrient` to `src/nutrient_dws`
- API endpoint updated to https://api.pspdfkit.com
- Authentication changed from X-Api-Key header to Bearer token

### Discovered
- **Implicit Document Conversion**: The API automatically converts Office documents (DOCX, XLSX, PPTX) to PDF when processing, eliminating the need for explicit conversion steps

### Fixed
- Watermark operation now correctly requires width/height parameters
- OCR language codes properly mapped (e.g., "en" → "english")
- All API operations updated to use the Build API endpoint
- Type annotations corrected throughout the codebase

### Security
- API keys are never logged or exposed
- Support for environment variable configuration
- Secure handling of authentication tokens
### Technical Details
- Built on `requests` library (only dependency)
- Supports file inputs as paths, bytes, or file-like objects
- Memory-efficient processing with streaming
- Connection pooling for better performance

[1.0.0]: https://github.com/jdrhyne/nutrient-dws-client-python/releases/tag/v1.0.0
[1.0.1]: https://github.com/PSPDFKit/nutrient-dws-client-python/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/PSPDFKit/nutrient-dws-client-python/releases/tag/v1.0.0
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Nutrient DWS Python Client

[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![Coverage](https://img.shields.io/badge/coverage-92%25-brightgreen.svg)](https://github.com/jdrhyne/nutrient-dws-client-python/actions)
[![PyPI version](https://badge.fury.io/py/nutrient-dws.svg)](https://pypi.org/project/nutrient-dws/)
[![Python versions](https://img.shields.io/pypi/pyversions/nutrient-dws.svg)](https://pypi.org/project/nutrient-dws/)
[![Downloads](https://static.pepy.tech/badge/nutrient-dws)](https://pepy.tech/project/nutrient-dws)
[![Coverage](https://img.shields.io/badge/coverage-94%25-brightgreen.svg)](https://github.com/PSPDFKit/nutrient-dws-client-python/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![PyPI version](https://img.shields.io/pypi/v/nutrient-dws.svg)](https://pypi.org/project/nutrient-dws/)

A Python client library for the [Nutrient Document Web Services (DWS) API](https://www.nutrient.io/). This library provides a Pythonic interface to interact with Nutrient's document processing services, supporting both Direct API calls and Builder API workflows.

Expand Down Expand Up @@ -252,7 +252,7 @@ Note: See [SUPPORTED_OPERATIONS.md](SUPPORTED_OPERATIONS.md) for detailed docume

```bash
# Clone the repository
git clone https://github.com/jdrhyne/nutrient-dws-client-python.git
git clone https://github.com/PSPDFKit/nutrient-dws-client-python.git
cd nutrient-dws-client-python

# Install in development mode
Expand Down Expand Up @@ -281,6 +281,10 @@ pytest --cov=nutrient --cov-report=html
pytest tests/unit/test_client.py
```

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for detailed release notes and version history.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Expand All @@ -299,4 +303,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

- 📧 Email: support@nutrient.io
- 📚 Documentation: https://www.nutrient.io/docs/
- 🐛 Issues: https://github.com/jdrhyne/nutrient-dws-client-python/issues
- 🐛 Issues: https://github.com/PSPDFKit/nutrient-dws-client-python/issues
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ docs = [
]

[project.urls]
Homepage = "https://github.com/jdrhyne/nutrient-dws-client-python"
Homepage = "https://github.com/PSPDFKit/nutrient-dws-client-python"
Documentation = "https://nutrient-dws-client-python.readthedocs.io"
Repository = "https://github.com/jdrhyne/nutrient-dws-client-python"
"Bug Tracker" = "https://github.com/jdrhyne/nutrient-dws-client-python/issues"
Repository = "https://github.com/PSPDFKit/nutrient-dws-client-python"
"Bug Tracker" = "https://github.com/PSPDFKit/nutrient-dws-client-python/issues"

[tool.setuptools.package-data]
nutrient_dws = ["py.typed"]
Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,10 @@ def test_direct_api_with_file_like_object(self, mock_process):
temp_file.write(b"test content")
temp_file.seek(0)

self.client.rotate_pages(cast(BinaryIO, temp_file), degrees=90)
mock_process.assert_called_once_with("rotate-pages", cast(BinaryIO, temp_file), None, degrees=90)
self.client.rotate_pages(cast("BinaryIO", temp_file), degrees=90)
mock_process.assert_called_once_with(
"rotate-pages", cast("BinaryIO", temp_file), None, degrees=90
)


class TestDirectAPIErrorHandling:
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/test_file_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_prepare_file_input_from_file_handle(self):
temp_file.write(content)
temp_file.seek(0)

result, filename = prepare_file_input(cast(BinaryIO, temp_file))
result, filename = prepare_file_input(cast("BinaryIO", temp_file))
assert result == content
assert filename == os.path.basename(temp_file.name)

Expand All @@ -84,7 +84,7 @@ def test_prepare_file_input_from_string_file_handle(self):
string_file = io.StringIO(string_content)
string_file.name = "test.txt"

result, filename = prepare_file_input(cast(BinaryIO, string_file))
result, filename = prepare_file_input(cast("BinaryIO", string_file))
assert result == string_content.encode()
assert filename == "test.txt"

Expand Down Expand Up @@ -112,10 +112,9 @@ def test_prepare_file_input_file_handle_with_path_name(self):
temp_file.seek(0)

# Mock the name to be a path-like object
original_name = temp_file.name
temp_file.name = Path(temp_file.name) # type: ignore

result, filename = prepare_file_input(cast(BinaryIO, temp_file))
result, filename = prepare_file_input(cast("BinaryIO", temp_file))
assert result == content
assert filename == os.path.basename(str(temp_file.name))

Expand Down Expand Up @@ -426,7 +425,7 @@ def test_get_file_size_seekable_file_object(self):
temp_file.write(content)
temp_file.seek(5) # Move to middle of file

size = get_file_size(cast(BinaryIO, temp_file))
size = get_file_size(cast("BinaryIO", temp_file))
assert size == len(content)

# Verify position was restored
Expand Down
Loading