Skip to content

Commit 011279a

Browse files
committed
fix: resolve CI failures
- Update pytest coverage to use nutrient_dws instead of nutrient - Fix ruff configuration deprecation warnings in pyproject.toml - Apply ruff formatting to all Python files - Fix mypy type checking errors in DirectAPIMixin - Add Protocol typing to resolve circular import issues - Use cast for type checking in methods that use build() and _http_client The CI should now pass with all tests, linting, and type checking.
1 parent 44222d4 commit 011279a

File tree

13 files changed

+201
-210
lines changed

13 files changed

+201
-210
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
run: mypy src tests
4444

4545
- name: Run tests with pytest
46-
run: pytest -v --cov=nutrient --cov-report=xml --cov-report=term
46+
run: pytest -v --cov=nutrient_dws --cov-report=xml --cov-report=term
4747

4848
- name: Upload coverage to Codecov
4949
uses: codecov/codecov-action@v4

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ nutrient_dws = ["py.typed"]
6464
[tool.ruff]
6565
target-version = "py38"
6666
line-length = 100
67+
68+
[tool.ruff.lint]
6769
select = [
6870
"E", # pycodestyle errors
6971
"W", # pycodestyle warnings
@@ -84,10 +86,10 @@ ignore = [
8486
"D107", # Missing docstring in __init__
8587
]
8688

87-
[tool.ruff.pydocstyle]
89+
[tool.ruff.lint.pydocstyle]
8890
convention = "google"
8991

90-
[tool.ruff.per-file-ignores]
92+
[tool.ruff.lint.per-file-ignores]
9193
"tests/*" = ["D", "S101"] # Don't require docstrings in tests, allow asserts
9294

9395
[tool.mypy]

src/nutrient_dws/api/direct.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,40 @@
44
for supported document processing operations.
55
"""
66

7-
from typing import TYPE_CHECKING, Any, List, Optional
7+
from typing import TYPE_CHECKING, Any, List, Optional, Protocol, cast
88

99
from nutrient_dws.file_handler import FileInput
1010

1111
if TYPE_CHECKING:
12-
from nutrient_dws.client import NutrientClient
12+
from nutrient_dws.builder import BuildAPIWrapper
13+
from nutrient_dws.http_client import HTTPClient
14+
15+
16+
class HasBuildMethod(Protocol):
17+
"""Protocol for objects that have a build method."""
18+
19+
def build(self, input_file: FileInput) -> "BuildAPIWrapper":
20+
"""Build method signature."""
21+
...
22+
23+
@property
24+
def _http_client(self) -> "HTTPClient":
25+
"""HTTP client property."""
26+
...
1327

1428

1529
class DirectAPIMixin:
1630
"""Mixin class containing Direct API methods.
17-
31+
1832
These methods provide a simplified interface to common document
1933
processing operations. They internally use the Build API.
20-
34+
2135
Note: The API automatically converts supported document formats
2236
(DOCX, XLSX, PPTX) to PDF when processing.
2337
"""
2438

2539
def _process_file(
26-
self: "NutrientClient",
40+
self,
2741
tool: str,
2842
input_file: FileInput,
2943
output_path: Optional[str] = None,
@@ -40,7 +54,7 @@ def convert_to_pdf(
4054
"""Convert a document to PDF.
4155
4256
Converts Office documents (DOCX, XLSX, PPTX) to PDF format.
43-
This uses the API's implicit conversion - simply uploading a
57+
This uses the API's implicit conversion - simply uploading a
4458
non-PDF document returns it as a PDF.
4559
4660
Args:
@@ -53,12 +67,14 @@ def convert_to_pdf(
5367
Raises:
5468
AuthenticationError: If API key is missing or invalid.
5569
APIError: For other API errors (e.g., unsupported format).
56-
70+
5771
Note:
5872
HTML files are not currently supported by the API.
5973
"""
6074
# Use builder with no actions - implicit conversion happens
61-
return self.build(input_file).execute(output_path) # type: ignore
75+
if TYPE_CHECKING:
76+
self = cast(HasBuildMethod, self)
77+
return self.build(input_file).execute(output_path)
6278

6379
def flatten_annotations(
6480
self, input_file: FileInput, output_path: Optional[str] = None
@@ -120,7 +136,7 @@ def ocr_pdf(
120136
"""Apply OCR to a PDF to make it searchable.
121137
122138
Performs optical character recognition on the PDF to extract text
123-
and make it searchable. If input is an Office document, it will
139+
and make it searchable. If input is an Office document, it will
124140
be converted to PDF first.
125141
126142
Args:
@@ -199,7 +215,7 @@ def apply_redactions(
199215
"""Apply redaction annotations to permanently remove content.
200216
201217
Applies any redaction annotations in the PDF to permanently remove
202-
the underlying content. If input is an Office document, it will
218+
the underlying content. If input is an Office document, it will
203219
be converted to PDF first.
204220
205221
Args:
@@ -216,14 +232,14 @@ def apply_redactions(
216232
return self._process_file("apply-redactions", input_file, output_path)
217233

218234
def merge_pdfs(
219-
self: "NutrientClient",
235+
self,
220236
input_files: List[FileInput],
221237
output_path: Optional[str] = None,
222238
) -> Optional[bytes]:
223239
"""Merge multiple PDF files into one.
224240
225241
Combines multiple files into a single PDF in the order provided.
226-
Office documents (DOCX, XLSX, PPTX) will be automatically converted
242+
Office documents (DOCX, XLSX, PPTX) will be automatically converted
227243
to PDF before merging.
228244
229245
Args:
@@ -237,7 +253,7 @@ def merge_pdfs(
237253
AuthenticationError: If API key is missing or invalid.
238254
APIError: For other API errors.
239255
ValueError: If less than 2 files provided.
240-
256+
241257
Example:
242258
# Merge PDFs and Office documents
243259
client.merge_pdfs([
@@ -262,12 +278,11 @@ def merge_pdfs(
262278
parts.append({"file": field_name})
263279

264280
# Build instructions for merge (no actions needed)
265-
instructions = {
266-
"parts": parts,
267-
"actions": []
268-
}
281+
instructions = {"parts": parts, "actions": []}
269282

270283
# Make API request
284+
if TYPE_CHECKING:
285+
self = cast(HasBuildMethod, self)
271286
result = self._http_client.post(
272287
"/build",
273288
files=files,

src/nutrient_dws/builder.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class BuildAPIWrapper:
2121

2222
def __init__(self, client: Any, input_file: FileInput) -> None:
2323
"""Initialize builder with client and input file.
24-
24+
2525
Args:
2626
client: NutrientClient instance.
2727
input_file: Input file to process.
@@ -35,7 +35,7 @@ def __init__(self, client: Any, input_file: FileInput) -> None:
3535

3636
def _add_file_part(self, file: FileInput, name: str) -> None:
3737
"""Add an additional file part for operations like merge.
38-
38+
3939
Args:
4040
file: File to add.
4141
name: Name for the file part.
@@ -52,7 +52,7 @@ def add_step(self, tool: str, options: Optional[Dict[str, Any]] = None) -> "Buil
5252
5353
Returns:
5454
Self for method chaining.
55-
55+
5656
Example:
5757
>>> builder.add_step(tool="rotate-pages", options={"degrees": 180})
5858
"""
@@ -62,13 +62,13 @@ def add_step(self, tool: str, options: Optional[Dict[str, Any]] = None) -> "Buil
6262

6363
def set_output_options(self, **options: Any) -> "BuildAPIWrapper":
6464
"""Set output options for the final document.
65-
65+
6666
Args:
6767
**options: Output options (e.g., metadata, optimization).
68-
68+
6969
Returns:
7070
Self for method chaining.
71-
71+
7272
Example:
7373
>>> builder.set_output_options(
7474
... metadata={"title": "My Document", "author": "John Doe"},
@@ -86,7 +86,7 @@ def execute(self, output_path: Optional[str] = None) -> Optional[bytes]:
8686
8787
Returns:
8888
Processed file bytes, or None if output_path is provided.
89-
89+
9090
Raises:
9191
AuthenticationError: If API key is missing or invalid.
9292
APIError: For other API errors.
@@ -116,7 +116,7 @@ def execute(self, output_path: Optional[str] = None) -> Optional[bytes]:
116116

117117
def _build_instructions(self) -> Dict[str, Any]:
118118
"""Build the instructions payload for the API.
119-
119+
120120
Returns:
121121
Instructions dictionary for the Build API.
122122
"""
@@ -133,11 +133,11 @@ def _build_instructions(self) -> Dict[str, Any]:
133133

134134
def _map_tool_to_action(self, tool: str, options: Dict[str, Any]) -> Dict[str, Any]:
135135
"""Map tool name and options to Build API action format.
136-
136+
137137
Args:
138138
tool: Tool identifier.
139139
options: Tool options.
140-
140+
141141
Returns:
142142
Action dictionary for the Build API.
143143
"""

src/nutrient_dws/client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def build(self, input_file: FileInput) -> BuildAPIWrapper:
5959
6060
Returns:
6161
BuildAPIWrapper instance for chaining operations.
62-
62+
6363
Example:
6464
>>> builder = client.build(input_file="document.pdf")
6565
>>> builder.add_step(tool="rotate-pages", options={"degrees": 90})
@@ -75,19 +75,19 @@ def _process_file(
7575
**options: Any,
7676
) -> Optional[bytes]:
7777
"""Process a file using the Direct API.
78-
78+
7979
This is the internal method used by all Direct API methods.
8080
It internally uses the Build API with a single action.
81-
81+
8282
Args:
8383
tool: The tool identifier from the API.
8484
input_file: Input file to process.
8585
output_path: Optional path to save the output.
8686
**options: Tool-specific options.
87-
87+
8888
Returns:
8989
Processed file as bytes, or None if output_path is provided.
90-
90+
9191
Raises:
9292
AuthenticationError: If API key is missing or invalid.
9393
APIError: For other API errors.

src/nutrient_dws/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class NutrientError(Exception):
1111

1212
class AuthenticationError(NutrientError):
1313
"""Raised when authentication fails (401/403 errors).
14-
14+
1515
This typically indicates:
1616
- Missing API key
1717
- Invalid API key

src/nutrient_dws/http_client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class HTTPClient:
1818

1919
def __init__(self, api_key: Optional[str], timeout: int = 300) -> None:
2020
"""Initialize HTTP client with authentication.
21-
21+
2222
Args:
2323
api_key: API key for authentication.
2424
timeout: Request timeout in seconds.
@@ -61,13 +61,13 @@ def _create_session(self) -> requests.Session:
6161

6262
def _handle_response(self, response: requests.Response) -> bytes:
6363
"""Handle API response and raise appropriate exceptions.
64-
64+
6565
Args:
6666
response: Response from the API.
67-
67+
6868
Returns:
6969
Response content as bytes.
70-
70+
7171
Raises:
7272
AuthenticationError: For 401/403 responses.
7373
ValidationError: For 422 responses.
@@ -129,7 +129,7 @@ def post(
129129
130130
Returns:
131131
Response content as bytes.
132-
132+
133133
Raises:
134134
AuthenticationError: If API key is missing or invalid.
135135
TimeoutError: If request times out.

tests/integration/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ def pytest_addoption(parser):
3131
"--unit-only",
3232
action="store_true",
3333
default=False,
34-
help="Run only unit tests, skip integration tests"
34+
help="Run only unit tests, skip integration tests",
3535
)
3636
parser.addoption(
3737
"--integration-only",
3838
action="store_true",
3939
default=False,
40-
help="Run only integration tests, skip unit tests"
41-
)
40+
help="Run only integration tests, skip unit tests",
41+
)

0 commit comments

Comments
 (0)