diff --git a/ci/environment-3.10.yml b/ci/environment-3.10.yml index ad5f259..1a5c3d1 100644 --- a/ci/environment-3.10.yml +++ b/ci/environment-3.10.yml @@ -15,7 +15,6 @@ dependencies: - pre-commit - pytest - xarray - - ipython - pip - pip: - codecov diff --git a/ci/environment-3.11.yml b/ci/environment-3.11.yml index 32a5fe3..a1aecac 100644 --- a/ci/environment-3.11.yml +++ b/ci/environment-3.11.yml @@ -15,7 +15,6 @@ dependencies: - pre-commit - pytest - xarray - - ipython - pip - pip: - codecov diff --git a/ci/environment-3.12.yml b/ci/environment-3.12.yml index 41e953a..e321778 100644 --- a/ci/environment-3.12.yml +++ b/ci/environment-3.12.yml @@ -15,7 +15,6 @@ dependencies: - pre-commit - pytest - xarray - - ipython - pip - pip: - codecov diff --git a/ci/environment-3.13.yml b/ci/environment-3.13.yml index 2efb442..00586d9 100644 --- a/ci/environment-3.13.yml +++ b/ci/environment-3.13.yml @@ -15,7 +15,6 @@ dependencies: - pre-commit - pytest - xarray - - ipython - pip - pip: - codecov diff --git a/pyproject.toml b/pyproject.toml index 25cd2b1..0b83851 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "jsonschema", "pooch", "xarray", - "httpx>=0.28" + "access-ipy-telemetry>=0.1.0", ] dynamic = ["version"] diff --git a/src/access_nri_intake/data/__init__.py b/src/access_nri_intake/data/__init__.py index dfefe50..dcaf33d 100644 --- a/src/access_nri_intake/data/__init__.py +++ b/src/access_nri_intake/data/__init__.py @@ -5,35 +5,25 @@ import intake import intake.catalog -from IPython import get_ipython -from IPython.core.magic import register_line_magic +from access_ipy_telemetry.cli import configure_telemetry +from access_ipy_telemetry.utils import ApiHandler from access_nri_intake.utils import get_catalog_fp -from .telemetry import capture_datastore_searches +api_handler = ApiHandler() CATALOG_NAME_FORMAT = ( r"^v(?P2[0-9]{3})\-(?P1[0-2]|0[1-9])\-(?P0[1-9]|[1-2][0-9]|3[0-1])$" ) -def load_ipython_extension(ipython): - @register_line_magic("capture_func_calls") - def capture_func_calls(info): - """ - Returns the function calls from the code in the cell - """ - ipython.events.register("pre_run_cell", capture_datastore_searches) - - -# Register the extension -ip = get_ipython() -if ip: - load_ipython_extension(ip) - ip.run_line_magic("capture_func_calls", "") - try: data = intake.open_catalog(get_catalog_fp()).access_nri + cat_version = data._captured_init_kwargs.get("metadata", {}).get( + "version", "latest" + ) # Get the catalog version number and set it to "latest" if it can't be found + configure_telemetry(["--enable", "--silent"]) + api_handler.add_extra_field("catalog", {"catalog_version": cat_version}) except FileNotFoundError: warnings.warn( "Unable to access a default catalog location. Calling intake.cat.access_nri will not work.", diff --git a/src/access_nri_intake/data/telemetry.py b/src/access_nri_intake/data/telemetry.py deleted file mode 100644 index ef4bfa6..0000000 --- a/src/access_nri_intake/data/telemetry.py +++ /dev/null @@ -1,92 +0,0 @@ -import ast -import asyncio -import warnings - -import httpx -from IPython import get_ipython - -TELEMETRY_SERVER_URL = "https://intake-telemetry-bb870061f91a.herokuapp.com" - -TELEMETRY_REGISTRED_FUNCTIONS = [ - "esm_datastore.search", - "DfFileCatalog.search", -] - - -def send_api_request(function_name, kwargs): - telemetry_data = { - "name": f"CT_testing_{function_name}_ipy_extensions", - "search": kwargs, - } - - endpoint = f"{TELEMETRY_SERVER_URL}/telemetry/update" - - async def send_telemetry(data): - headers = {"Content-Type": "application/json"} - async with httpx.AsyncClient() as client: - try: - response = await client.post(endpoint, json=data, headers=headers) - response.raise_for_status() - - print(f"Telemetry data sent: {response.json()}") - except httpx.RequestError as e: - warnings.warn( - f"Request failed: {e}", category=RuntimeWarning, stacklevel=2 - ) - - # Check if there's an existing event loop, otherwise create a new one - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - if loop.is_running(): - loop.create_task(send_telemetry(telemetry_data)) - else: - loop.run_until_complete(send_telemetry(telemetry_data)) - return None - - -def capture_datastore_searches(info): - """ - Use the AST module to parse the code that we are executing & send an API call - if we - - """ - code = info.raw_cell - - # Remove lines that contain IPython magic commands - code = "\n".join( - line for line in code.splitlines() if not line.strip().startswith("%") - ) - - tree = ast.parse(code) - user_namespace = get_ipython().user_ns - - for node in ast.walk(tree): - if isinstance(node, ast.Call): - if isinstance(node.func, ast.Name): - func_name = node.func.id - elif isinstance(node.func, ast.Attribute): - # Check if the attribute is a method call on a class instance or a module function - if isinstance(node.func.value, ast.Name): - instance_name = node.func.value.id - method_name = node.func.attr - try: - # Evaluate the instance to get its class name - instance = eval(instance_name, globals(), user_namespace) - class_name = instance.__class__.__name__ - func_name = f"{class_name}.{method_name}" - except Exception as e: - print(f"Error evaluating instance: {e}") - continue - - if func_name in TELEMETRY_REGISTRED_FUNCTIONS: - # args = [ast.dump(arg) for arg in node.args] - kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in node.keywords} - send_api_request( - func_name, - # args, - kwargs, - )