2020
2121
2222class OpenCageBatchGeocoder ():
23+ """Batch geocoder that processes CSV files using the OpenCage API.
2324
24- """ Called from command_line.py
25- init() receives the parsed command line parameters
26- geocode() receive an input and output CSV reader/writer and loops over the data
25+ Reads rows from a CSV input, geocodes each address using async workers,
26+ and writes results to a CSV output.
27+
28+ Args:
29+ options: Parsed command-line options from argparse.
2730 """
2831
2932 def __init__ (self , options ):
@@ -33,9 +36,16 @@ def __init__(self, options):
3336 self .write_counter = 1
3437
3538 def __call__ (self , * args , ** kwargs ):
39+ """Run the batch geocoder synchronously via asyncio.run."""
3640 asyncio .run (self .geocode (* args , ** kwargs ))
3741
3842 async def geocode (self , csv_input , csv_output ):
43+ """Process a CSV input, geocode each row, and write results.
44+
45+ Args:
46+ csv_input: CSV reader for input rows.
47+ csv_output: CSV writer for output rows.
48+ """
3949 if not self .options .dry_run :
4050 test = await self .test_request ()
4151 if test ['error' ]:
@@ -81,6 +91,12 @@ async def geocode(self, csv_input, csv_output):
8191 progress_bar .close ()
8292
8393 async def test_request (self ):
94+ """Send a test geocoding request to verify the API key.
95+
96+ Returns:
97+ Dict with 'error' (None or exception) and 'free' (bool indicating
98+ whether a free trial account is being used).
99+ """
84100 try :
85101 async with OpenCageGeocode (
86102 self .options .api_key ,
@@ -99,6 +115,15 @@ async def test_request(self):
99115 return {'error' : exc }
100116
101117 async def read_input (self , csv_input , queue ):
118+ """Read all rows from CSV input and add them to the work queue.
119+
120+ Args:
121+ csv_input: CSV reader for input rows.
122+ queue: Async queue to populate with parsed input items.
123+
124+ Returns:
125+ True if any warnings were encountered while reading, False otherwise.
126+ """
102127 any_warnings = False
103128 for index , row in enumerate (csv_input ):
104129 line_number = index + 1
@@ -119,6 +144,16 @@ async def read_input(self, csv_input, queue):
119144 return any_warnings
120145
121146 async def read_one_line (self , row , row_id ):
147+ """Parse a single CSV row into a work item for geocoding.
148+
149+ Args:
150+ row: List of column values from the CSV reader.
151+ row_id: 1-based line number of the row in the input.
152+
153+ Returns:
154+ Dict with keys 'row_id', 'address', 'original_columns',
155+ and 'warnings'.
156+ """
122157 warnings = False
123158
124159 if self .options .input_columns :
@@ -159,6 +194,13 @@ async def read_one_line(self, row, row_id):
159194 return {'row_id' : row_id , 'address' : ',' .join (address ), 'original_columns' : row , 'warnings' : warnings }
160195
161196 async def worker (self , csv_output , queue , progress ):
197+ """Consume items from the queue and geocode each one.
198+
199+ Args:
200+ csv_output: CSV writer for output rows.
201+ queue: Async queue of work items to process.
202+ progress: tqdm progress bar, or False if disabled.
203+ """
162204 while True :
163205 item = await queue .get ()
164206
@@ -173,6 +215,14 @@ async def worker(self, csv_output, queue, progress):
173215 queue .task_done ()
174216
175217 async def geocode_one_address (self , csv_output , row_id , address , original_columns ):
218+ """Geocode a single address and write the result to the output.
219+
220+ Args:
221+ csv_output: CSV writer for output rows.
222+ row_id: 1-based line number of the row in the input.
223+ address: Address string (or lat,lng for reverse geocoding).
224+ original_columns: Original CSV row columns to preserve in output.
225+ """
176226 def on_backoff (details ):
177227 if not self .options .quiet :
178228 sys .stderr .write ("Backing off {wait:0.1f} seconds afters {tries} tries "
@@ -242,6 +292,18 @@ async def write_one_geocoding_result(
242292 geocoding_result ,
243293 raw_response ,
244294 original_columns ):
295+ """Write a single geocoding result row to the CSV output.
296+
297+ Appends the requested output columns to the original CSV columns.
298+ Rows are written in order unless the --unordered option is set.
299+
300+ Args:
301+ csv_output: CSV writer for output rows.
302+ row_id: 1-based line number of the row in the input.
303+ geocoding_result: First result dict from the API, or None.
304+ raw_response: Full API response dict.
305+ original_columns: Original CSV row columns to preserve in output.
306+ """
245307 row = original_columns
246308
247309 for column in self .options .add_columns :
@@ -280,10 +342,32 @@ async def write_one_geocoding_result(
280342 self .write_counter = self .write_counter + 1
281343
282344 def log (self , message ):
345+ """Write a message to stderr unless quiet mode is enabled.
346+
347+ Args:
348+ message: Message string to display.
349+ """
283350 if not self .options .quiet :
284351 sys .stderr .write (f"{ message } \n " )
285352
286353 def deep_get_result_value (self , data , keys , default = None ):
354+ """Retrieve a nested value from a dict using a list of keys.
355+
356+ Args:
357+ data: Dict to traverse.
358+ keys: List of keys to follow in sequence.
359+ default: Value to return if any key is missing.
360+
361+ Returns:
362+ The nested value, or default if the path doesn't exist.
363+
364+ Example:
365+ >>> data = {'status': {'code': 200, 'message': 'OK'}}
366+ >>> self.deep_get_result_value(data, ['status', 'message'])
367+ 'OK'
368+ >>> self.deep_get_result_value(data, ['missing', 'key'], '')
369+ ''
370+ """
287371 for key in keys :
288372 if isinstance (data , dict ):
289373 data = data .get (key , default )
0 commit comments