diff --git a/.env.example b/.env.example index 265e5ca..6458a90 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ ADO_PAT=(PAT from Azure DevOps) ADO_URL=(https://dev.azure.com/ORG) +APPLICATIONINSIGHTS_CONNECTION_STRING=(Connection string from Azure Application Insights, or blank if not using) ASANA_TOKEN=(PAT from Asana) ASANA_WORKSPACE_NAME=(Name of the Asana Workspace) +OTEL_SERVICE_NAME=sync diff --git a/.vscode/launch.json b/.vscode/launch.json index 7012894..98997b4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,8 @@ "justMyCode": true, "envFile": "${workspaceFolder}/.env", "env": { - "LOG_LEVEL": "DEBUG", + "LOG_LEVEL": "INFO" } } ] -} \ No newline at end of file +} diff --git a/ado_asana_sync/sync/app.py b/ado_asana_sync/sync/app.py index 19f3626..1e67657 100644 --- a/ado_asana_sync/sync/app.py +++ b/ado_asana_sync/sync/app.py @@ -8,9 +8,11 @@ import logging import os +from typing import Optional import asana # type: ignore from azure.devops.connection import Connection # type: ignore +from azure.monitor.opentelemetry import configure_azure_monitor from msrest.authentication import BasicAuthentication from tinydb import TinyDB @@ -57,13 +59,22 @@ def __init__( ado_url: str = "", asana_token: str = "", asana_workspace_name: str = "", + applicationinsights_connection_string: Optional[str] = None, ) -> None: - self.ado_pat = ado_pat or os.environ.get("ADO_PAT") - self.ado_url = ado_url or os.environ.get("ADO_URL") - self.asana_token = asana_token or os.environ.get("ASANA_TOKEN") + self.ado_pat = ado_pat or os.environ.get("ADO_PAT", "") + self.ado_url = ado_url or os.environ.get("ADO_URL", "") + self.asana_token = asana_token or os.environ.get("ASANA_TOKEN", "") self.asana_workspace_name = asana_workspace_name or os.environ.get( - "ASANA_WORKSPACE_NAME" + "ASANA_WORKSPACE_NAME", "" ) + self.applicationinsights_connection_string = ( + applicationinsights_connection_string + or os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING", None) + ) + if not self.applicationinsights_connection_string: + os.environ["OTEL_LOGS_EXPORTER"] = "None" + os.environ["OTEL_METRICS_EXPORTER"] = "None" + os.environ["OTEL_TRACES_EXPORTER"] = "None" self.ado_core_client = None self.ado_wit_client = None self.ado_work_client = None @@ -97,19 +108,23 @@ def connect(self) -> None: """ Connects to ADO and Asana, and sets up the TinyDB database. """ - # connect ADO + # Connect ADO. _LOGGER.debug("Connecting to Azure DevOps") ado_credentials = BasicAuthentication("", self.ado_pat) ado_connection = Connection(base_url=self.ado_url, creds=ado_credentials) self.ado_core_client = ado_connection.clients.get_core_client() self.ado_work_client = ado_connection.clients.get_work_client() self.ado_wit_client = ado_connection.clients.get_work_item_tracking_client() - # connect Asana + # Connect Asana. _LOGGER.debug("Connecting to Asana") asana_config = asana.Configuration() asana_config.access_token = self.asana_token self.asana_client = asana.ApiClient(asana_config) - # setup tinydb + # Configure application insights. + configure_azure_monitor( + connection_string=self.applicationinsights_connection_string, + ) + # Setup tinydb. _LOGGER.debug("Opening local database") self.db = TinyDB( os.path.join(os.path.dirname(__package__), "data", "appdata.json") diff --git a/ado_asana_sync/sync/asana.py b/ado_asana_sync/sync/asana.py index 0b47c05..eee9e84 100644 --- a/ado_asana_sync/sync/asana.py +++ b/ado_asana_sync/sync/asana.py @@ -3,16 +3,15 @@ from __future__ import annotations -import logging - import asana # type: ignore from asana.rest import ApiException # type: ignore -from .app import App +from ado_asana_sync.utils.logging_tracing import setup_logging_and_tracing +from .app import App -# _LOGGER is the logging instance for this file. -_LOGGER = logging.getLogger(__name__) +# This module uses the logger and tracer instances _LOGGER and _TRACER for logging and tracing, respectively. +_LOGGER, _TRACER = setup_logging_and_tracing(__name__) def get_asana_task(app: App, task_gid: str) -> object | None: @@ -26,32 +25,39 @@ def get_asana_task(app: App, task_gid: str) -> object | None: :return: Task object or None if no task is found. :rtype: object or None """ - api_instance = asana.TasksApi(app.asana_client) - try: - # Get all tasks in the project. - opt_fields = [ - "assignee_section", - "due_at", - "name", - "completed_at", - "tags", - "dependents", - "projects", - "completed", - "permalink_url", - "parent", - "assignee", - "assignee_status", - "num_subtasks", - "modified_at", - "workspace", - "due_on", - ] - api_response = api_instance.get_task( - task_gid, - opt_fields=opt_fields, + with _TRACER.start_as_current_span("get_asana_task") as span: + span.set_attributes( + { + "asana_workspace_name": app.asana_workspace_name, + "task_gid": task_gid, + } ) - return api_response.data - except ApiException as exception: - _LOGGER.error("Exception when calling TasksApi->get_task: %s\n", exception) - return None + api_instance = asana.TasksApi(app.asana_client) + try: + # Get all tasks in the project. + opt_fields = [ + "assignee_section", + "due_at", + "name", + "completed_at", + "tags", + "dependents", + "projects", + "completed", + "permalink_url", + "parent", + "assignee", + "assignee_status", + "num_subtasks", + "modified_at", + "workspace", + "due_on", + ] + api_response = api_instance.get_task( + task_gid, + opt_fields=opt_fields, + ) + return api_response.data + except ApiException as exception: + _LOGGER.error("Exception when calling TasksApi->get_task: %s\n", exception) + return None diff --git a/ado_asana_sync/sync/sync.py b/ado_asana_sync/sync/sync.py index ef9d4cf..0dec53d 100644 --- a/ado_asana_sync/sync/sync.py +++ b/ado_asana_sync/sync/sync.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -import logging import os from dataclasses import dataclass from datetime import datetime, timezone @@ -14,14 +13,15 @@ from azure.devops.v7_0.work_item_tracking.models import WorkItem # type: ignore from ado_asana_sync.utils.date import iso8601_utc +from ado_asana_sync.utils.logging_tracing import setup_logging_and_tracing from ado_asana_sync.utils.utils import safe_get from .app import App from .asana import get_asana_task from .task_item import TaskItem -# _LOGGER is the logging instance for this file. -_LOGGER = logging.getLogger(__name__) +# This module uses the logger and tracer instances _LOGGER and _TRACER for logging and tracing, respectively. +_LOGGER, _TRACER = setup_logging_and_tracing(__name__) # _SYNC_THRESHOLD defines the number of days to continue syncing closed tasks, after this many days they will be removed from # the sync DB. _SYNC_THRESHOLD = os.environ.get("SYNC_THRESHOLD", 30) @@ -29,18 +29,30 @@ # it will cause the linked Asana task to be closed. _CLOSED_STATES = {"Closed", "Removed", "Done"} +# ADO field constants +ADO_STATE = "System.State" +ADO_TITLE = "System.Title" +ADO_WORK_ITEM_TYPE = "System.WorkItemType" + def start_sync(app: App) -> None: while True: - app.asana_tag_gid = create_tag_if_not_existing( - app, get_asana_workspace(app, app.asana_workspace_name), app.asana_tag_name - ) - projects = read_projects() - for project in projects: - sync_project(app, project) + with _TRACER.start_as_current_span("start_sync") as span: + span.add_event("Start sync run") + app.asana_tag_gid = create_tag_if_not_existing( + app, + get_asana_workspace(app, app.asana_workspace_name), + app.asana_tag_name, + ) + projects = read_projects() + for project in projects: + sync_project(app, project) - _LOGGER.info("Sync process complete, sleeping for %s seconds", app.sleep_time) - sleep(app.sleep_time) + _LOGGER.info( + "Sync process complete, sleeping for %s seconds", app.sleep_time + ) + span.end() + sleep(app.sleep_time) def read_projects() -> list: @@ -50,25 +62,29 @@ def read_projects() -> list: Returns: projects (list): List of projects with specific attributes. """ - # Initialize an empty list to store the projects - projects = [] - - # Open the JSON file and load the data - with open(os.path.join(os.path.dirname(__package__), "data", "projects.json")) as f: - data = json.load(f) - - # Iterate over each project in the data and append it to the projects list - for project in data: - projects.append( - { - "adoProjectName": project["adoProjectName"], - "adoTeamName": project["adoTeamName"], - "asanaProjectName": project["asanaProjectName"], - } - ) + with _TRACER.start_as_current_span("read_projects"): + # Initialize an empty list to store the projects + projects = [] + + # Open the JSON file and load the data + with open( + os.path.join(os.path.dirname(__package__), "data", "projects.json"), + encoding="utf-8", + ) as file: + data = json.load(file) + + # Iterate over each project in the data and append it to the projects list + for project in data: + projects.append( + { + "adoProjectName": project["adoProjectName"], + "adoTeamName": project["adoTeamName"], + "asanaProjectName": project["asanaProjectName"], + } + ) - # Return the list of projects - return projects + # Return the list of projects + return projects def create_tag_if_not_existing( @@ -88,21 +104,23 @@ def create_tag_if_not_existing( Raises: ApiException: If an error occurs while making the API call. """ - existing_tag = get_tag_by_name(app, workspace, tag) - if existing_tag is not None: - return existing_tag - api_instance = asana.TagsApi(app.asana_client) - body = asana.TagsBody({"name": tag}) - try: - # Create a tag - _LOGGER.info("tag '%s' not found, creating it", tag) - api_response = api_instance.create_tag_for_workspace(body, workspace) - return api_response.data - except ApiException as exception: - _LOGGER.error( - "Exception when calling TagsApi->create_tag_for_workspace: %s\n", exception - ) - return None + with _TRACER.start_as_current_span("create_tag_if_not_existing"): + existing_tag = get_tag_by_name(app, workspace, tag) + if existing_tag is not None: + return existing_tag + api_instance = asana.TagsApi(app.asana_client) + body = asana.TagsBody({"name": tag}) + try: + # Create a tag + _LOGGER.info("tag '%s' not found, creating it", tag) + api_response = api_instance.create_tag_for_workspace(body, workspace) + return api_response.data + except ApiException as exception: + _LOGGER.error( + "Exception when calling TagsApi->create_tag_for_workspace: %s\n", + exception, + ) + return None def get_tag_by_name(app: App, workspace: str, tag: str) -> TagResponse | None: @@ -116,37 +134,39 @@ def get_tag_by_name(app: App, workspace: str, tag: str) -> TagResponse | None: Returns: TagResponse | None: The tag object if found, or None if not found. """ - api_instance = asana.TagsApi(app.asana_client) - try: - # Get all tags in the workspace. - _LOGGER.info("get workspace tag '%s'", tag) - api_response = api_instance.get_tags(workspace=workspace) + with _TRACER.start_as_current_span("get_tag_by_name"): + api_instance = asana.TagsApi(app.asana_client) + try: + # Get all tags in the workspace. + _LOGGER.info("get workspace tag '%s'", tag) + api_response = api_instance.get_tags(workspace=workspace) - # Iterate through the tags to find the desired tag. - tags_by_name = {t.name: t for t in api_response.data} - return tags_by_name.get(tag) - except ApiException as exception: - _LOGGER.error("Exception when calling TagsApi->get_tags: %s\n", exception) - return None + # Iterate through the tags to find the desired tag. + tags_by_name = {t.name: t for t in api_response.data} + return tags_by_name.get(tag) + except ApiException as exception: + _LOGGER.error("Exception when calling TagsApi->get_tags: %s\n", exception) + return None def get_asana_task_tags(app: App, task: TaskItem) -> list[TagResponse]: """ Retrieves the tag for a given Asana task. """ - api_instance = asana.TagsApi(app.asana_client) + with _TRACER.start_as_current_span("get_asana_task_tags"): + api_instance = asana.TagsApi(app.asana_client) - try: - # Get a task's tags - api_response = api_instance.get_tags_for_task( - task.asana_gid, - ) - return api_response.data - except ApiException as exception: - _LOGGER.error( - "Exception when calling TagsApi->get_tags_for_task: %s\n", exception - ) - return [] + try: + # Get a task's tags + api_response = api_instance.get_tags_for_task( + task.asana_gid, + ) + return api_response.data + except ApiException as exception: + _LOGGER.error( + "Exception when calling TagsApi->get_tags_for_task: %s\n", exception + ) + return [] def tag_asana_item(app: App, task: TaskItem, tag: TagResponse) -> None: @@ -235,7 +255,7 @@ def sync_project(app: App, project): if ado_assigned is None and existing_match is None: _LOGGER.debug( "%s:skipping item as it is not assigned", - ado_task.fields["System.Title"], + ado_task.fields[ADO_TITLE], ) continue asana_matched_user = matching_user(asana_users, ado_assigned) @@ -243,13 +263,13 @@ def sync_project(app: App, project): continue if existing_match is None: - _LOGGER.info("%s:unmapped task", ado_task.fields["System.Title"]) + _LOGGER.info("%s:unmapped task", ado_task.fields[ADO_TITLE]) existing_match = TaskItem( ado_id=ado_task.id, ado_rev=ado_task.rev, - title=ado_task.fields["System.Title"], - item_type=ado_task.fields["System.WorkItemType"], - state=ado_task.fields["System.State"], + title=ado_task.fields[ADO_TITLE], + item_type=ado_task.fields[ADO_WORK_ITEM_TYPE], + state=ado_task.fields[ADO_STATE], created_date=iso8601_utc(datetime.utcnow()), updated_date=iso8601_utc(datetime.utcnow()), url=safe_get( @@ -265,7 +285,7 @@ def sync_project(app: App, project): # The Asana task does not exist, create it and map the tasks. _LOGGER.info( "%s:no matching asana task exists, creating new task", - ado_task.fields["System.Title"], + ado_task.fields[ADO_TITLE], ) create_asana_task( app, @@ -276,7 +296,7 @@ def sync_project(app: App, project): continue else: # The Asana task exists, map the tasks in the db. - _LOGGER.info("%s:dating task", ado_task.fields["System.Title"]) + _LOGGER.info("%s:dating task", ado_task.fields[ADO_TITLE]) if asana_task is not None: existing_match.asana_gid = asana_task.gid update_asana_task( @@ -300,9 +320,9 @@ def sync_project(app: App, project): _LOGGER.error("No Asana task found with gid: %s", existing_match.asana_gid) continue existing_match.ado_rev = ado_task.rev - existing_match.title = ado_task.fields["System.Title"] - existing_match.item_type = ado_task.fields["System.WorkItemType"] - existing_match.state = ado_task.fields["System.State"] + existing_match.title = ado_task.fields[ADO_TITLE] + existing_match.item_type = ado_task.fields[ADO_WORK_ITEM_TYPE] + existing_match.state = ado_task.fields[ADO_STATE] existing_match.updated_date = iso8601_utc(datetime.now()) existing_match.url = safe_get( ado_task, "_links", "additional_properties", "html", "href" @@ -336,6 +356,12 @@ def sync_project(app: App, project): # Get the work item details from ADO. existing_match = TaskItem.search(app, ado_id=wi["ado_id"]) + if existing_match is None: + _LOGGER.warning( + "Task with ADO ID %s not found in database", + wi["ado_id"], + ) + continue ado_task = app.ado_wit_client.get_work_item(existing_match.ado_id) # Check if the item is already up to date. @@ -355,9 +381,9 @@ def sync_project(app: App, project): ) continue existing_match.ado_rev = ado_task.rev - existing_match.title = ado_task.fields["System.Title"] - existing_match.item_type = ado_task.fields["System.WorkItemType"] - existing_match.state = ado_task.fields["System.State"] + existing_match.title = ado_task.fields[ADO_TITLE] + existing_match.item_type = ado_task.fields[ADO_WORK_ITEM_TYPE] + existing_match.state = ado_task.fields[ADO_STATE] existing_match.updated_date = iso8601_utc(datetime.now()) existing_match.url = safe_get( ado_task, "_links", "additional_properties", "html", "href" @@ -425,7 +451,7 @@ def matching_user( return None -def get_asana_workspace(app: App, name) -> str | None: +def get_asana_workspace(app: App, name: str) -> str: """ Returns the workspace gid for the named Asana workspace. @@ -439,11 +465,12 @@ def get_asana_workspace(app: App, name) -> str | None: for w in api_response.data: if w.name == name: return w.gid + raise ValueError(f"No workspace found with name '{name}'") except ApiException as exception: _LOGGER.error( "Exception when calling WorkspacesApi->get_workspaces: %s\n", exception ) - return None + raise ValueError(f"Call to Asana API failed: {exception}") from exception def get_asana_project(app: App, workspace_gid, name) -> str | None: diff --git a/ado_asana_sync/utils/logging_tracing.py b/ado_asana_sync/utils/logging_tracing.py new file mode 100644 index 0000000..179b2b4 --- /dev/null +++ b/ado_asana_sync/utils/logging_tracing.py @@ -0,0 +1,31 @@ +""" +This package provides logging and tracing functionality for the application. +It uses the OpenTelemetry library for tracing and the logging module for logging. + +The `setup_logging_and_tracing` function initializes the logger and tracer for the specified module. +It takes in the module name as an argument and returns the logger and tracer objects. + +Example usage: + from ado_asana_sync.utils.logging_tracing import setup_logging_and_tracing + + # This module uses the logger and tracer instances _LOGGER and _TRACER for logging and tracing, respectively. + _LOGGER, _TRACER = setup_logging_and_tracing(__name__) +""" +import logging +from opentelemetry import trace + + +def setup_logging_and_tracing(module_name: str): + """ + Initializes the logger and tracer for the specified module. + + Args: + module_name (str): The name of the module to initialize the logger and tracer for. + + Returns: + tuple: A tuple containing the logger and tracer objects. + """ + logger = logging.getLogger(module_name) + tracer = trace.get_tracer(module_name) + logging.getLogger("azure").setLevel(logging.WARNING) + return logger, tracer diff --git a/poetry.lock b/poetry.lock index 6c149ad..7f71374 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,23 @@ python-dateutil = ">=2.1" six = ">=1.10" urllib3 = ">=1.23" +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + [[package]] name = "astroid" version = "2.15.8" @@ -55,6 +72,21 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["aiohttp (>=3.0)"] +[[package]] +name = "azure-core-tracing-opentelemetry" +version = "1.0.0b11" +description = "Microsoft Azure Azure Core OpenTelemetry plugin Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-core-tracing-opentelemetry-1.0.0b11.tar.gz", hash = "sha256:a230d1555838b5d07b7594221cd639ea7bc24e29c881e5675e311c6067bad4f5"}, + {file = "azure_core_tracing_opentelemetry-1.0.0b11-py3-none-any.whl", hash = "sha256:016cefcaff2900fb5cdb7a8a7abd03e9c266622c06e26b3fe6dafa54c4b48bf5"}, +] + +[package.dependencies] +azure-core = ">=1.24.0,<2.0.0" +opentelemetry-api = ">=1.12.0,<2.0.0" + [[package]] name = "azure-devops" version = "7.1.0b3" @@ -69,6 +101,48 @@ files = [ [package.dependencies] msrest = ">=0.7.1,<0.8.0" +[[package]] +name = "azure-monitor-opentelemetry" +version = "1.0.0" +description = "Microsoft Azure Monitor Opentelemetry Distro Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-monitor-opentelemetry-1.0.0.tar.gz", hash = "sha256:cc0d28d5dda6cc09aacf6afc1010b3ad77d8d1dca0ac33b8a20cd3d52fab5082"}, + {file = "azure_monitor_opentelemetry-1.0.0-py3-none-any.whl", hash = "sha256:1996c935088e0a604e5bb46896786415aa9ba80b5904173fc1961b3b950cb17a"}, +] + +[package.dependencies] +azure-core = ">=1.24.0,<2.0.0" +azure-core-tracing-opentelemetry = ">=1.0.0b10,<1.1.0" +azure-monitor-opentelemetry-exporter = ">=1.0.0b17,<1.1.0" +opentelemetry-instrumentation-django = ">=0.41b0,<1.0" +opentelemetry-instrumentation-fastapi = ">=0.41b0,<1.0" +opentelemetry-instrumentation-flask = ">=0.41b0,<1.0" +opentelemetry-instrumentation-psycopg2 = ">=0.41b0,<1.0" +opentelemetry-instrumentation-requests = ">=0.41b0,<1.0" +opentelemetry-instrumentation-urllib = ">=0.41b0,<1.0" +opentelemetry-instrumentation-urllib3 = ">=0.41b0,<1.0" +opentelemetry-resource-detector-azure = ">=0.1.0,<0.2.0" + +[[package]] +name = "azure-monitor-opentelemetry-exporter" +version = "1.0.0b17" +description = "Microsoft Azure Monitor Opentelemetry Exporter Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-monitor-opentelemetry-exporter-1.0.0b17.tar.gz", hash = "sha256:97a0c2e8365a07eadd7264bb5304b306172dd66af0d1d4041e58a812e268bfdd"}, + {file = "azure_monitor_opentelemetry_exporter-1.0.0b17-py2.py3-none-any.whl", hash = "sha256:d8235b2e8d30ffb9d16cc36e4ab1e88a8ae6cd8d89270584e93189b18d0ce87b"}, +] + +[package.dependencies] +azure-core = ">=1.23.0,<2.0.0" +fixedint = "0.1.6" +msrest = ">=0.6.10" +opentelemetry-api = ">=1.20,<2.0" +opentelemetry-sdk = ">=1.20,<2.0" + [[package]] name = "bandit" version = "1.7.5" @@ -347,6 +421,23 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "dill" version = "0.3.7" @@ -404,6 +495,18 @@ typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.11\""} docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +[[package]] +name = "fixedint" +version = "0.1.6" +description = "simple fixed-width integers" +optional = false +python-versions = "*" +files = [ + {file = "fixedint-0.1.6-py2-none-any.whl", hash = "sha256:41953193f08cbe984f584d8513c38fe5eea5fbd392257433b2210391c8a21ead"}, + {file = "fixedint-0.1.6-py3-none-any.whl", hash = "sha256:b8cf9f913735d2904deadda7a6daa9f57100599da1de57a7448ea1be75ae8c9c"}, + {file = "fixedint-0.1.6.tar.gz", hash = "sha256:703005d090499d41ce7ce2ee7eae8f7a5589a81acdc6b79f1728a56495f2c799"}, +] + [[package]] name = "flake8" version = "6.1.0" @@ -459,6 +562,25 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -686,6 +808,302 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "opentelemetry-api" +version = "1.20.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_api-1.20.0-py3-none-any.whl", hash = "sha256:982b76036fec0fdaf490ae3dfd9f28c81442a33414f737abc687a32758cdcba5"}, + {file = "opentelemetry_api-1.20.0.tar.gz", hash = "sha256:06abe351db7572f8afdd0fb889ce53f3c992dbf6f6262507b385cc1963e06983"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<7.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.41b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation-0.41b0-py3-none-any.whl", hash = "sha256:0ef9e5705ceca0205992a4a845ae4251ce6ec15a1206ca07c2b00afb0c5bd386"}, + {file = "opentelemetry_instrumentation-0.41b0.tar.gz", hash = "sha256:214382ba10dfd29d4e24898a4c7ef18b7368178a6277a1aec95cdb75cabf4612"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +setuptools = ">=16.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.41b0" +description = "ASGI instrumentation for OpenTelemetry" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_asgi-0.41b0-py3-none-any.whl", hash = "sha256:46084195fb9c50507abbe1dd490ae4c31c8658c5790f1ddf7af95c417dbe6422"}, + {file = "opentelemetry_instrumentation_asgi-0.41b0.tar.gz", hash = "sha256:921244138b37a9a25edf2153f1c248f16f98610ee8d840b25fd7bf6b165e4d72"}, +] + +[package.dependencies] +asgiref = ">=3.0,<4.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +instruments = ["asgiref (>=3.0,<4.0)"] +test = ["opentelemetry-instrumentation-asgi[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-dbapi" +version = "0.41b0" +description = "OpenTelemetry Database API instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_dbapi-0.41b0-py3-none-any.whl", hash = "sha256:b34cf3d9c3e7dd0a4ca6a75913a8cebe54b5b977464db4371ed00da48f9040e6"}, + {file = "opentelemetry_instrumentation_dbapi-0.41b0.tar.gz", hash = "sha256:53a3925eb7e8e89cff45fb90a15c5fb520fc75f934dee36f315af24f1bf25701"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +wrapt = ">=1.0.0,<2.0.0" + +[package.extras] +test = ["opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-django" +version = "0.41b0" +description = "OpenTelemetry Instrumentation for Django" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_django-0.41b0-py3-none-any.whl", hash = "sha256:96fb8169a7ed1e5cc1a26b00b9c6556af4b5f11ad6ea69df0e3bd76813baa8e3"}, + {file = "opentelemetry_instrumentation_django-0.41b0.tar.gz", hash = "sha256:3942e54d0cd5f220ff299b8e84b5fb42c9c43d2a8e0bad2cbb47722147a1bc3d"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-instrumentation-wsgi = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +asgi = ["opentelemetry-instrumentation-asgi (==0.41b0)"] +instruments = ["django (>=1.10)"] +test = ["opentelemetry-instrumentation-django[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.41b0" +description = "OpenTelemetry FastAPI Instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_fastapi-0.41b0-py3-none-any.whl", hash = "sha256:5990368e99ecc989df0a248a0b9b8e85d8b3eb7c1dbf5131c36982ba7f4a43b7"}, + {file = "opentelemetry_instrumentation_fastapi-0.41b0.tar.gz", hash = "sha256:eb4ceefe8b944fc9ea5e61fa558b99afd1285431b563f3f0104ac177cde4dfe5"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-instrumentation-asgi = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +instruments = ["fastapi (>=0.58,<1.0)"] +test = ["httpx (>=0.22,<1.0)", "opentelemetry-instrumentation-fastapi[instruments]", "opentelemetry-test-utils (==0.41b0)", "requests (>=2.23,<3.0)"] + +[[package]] +name = "opentelemetry-instrumentation-flask" +version = "0.41b0" +description = "Flask instrumentation for OpenTelemetry" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_flask-0.41b0-py3-none-any.whl", hash = "sha256:74b3c33ee76a6c104c190325c5e083549afa28f19603aa3dc346783cbbc3325c"}, + {file = "opentelemetry_instrumentation_flask-0.41b0.tar.gz", hash = "sha256:f3a98f026e566420974bf1b8fa10c915f9bef5d851faecace12fc1e893639194"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-instrumentation-wsgi = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" +packaging = ">=21.0" + +[package.extras] +instruments = ["flask (>=1.0,<3.0)"] +test = ["markupsafe (==2.1.2)", "opentelemetry-instrumentation-flask[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-psycopg2" +version = "0.41b0" +description = "OpenTelemetry psycopg2 instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_psycopg2-0.41b0-py3-none-any.whl", hash = "sha256:0d6852e568f7e8750abf4cf49f1cab09dcf6f49ef9b2ab820a091c679d710c2a"}, + {file = "opentelemetry_instrumentation_psycopg2-0.41b0.tar.gz", hash = "sha256:94b511b752fc4d12d4c473cda2218a42248ac3a5a9b3d4ac2d5e247537fa7785"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-instrumentation-dbapi = "0.41b0" + +[package.extras] +instruments = ["psycopg2 (>=2.7.3.1)"] +test = ["opentelemetry-instrumentation-psycopg2[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.41b0" +description = "OpenTelemetry requests instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_requests-0.41b0-py3-none-any.whl", hash = "sha256:687fde31111669e729054e64d246c96b0b9d4d8702bd0e3569b7660bdb528d71"}, + {file = "opentelemetry_instrumentation_requests-0.41b0.tar.gz", hash = "sha256:bdc5515ae7533e620b312fd989941b7c2c92d492a2d4418f6ef8db5d7422fa64"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +instruments = ["requests (>=2.0,<3.0)"] +test = ["httpretty (>=1.0,<2.0)", "opentelemetry-instrumentation-requests[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-urllib" +version = "0.41b0" +description = "OpenTelemetry urllib instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_urllib-0.41b0-py3-none-any.whl", hash = "sha256:cee9e95f55a73480df0915358ce8668bbeda53324c9426847e2ccaea0cac1a87"}, + {file = "opentelemetry_instrumentation_urllib-0.41b0.tar.gz", hash = "sha256:113416b8bd9c2d5c890cb6f86737886e209a3776c2ecdc023887bd78634d5ef3"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +test = ["httpretty (>=1.0,<2.0)", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-urllib3" +version = "0.41b0" +description = "OpenTelemetry urllib3 instrumentation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_urllib3-0.41b0-py3-none-any.whl", hash = "sha256:812771728183654b7c0b8b9672cb3d8946e533afd2e45beefdf8981e47ecba0e"}, + {file = "opentelemetry_instrumentation_urllib3-0.41b0.tar.gz", hash = "sha256:65c539fcb1691b7558796f333a9e862028a56baa79e8e5fdbd244de65c6f8f3d"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" +wrapt = ">=1.0.0,<2.0.0" + +[package.extras] +instruments = ["urllib3 (>=1.0.0,<3.0.0)"] +test = ["httpretty (>=1.0,<2.0)", "opentelemetry-instrumentation-urllib3[instruments]", "opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-instrumentation-wsgi" +version = "0.41b0" +description = "WSGI Middleware for OpenTelemetry" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_instrumentation_wsgi-0.41b0-py3-none-any.whl", hash = "sha256:0095834dda60f876c7ed00bc16292b40db86f8cfebdf251417adbd9810993644"}, + {file = "opentelemetry_instrumentation_wsgi-0.41b0.tar.gz", hash = "sha256:4c689d59f8e33d2c7a990e475b7934baca554f35684adc346f0882d974c7dc2d"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.41b0" +opentelemetry-semantic-conventions = "0.41b0" +opentelemetry-util-http = "0.41b0" + +[package.extras] +test = ["opentelemetry-test-utils (==0.41b0)"] + +[[package]] +name = "opentelemetry-resource-detector-azure" +version = "0.1.0" +description = "Azure Resource Detector for OpenTelemetry" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_resource_detector_azure-0.1.0-py3-none-any.whl", hash = "sha256:1cf96912c4f4ce0cf8520796e51b1f64a4c2f49ec1efb715ec9d04fffe7fb395"}, + {file = "opentelemetry_resource_detector_azure-0.1.0.tar.gz", hash = "sha256:ac075af66b2845bb4c7f798c4428cb1a50d90acf7e363a0e2a77c4045c287a7a"}, +] + +[package.dependencies] +opentelemetry-sdk = ">=1.19,<2.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.20.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_sdk-1.20.0-py3-none-any.whl", hash = "sha256:f2230c276ff4c63ea09b3cb2e2ac6b1265f90af64e8d16bbf275c81a9ce8e804"}, + {file = "opentelemetry_sdk-1.20.0.tar.gz", hash = "sha256:702e432a457fa717fd2ddfd30640180e69938f85bb7fec3e479f85f61c1843f8"}, +] + +[package.dependencies] +opentelemetry-api = "1.20.0" +opentelemetry-semantic-conventions = "0.41b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.41b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_semantic_conventions-0.41b0-py3-none-any.whl", hash = "sha256:45404391ed9e50998183a4925ad1b497c01c143f06500c3b9c3d0013492bb0f2"}, + {file = "opentelemetry_semantic_conventions-0.41b0.tar.gz", hash = "sha256:0ce5b040b8a3fc816ea5879a743b3d6fe5db61f6485e4def94c6ee4d402e1eb7"}, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.41b0" +description = "Web util for OpenTelemetry" +optional = false +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_util_http-0.41b0-py3-none-any.whl", hash = "sha256:6a167fd1e0e8b0f629530d971165b5d82ed0be2154b7f29498499c3a517edee5"}, + {file = "opentelemetry_util_http-0.41b0.tar.gz", hash = "sha256:16d5bd04a380dc1079e766562d1e1626cbb47720f197f67010c45f090fffdfb3"}, +] + [[package]] name = "packaging" version = "23.1" @@ -909,7 +1327,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -917,15 +1334,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -942,7 +1352,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -950,7 +1359,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1013,6 +1421,22 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -1241,7 +1665,22 @@ files = [ {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0daed9ddbde2e58375940a0754687d68b24d35deea1d0e903aea6785f213da06" +content-hash = "1fd62d2ea6b509e7eff6873efea341a977699db664d0ee25fc9cca739f2ce1b7" diff --git a/pyproject.toml b/pyproject.toml index 8fc92af..e009fda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ asana = "^4.0.6" azure-devops = "^7.1.0b3" tinydb = "^4.8.0" pytz = "^2023.3" +azure-monitor-opentelemetry = "^1.0.0" [tool.poetry.group.dev.dependencies] bandit = "^1.7.5" diff --git a/tests/utils/test_logging_tracing.py b/tests/utils/test_logging_tracing.py new file mode 100644 index 0000000..785e3ae --- /dev/null +++ b/tests/utils/test_logging_tracing.py @@ -0,0 +1,17 @@ +import logging +import unittest + +from opentelemetry import trace + +from ado_asana_sync.utils.logging_tracing import setup_logging_and_tracing + + +class TestLoggingTracing(unittest.TestCase): + def test_setup_logging_and_tracing(self): + logger, tracer = setup_logging_and_tracing(__name__) + self.assertIsInstance(logger, logging.Logger) + self.assertIsInstance(tracer, trace.Tracer) + + +if __name__ == "__main__": + unittest.main()