czds

- ICANN Centralized Zone Data Service Tool
git clone git://git.acid.vegas/czds.git
Log | Files | Refs | Archive | README | LICENSE

commit edd1e130e67426fc2be66b13c72041a81b57df82
parent 2690f4e50b362eeb246612c53f73417fc55e5180
Author: acidvegas <acid.vegas@acid.vegas>
Date: Fri, 21 Mar 2025 01:33:19 -0400

Added verbosity

Diffstat:
Mczds/__init__.py | 2+-
Mczds/__main__.py | 1+
Mczds/client.py | 64++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msetup.py | 2+-

4 files changed, 51 insertions(+), 18 deletions(-)

diff --git a/czds/__init__.py b/czds/__init__.py
@@ -5,7 +5,7 @@
 from .client import CZDS
 
 
-__version__ = '1.2.1'
+__version__ = '1.2.2'
 __author__  = 'acidvegas'
 __email__   = 'acid.vegas@acid.vegas'
 __github__  = 'https://github.com/acidvegas/czds'
 \ No newline at end of file
diff --git a/czds/__main__.py b/czds/__main__.py
@@ -78,5 +78,6 @@ def cli_entry():
     return asyncio.run(main())
 
 
+
 if __name__ == '__main__':
     asyncio.run(main()) 
 \ No newline at end of file
diff --git a/czds/client.py b/czds/client.py
@@ -3,8 +3,10 @@
 # czds/client.py
 
 import asyncio
-import os
 import gzip
+import logging
+import os
+
 
 try:
     import aiohttp
@@ -17,6 +19,13 @@ except ImportError:
     raise ImportError('missing aiofiles library (pip install aiofiles)')
 
 
+# Configure logging
+logging.basicConfig(
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+    level=logging.INFO
+)
+
+
 class CZDS:
     '''Class for the ICANN Centralized Zones Data Service'''
 
@@ -32,6 +41,7 @@ class CZDS:
         self.password = password
         self.headers  = None # Store the authorization header for reuse
         self.session  = None # Store the client session for reuse
+        logging.info('Initialized CZDS client')
 
 
     async def __aenter__(self):
@@ -39,7 +49,7 @@ class CZDS:
 
         self.session = aiohttp.ClientSession()
         self.headers = {'Authorization': f'Bearer {await self.authenticate()}'}
-
+        logging.debug('Entered async context')
         return self
 
 
@@ -48,6 +58,7 @@ class CZDS:
 
         if self.session:
             await self.session.close()
+            logging.debug('Closed aiohttp session')
 
 
     async def authenticate(self) -> str:
@@ -55,27 +66,36 @@ class CZDS:
 
         try:
             data = {'username': self.username, 'password': self.password}
+            logging.info('Authenticating with ICANN API')
 
             async with self.session.post('https://account-api.icann.org/api/authenticate', json=data) as response:
                 if response.status != 200:
-                    raise Exception(f'Authentication failed: {response.status} {await response.text()}')
+                    error_msg = f'Authentication failed: {response.status} {await response.text()}'
+                    logging.error(error_msg)
+                    raise Exception(error_msg)
 
                 result = await response.json()
-
+                logging.info('Successfully authenticated with ICANN API')
                 return result['accessToken']
 
         except Exception as e:
-            raise Exception(f'Failed to authenticate with ICANN API: {e}')
+            error_msg = f'Failed to authenticate with ICANN API: {e}'
+            logging.error(error_msg)
+            raise Exception(error_msg)
 
 
     async def fetch_zone_links(self) -> list:
         '''Fetch the list of zone files available for download'''
-
+        logging.info('Fetching zone links')
         async with self.session.get('https://czds-api.icann.org/czds/downloads/links', headers=self.headers) as response:
             if response.status != 200:
-                raise Exception(f'Failed to fetch zone links: {response.status} {await response.text()}')
+                error_msg = f'Failed to fetch zone links: {response.status} {await response.text()}'
+                logging.error(error_msg)
+                raise Exception(error_msg)
 
-            return await response.json()
+            links = await response.json()
+            logging.info(f'Successfully fetched {len(links)} zone links')
+            return links
 
 
     async def get_report(self, filepath: str = None, scrub: bool = True, format: str = 'csv') -> str | dict:
@@ -87,20 +107,24 @@ class CZDS:
         :param format: Output format ('csv' or 'json')
         :return: Report content as CSV string or JSON dict
         '''
-
+        logging.info('Downloading zone stats report')
         async with self.session.get('https://czds-api.icann.org/czds/requests/report', headers=self.headers) as response:
             if response.status != 200:
-                raise Exception(f'Failed to download the zone stats report: {response.status} {await response.text()}')
+                error_msg = f'Failed to download the zone stats report: {response.status} {await response.text()}'
+                logging.error(error_msg)
+                raise Exception(error_msg)
 
             content = await response.text()
 
             if scrub:
                 content = content.replace(self.username, 'nobody@no.name')
+                logging.debug('Scrubbed username from report')
 
             if format.lower() == 'json':
                 rows    = [row.split(',') for row in content.strip().split('\n')]
                 header  = rows[0]
                 content = [dict(zip(header, row)) for row in rows[1:]]
+                logging.debug('Converted report to JSON format')
 
             if filepath:
                 async with aiofiles.open(filepath, 'w') as file:
@@ -109,9 +133,10 @@ class CZDS:
                         await file.write(json.dumps(content, indent=4))
                     else:
                         await file.write(content)
+                    logging.info(f'Saved report to {filepath}')
 
             return content
-        
+
 
     async def gzip_decompress(self, filepath: str, cleanup: bool = True):
         '''
@@ -120,7 +145,7 @@ class CZDS:
         :param filepath: Path to the gzip file
         :param cleanup: Whether to remove the original gzip file after decompression
         '''
-
+        logging.debug(f'Decompressing {filepath}')
         output_path = filepath[:-3] # Remove .gz extension
         
         async with aiofiles.open(filepath, 'rb') as f_in:
@@ -131,6 +156,7 @@ class CZDS:
         
         if cleanup:
             os.remove(filepath)
+            logging.debug(f'Removed original gzip file: {filepath}')
 
 
     async def download_zone(self, url: str, output_directory: str, decompress: bool = False, cleanup: bool = True, semaphore: asyncio.Semaphore = None):
@@ -143,14 +169,18 @@ class CZDS:
         :param cleanup: Whether to remove the original gzip file after decompression
         :param semaphore: Optional semaphore for controlling concurrency
         '''
-
         async def _download():
+            logging.debug(f'Downloading zone file from {url}')
             async with self.session.get(url, headers=self.headers) as response:
                 if response.status != 200:
-                    raise Exception(f'Failed to download {url}: {response.status} {await response.text()}')
+                    error_msg = f'Failed to download {url}: {response.status} {await response.text()}'
+                    logging.error(error_msg)
+                    raise Exception(error_msg)
 
                 if not (content_disposition := response.headers.get('Content-Disposition')):
-                    raise ValueError('Missing Content-Disposition header')
+                    error_msg = 'Missing Content-Disposition header'
+                    logging.error(error_msg)
+                    raise ValueError(error_msg)
 
                 filename = content_disposition.split('filename=')[-1].strip('"')
                 filepath = os.path.join(output_directory, filename)
@@ -161,6 +191,7 @@ class CZDS:
                         if not chunk:
                             break
                         await file.write(chunk)
+                    logging.info(f'Successfully downloaded {filename}')
 
                 if decompress:
                     await self.gzip_decompress(filepath, cleanup)
@@ -184,11 +215,12 @@ class CZDS:
         :param decompress: Whether to decompress the gzip files after download
         :param cleanup: Whether to remove the original gzip files after decompression
         '''
-
         os.makedirs(output_directory, exist_ok=True)
+        logging.info(f'Starting concurrent download of zones with concurrency={concurrency}')
 
         semaphore  = asyncio.Semaphore(concurrency)
         zone_links = await self.fetch_zone_links()
         tasks      = [self.download_zone(url, output_directory, decompress, cleanup, semaphore) for url in zone_links]
 
         await asyncio.gather(*tasks)
+        logging.info('Completed downloading all zone files')
diff --git a/setup.py b/setup.py
@@ -11,7 +11,7 @@ with open('README.md', 'r', encoding='utf-8') as fh:
 
 setup(
 	name='czds-api',
-	version='1.2.1',
+	version='1.2.2',
 	author='acidvegas',
 	author_email='acid.vegas@acid.vegas',
 	description='ICANN API for the Centralized Zones Data Service',