11"""Builder API implementation for multi-step workflows."""
22
3- from typing import Any , Dict , Optional
3+ import json
4+ from typing import Any , Dict , List , Optional , Union
5+
6+ from nutrient .file_handler import FileInput , prepare_file_for_upload , save_file_output
47
58
69class BuildAPIWrapper :
7- """Builder pattern implementation for chaining document operations."""
10+ """Builder pattern implementation for chaining document operations.
11+
12+ This class provides a fluent interface for building complex document
13+ processing workflows using the Nutrient Build API.
14+
15+ Example:
16+ >>> client.build(input_file="document.pdf") \\
17+ ... .add_step(tool="rotate-pages", options={"degrees": 90}) \\
18+ ... .add_step(tool="ocr-pdf", options={"language": "en"}) \\
19+ ... .add_step(tool="watermark-pdf", options={"text": "CONFIDENTIAL"}) \\
20+ ... .execute(output_path="processed.pdf")
21+ """
822
9- def __init__ (self , client , input_file ) -> None :
10- """Initialize builder with client and input file."""
23+ def __init__ (self , client , input_file : FileInput ) -> None :
24+ """Initialize builder with client and input file.
25+
26+ Args:
27+ client: NutrientClient instance.
28+ input_file: Input file to process.
29+ """
1130 self ._client = client
1231 self ._input_file = input_file
13- self ._steps : list [Dict [str , Any ]] = []
32+ self ._parts : List [Dict [str , Any ]] = []
33+ self ._actions : List [Dict [str , Any ]] = []
34+ self ._output_options : Dict [str , Any ] = {}
1435
1536 def add_step (self , tool : str , options : Optional [Dict [str , Any ]] = None ) -> "BuildAPIWrapper" :
1637 """Add a processing step to the workflow.
1738
1839 Args:
19- tool: Tool identifier from the API .
40+ tool: Tool identifier (e.g., 'rotate-pages', 'ocr-pdf') .
2041 options: Optional parameters for the tool.
2142
2243 Returns:
2344 Self for method chaining.
45+
46+ Example:
47+ >>> builder.add_step(tool="rotate-pages", options={"degrees": 180})
2448 """
25- raise NotImplementedError ("Builder API not yet implemented" )
49+ action = self ._map_tool_to_action (tool , options or {})
50+ self ._actions .append (action )
51+ return self
52+
53+ def set_output_options (self , ** options : Any ) -> "BuildAPIWrapper" :
54+ """Set output options for the final document.
55+
56+ Args:
57+ **options: Output options (e.g., metadata, optimization).
58+
59+ Returns:
60+ Self for method chaining.
61+
62+ Example:
63+ >>> builder.set_output_options(
64+ ... metadata={"title": "My Document", "author": "John Doe"},
65+ ... optimize=True
66+ ... )
67+ """
68+ self ._output_options .update (options )
69+ return self
2670
2771 def execute (self , output_path : Optional [str ] = None ) -> Optional [bytes ]:
2872 """Execute the workflow.
@@ -32,5 +76,115 @@ def execute(self, output_path: Optional[str] = None) -> Optional[bytes]:
3276
3377 Returns:
3478 Processed file bytes, or None if output_path is provided.
79+
80+ Raises:
81+ AuthenticationError: If API key is missing or invalid.
82+ APIError: For other API errors.
3583 """
36- raise NotImplementedError ("Builder API not yet implemented" )
84+ # Prepare the build instructions
85+ instructions = self ._build_instructions ()
86+
87+ # Prepare file for upload
88+ file_field , file_data = prepare_file_for_upload (self ._input_file )
89+ files = {file_field : file_data }
90+
91+ # Make API request
92+ result = self ._client ._http_client .post (
93+ "/build" ,
94+ files = files ,
95+ json_data = instructions ,
96+ )
97+
98+ # Handle output
99+ if output_path :
100+ save_file_output (result , output_path )
101+ return None
102+ else :
103+ return result
104+
105+ def _build_instructions (self ) -> Dict [str , Any ]:
106+ """Build the instructions payload for the API.
107+
108+ Returns:
109+ Instructions dictionary for the Build API.
110+ """
111+ # Add the input file as the first part
112+ instructions = {
113+ "parts" : [
114+ {"file" : "file" } # Reference to the uploaded file
115+ ],
116+ "actions" : self ._actions ,
117+ }
118+
119+ # Add output options if specified
120+ if self ._output_options :
121+ instructions ["output" ] = self ._output_options
122+
123+ return instructions
124+
125+ def _map_tool_to_action (self , tool : str , options : Dict [str , Any ]) -> Dict [str , Any ]:
126+ """Map tool name and options to Build API action format.
127+
128+ Args:
129+ tool: Tool identifier.
130+ options: Tool options.
131+
132+ Returns:
133+ Action dictionary for the Build API.
134+ """
135+ # Map tool names to action types
136+ tool_mapping = {
137+ "rotate-pages" : "rotate" ,
138+ "ocr-pdf" : "ocr" ,
139+ "watermark-pdf" : "watermark" ,
140+ "flatten-annotations" : "flatten" ,
141+ "apply-instant-json" : "applyInstantJson" ,
142+ "apply-xfdf" : "applyXfdf" ,
143+ "create-redactions" : "createRedactions" ,
144+ "apply-redactions" : "applyRedactions" ,
145+ }
146+
147+ action_type = tool_mapping .get (tool , tool )
148+
149+ # Build action dictionary
150+ action = {"type" : action_type }
151+
152+ # Handle special cases for different action types
153+ if action_type == "rotate" :
154+ action ["rotateBy" ] = options .get ("degrees" , 0 )
155+ if "page_indexes" in options :
156+ action ["pageIndexes" ] = options ["page_indexes" ]
157+
158+ elif action_type == "ocr" :
159+ if "language" in options :
160+ action ["language" ] = options ["language" ]
161+
162+ elif action_type == "watermark" :
163+ if "text" in options :
164+ action ["text" ] = options ["text" ]
165+ if "image_url" in options :
166+ action ["image" ] = {"url" : options ["image_url" ]}
167+ if "opacity" in options :
168+ action ["opacity" ] = options ["opacity" ]
169+ if "position" in options :
170+ action ["position" ] = options ["position" ]
171+
172+ else :
173+ # For other actions, pass options directly
174+ action .update (options )
175+
176+ return action
177+
178+ def __str__ (self ) -> str :
179+ """String representation of the build workflow."""
180+ steps = [f"{ action ['type' ]} " for action in self ._actions ]
181+ return f"BuildAPIWrapper(steps={ steps } )"
182+
183+ def __repr__ (self ) -> str :
184+ """Detailed representation of the build workflow."""
185+ return (
186+ f"BuildAPIWrapper("
187+ f"input_file={ self ._input_file !r} , "
188+ f"actions={ self ._actions !r} , "
189+ f"output_options={ self ._output_options !r} )"
190+ )
0 commit comments