Skip to content

Commit 4e85f9d

Browse files
committed
fix: CI pipeline linting and type checking errors
- Fixed RUF034: Removed unnecessary conditional expression in generate_api_methods.py - Fixed SIM115: Added noqa comment for intentional file handle without context manager - Fixed E501: Split long lines in test files to meet 100 character limit - Fixed B017: Replaced generic Exception with specific APIError in test assertion - Fixed mypy type checking errors by adding proper type ignore comments - All linting (ruff) and type checking (mypy) now pass successfully
1 parent 011279a commit 4e85f9d

File tree

9 files changed

+108
-93
lines changed

9 files changed

+108
-93
lines changed

scripts/build_package.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,34 @@
55
import sys
66
from pathlib import Path
77

8+
89
def main():
910
"""Build the package."""
1011
root_dir = Path(__file__).parent.parent
11-
12+
1213
# Clean previous builds
1314
print("Cleaning previous builds...")
1415
for dir_name in ["dist", "build", "*.egg-info"]:
1516
subprocess.run(["rm", "-rf", str(root_dir / dir_name)])
16-
17+
1718
# Build the package
1819
print("Building package...")
1920
result = subprocess.run(
20-
[sys.executable, "-m", "build"],
21-
cwd=root_dir,
22-
capture_output=True,
23-
text=True
21+
[sys.executable, "-m", "build"], cwd=root_dir, capture_output=True, text=True
2422
)
25-
23+
2624
if result.returncode != 0:
2725
print(f"Build failed:\n{result.stderr}")
2826
return 1
29-
27+
3028
print("Build successful!")
3129
print("\nBuilt files:")
3230
dist_dir = root_dir / "dist"
3331
for file in dist_dir.iterdir():
3432
print(f" - {file.name}")
35-
33+
3634
return 0
3735

36+
3837
if __name__ == "__main__":
39-
sys.exit(main())
38+
sys.exit(main())

scripts/generate_api_methods.py

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def get_python_type(schema: Dict[str, Any]) -> str:
3535

3636
def create_manual_tools() -> List[Dict[str, Any]]:
3737
"""Create tool definitions based on the specification documentation.
38-
38+
3939
Since the Nutrient API uses a build endpoint with actions rather than
4040
individual tool endpoints, we'll create convenience methods that wrap
4141
the build API.
@@ -90,7 +90,10 @@ def create_manual_tools() -> List[Dict[str, Any]]:
9090
"page_indexes": {
9191
"type": "List[int]",
9292
"required": False,
93-
"description": "List of page indexes to rotate (0-based). If not specified, all pages are rotated.",
93+
"description": (
94+
"List of page indexes to rotate (0-based). "
95+
"If not specified, all pages are rotated."
96+
),
9497
},
9598
},
9699
},
@@ -245,10 +248,7 @@ def generate_method_code(tool_info: Dict[str, Any]) -> str:
245248
if not param_info["required"]:
246249
param_type = param_info["type"]
247250
# Handle List types
248-
if param_type.startswith("List["):
249-
base_type = param_type
250-
else:
251-
base_type = param_type
251+
base_type = param_type
252252

253253
default = param_info.get("default")
254254
if default is None:
@@ -263,7 +263,9 @@ def generate_method_code(tool_info: Dict[str, Any]) -> str:
263263
# Build method signature
264264
if len(param_list) > 3: # Multiple parameters
265265
params_str = ",\n ".join(param_list)
266-
method_signature = f" def {method_name}(\n {params_str},\n ) -> Optional[bytes]:"
266+
method_signature = (
267+
f" def {method_name}(\n {params_str},\n ) -> Optional[bytes]:"
268+
)
267269
else:
268270
params_str = ", ".join(param_list)
269271
method_signature = f" def {method_name}({params_str}) -> Optional[bytes]:"
@@ -274,48 +276,56 @@ def generate_method_code(tool_info: Dict[str, Any]) -> str:
274276
docstring_lines.append("")
275277
docstring_lines.append(f" {description}")
276278

277-
docstring_lines.extend([
278-
"",
279-
" Args:",
280-
" input_file: Input file (path, bytes, or file-like object).",
281-
])
279+
docstring_lines.extend(
280+
[
281+
"",
282+
" Args:",
283+
" input_file: Input file (path, bytes, or file-like object).",
284+
]
285+
)
282286

283287
if param_docs:
284288
docstring_lines.extend(param_docs)
285289

286-
docstring_lines.extend([
287-
" output_path: Optional path to save the output file.",
288-
"",
289-
" Returns:",
290-
" Processed file as bytes, or None if output_path is provided.",
291-
"",
292-
" Raises:",
293-
" AuthenticationError: If API key is missing or invalid.",
294-
" APIError: For other API errors.",
295-
' """',
296-
])
290+
docstring_lines.extend(
291+
[
292+
" output_path: Optional path to save the output file.",
293+
"",
294+
" Returns:",
295+
" Processed file as bytes, or None if output_path is provided.",
296+
"",
297+
" Raises:",
298+
" AuthenticationError: If API key is missing or invalid.",
299+
" APIError: For other API errors.",
300+
' """',
301+
]
302+
)
297303

298304
# Build method body
299305
method_body = []
300306

301307
# Collect kwargs
302-
kwargs_params = [
303-
f"{name}={name}"
304-
for name in parameters.keys()
305-
]
308+
kwargs_params = [f"{name}={name}" for name in parameters]
306309

307310
if kwargs_params:
308311
kwargs_str = ", ".join(kwargs_params)
309-
method_body.append(f' return self._process_file("{tool_name}", input_file, output_path, {kwargs_str})')
312+
method_body.append(
313+
f' return self._process_file("{tool_name}", input_file, '
314+
f"output_path, {kwargs_str})"
315+
)
310316
else:
311-
method_body.append(f' return self._process_file("{tool_name}", input_file, output_path)')
317+
method_body.append(
318+
f' return self._process_file("{tool_name}", input_file, output_path)'
319+
)
312320

313321
# Combine all parts
314-
return "\n".join([
315-
method_signature,
316-
"\n".join(docstring_lines),
317-
"\n".join(method_body),
318-
])
322+
return "\n".join(
323+
[
324+
method_signature,
325+
"\n".join(docstring_lines),
326+
"\n".join(method_body),
327+
]
328+
)
319329

320330

321331
def generate_api_methods(spec_path: Path, output_path: Path) -> None:
@@ -330,23 +340,23 @@ def generate_api_methods(spec_path: Path, output_path: Path) -> None:
330340
# Generate code
331341
code_lines = [
332342
'"""Direct API methods for individual document processing tools.',
333-
'',
334-
'This file provides convenient methods that wrap the Nutrient Build API',
335-
'for common document processing operations.',
343+
"",
344+
"This file provides convenient methods that wrap the Nutrient Build API",
345+
"for common document processing operations.",
336346
'"""',
337-
'',
338-
'from typing import List, Optional',
339-
'',
340-
'from nutrient_dws.file_handler import FileInput',
341-
'',
342-
'',
343-
'class DirectAPIMixin:',
347+
"",
348+
"from typing import List, Optional",
349+
"",
350+
"from nutrient_dws.file_handler import FileInput",
351+
"",
352+
"",
353+
"class DirectAPIMixin:",
344354
' """Mixin class containing Direct API methods.',
345-
' ',
346-
' These methods provide a simplified interface to common document',
347-
' processing operations. They internally use the Build API.',
355+
" ",
356+
" These methods provide a simplified interface to common document",
357+
" processing operations. They internally use the Build API.",
348358
' """',
349-
'',
359+
"",
350360
]
351361

352362
# Add methods

src/nutrient_dws/api/direct.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515

1616
class HasBuildMethod(Protocol):
1717
"""Protocol for objects that have a build method."""
18-
18+
1919
def build(self, input_file: FileInput) -> "BuildAPIWrapper":
2020
"""Build method signature."""
2121
...
22-
22+
2323
@property
2424
def _http_client(self) -> "HTTPClient":
2525
"""HTTP client property."""
@@ -72,9 +72,8 @@ def convert_to_pdf(
7272
HTML files are not currently supported by the API.
7373
"""
7474
# Use builder with no actions - implicit conversion happens
75-
if TYPE_CHECKING:
76-
self = cast(HasBuildMethod, self)
77-
return self.build(input_file).execute(output_path)
75+
# Type checking: at runtime, self is NutrientClient which has these methods
76+
return self.build(input_file).execute(output_path) # type: ignore[attr-defined,no-any-return]
7877

7978
def flatten_annotations(
8079
self, input_file: FileInput, output_path: Optional[str] = None
@@ -281,9 +280,8 @@ def merge_pdfs(
281280
instructions = {"parts": parts, "actions": []}
282281

283282
# Make API request
284-
if TYPE_CHECKING:
285-
self = cast(HasBuildMethod, self)
286-
result = self._http_client.post(
283+
# Type checking: at runtime, self is NutrientClient which has _http_client
284+
result = self._http_client.post( # type: ignore[attr-defined]
287285
"/build",
288286
files=files,
289287
json_data=instructions,
@@ -294,4 +292,4 @@ def merge_pdfs(
294292
save_file_output(result, output_path)
295293
return None
296294
else:
297-
return result
295+
return result # type: ignore[no-any-return]

src/nutrient_dws/builder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77

88
class BuildAPIWrapper:
9-
"""Builder pattern implementation for chaining document operations.
10-
9+
r"""Builder pattern implementation for chaining document operations.
10+
1111
This class provides a fluent interface for building complex document
1212
processing workflows using the Nutrient Build API.
13-
13+
1414
Example:
1515
>>> client.build(input_file="document.pdf") \\
1616
... .add_step(tool="rotate-pages", options={"degrees": 90}) \\

src/nutrient_dws/client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@
1010

1111

1212
class NutrientClient(DirectAPIMixin):
13-
"""Main client for interacting with Nutrient DWS API.
14-
13+
r"""Main client for interacting with Nutrient DWS API.
14+
1515
This client provides two ways to interact with the API:
16-
16+
1717
1. Direct API: Individual method calls for single operations
1818
Example: client.convert_to_pdf(input_file="document.docx")
19-
19+
2020
2. Builder API: Fluent interface for chaining multiple operations
2121
Example: client.build(input_file="doc.docx").add_step("convert-to-pdf").execute()
2222
2323
Args:
2424
api_key: API key for authentication. If not provided, will look for
2525
NUTRIENT_API_KEY environment variable.
2626
timeout: Request timeout in seconds. Defaults to 300.
27-
27+
2828
Raises:
2929
AuthenticationError: When making API calls without a valid API key.
30-
30+
3131
Example:
3232
>>> from nutrient_dws import NutrientClient
3333
>>> client = NutrientClient(api_key="your-api-key")

src/nutrient_dws/file_handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ def prepare_file_for_upload(
8080
# For large files, return file handle instead of reading into memory
8181
file_size = path.stat().st_size
8282
if file_size > 10 * 1024 * 1024: # 10MB threshold
83-
file_handle = open(path, "rb")
83+
# Note: File handle is intentionally not using context manager
84+
# as it needs to remain open for streaming upload by HTTP client
85+
file_handle = open(path, "rb") # noqa: SIM115
8486
return field_name, (path.name, file_handle, content_type)
8587
else:
8688
return field_name, (path.name, path.read_bytes(), content_type)

tests/integration/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Configuration for integration tests."""
22

3-
import os
43

54
import pytest
65

tests/integration/test_api_integration.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
import os
88
from pathlib import Path
9-
from typing import Generator
109

1110
import pytest
1211

1312
from nutrient_dws import NutrientClient
1413
from nutrient_dws.exceptions import AuthenticationError
1514

16-
1715
# Skip integration tests if no API key is provided
1816
pytestmark = pytest.mark.skipif(
1917
not os.environ.get("NUTRIENT_API_KEY"), reason="NUTRIENT_API_KEY environment variable not set"
@@ -39,7 +37,9 @@ def sample_pdf(tmp_path: Path) -> Path:
3937
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
4038
endobj
4139
3 0 obj
42-
<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >> /MediaBox [0 0 612 792] /Contents 4 0 R >>
40+
<< /Type /Page /Parent 2 0 R /Resources
41+
<< /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>
42+
/MediaBox [0 0 612 792] /Contents 4 0 R >>
4343
endobj
4444
4 0 obj
4545
<< /Length 44 >>
@@ -53,11 +53,11 @@ def sample_pdf(tmp_path: Path) -> Path:
5353
endobj
5454
xref
5555
0 5
56-
0000000000 65535 f
57-
0000000009 00000 n
58-
0000000058 00000 n
59-
0000000115 00000 n
60-
0000000323 00000 n
56+
0000000000 65535 f
57+
0000000009 00000 n
58+
0000000058 00000 n
59+
0000000115 00000 n
60+
0000000323 00000 n
6161
trailer
6262
<< /Size 5 /Root 1 0 R >>
6363
startxref
@@ -81,17 +81,21 @@ def sample_docx(tmp_path: Path) -> Path:
8181
"[Content_Types].xml",
8282
"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
8383
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
84-
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
84+
<Default Extension="rels"
85+
ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
8586
<Default Extension="xml" ContentType="application/xml"/>
86-
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
87+
<Override PartName="/word/document.xml"
88+
ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
8789
</Types>""",
8890
)
8991

9092
docx.writestr(
9193
"_rels/.rels",
9294
"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9395
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
94-
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
96+
<Relationship Id="rId1"
97+
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
98+
Target="word/document.xml"/>
9599
</Relationships>""",
96100
)
97101

@@ -292,7 +296,9 @@ def test_invalid_file(self, client: NutrientClient, tmp_path: Path) -> None:
292296
invalid_file = tmp_path / "invalid.txt"
293297
invalid_file.write_text("This is not a PDF")
294298

295-
with pytest.raises(Exception): # API should return an error
299+
from nutrient_dws.exceptions import APIError
300+
301+
with pytest.raises(APIError): # API should return an error
296302
client.rotate_pages(input_file=invalid_file, degrees=90)
297303

298304
def test_missing_file(self, client: NutrientClient) -> None:

0 commit comments

Comments
 (0)