Add asyncclick instrumentation (#3319)

* Add asyncclick instrumentation

* Add instrumentation for asyncclick based CLI apps
* Add tox
* Add Changelog
* Update workflows

This implementation is based on the original click instrumentation work by:
- Emídio Neto <9735060+emdneto@users.noreply.github.com>
- Anuraag (Rag) Agrawal <anuraaga@gmail.com>
- Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>

* Update CHANGELOG.md

Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>

* Fix async refs in code-block example

---------

Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
This commit is contained in:
Joe McGinley 2025-03-28 11:20:29 +00:00 committed by GitHub
parent 50ab047143
commit 0ea9998c4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1172 additions and 252 deletions

View File

@ -11,6 +11,9 @@ components:
- oxeye-nikolay
- nikosokolik
instrumentation/opentelemetry-instrumentation-asyncclick:
- jomcgi
instrumentation/opentelemetry-instrumentation-kafka-python:
- nozik

View File

@ -525,6 +525,28 @@ jobs:
- name: Run tests
run: tox -e py38-test-instrumentation-boto -- -ra
py38-test-instrumentation-asyncclick:
name: instrumentation-asyncclick
runs-on: ubuntu-latest
steps:
- name: Checkout contrib repo @ SHA - ${{ env.CONTRIB_REPO_SHA }}
uses: actions/checkout@v4
with:
repository: open-telemetry/opentelemetry-python-contrib
ref: ${{ env.CONTRIB_REPO_SHA }}
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
architecture: "x64"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-asyncclick -- -ra
py38-test-instrumentation-click:
name: instrumentation-click
runs-on: ubuntu-latest

View File

@ -322,6 +322,24 @@ jobs:
- name: Run tests
run: tox -e lint-instrumentation-boto
lint-instrumentation-asyncclick:
name: instrumentation-asyncclick
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e lint-instrumentation-asyncclick
lint-instrumentation-click:
name: instrumentation-click
runs-on: ubuntu-latest

View File

@ -2842,6 +2842,132 @@ jobs:
- name: Run tests
run: tox -e py311-test-instrumentation-boto -- -ra
py38-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-asyncclick -- -ra
py39-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-asyncclick -- -ra
py310-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.10 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py310-test-instrumentation-asyncclick -- -ra
py311-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.11 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py311-test-instrumentation-asyncclick -- -ra
py312-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.12 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py312-test-instrumentation-asyncclick -- -ra
py313-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick 3.13 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py313-test-instrumentation-asyncclick -- -ra
pypy3-test-instrumentation-asyncclick_ubuntu-latest:
name: instrumentation-asyncclick pypy-3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python pypy-3.8
uses: actions/setup-python@v5
with:
python-version: "pypy-3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e pypy3-test-instrumentation-asyncclick -- -ra
py38-test-instrumentation-click_ubuntu-latest:
name: instrumentation-click 3.8 Ubuntu
runs-on: ubuntu-latest
@ -4389,129 +4515,3 @@ jobs:
- name: Run tests
run: tox -e py312-test-instrumentation-urllib -- -ra
py313-test-instrumentation-urllib_ubuntu-latest:
name: instrumentation-urllib 3.13 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py313-test-instrumentation-urllib -- -ra
pypy3-test-instrumentation-urllib_ubuntu-latest:
name: instrumentation-urllib pypy-3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python pypy-3.8
uses: actions/setup-python@v5
with:
python-version: "pypy-3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e pypy3-test-instrumentation-urllib -- -ra
py38-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-urllib3-0 -- -ra
py38-test-instrumentation-urllib3-1_ubuntu-latest:
name: instrumentation-urllib3-1 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-urllib3-1 -- -ra
py39-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-urllib3-0 -- -ra
py39-test-instrumentation-urllib3-1_ubuntu-latest:
name: instrumentation-urllib3-1 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-urllib3-1 -- -ra
py310-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.10 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py310-test-instrumentation-urllib3-0 -- -ra

View File

@ -16,6 +16,132 @@ env:
jobs:
py313-test-instrumentation-urllib_ubuntu-latest:
name: instrumentation-urllib 3.13 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py313-test-instrumentation-urllib -- -ra
pypy3-test-instrumentation-urllib_ubuntu-latest:
name: instrumentation-urllib pypy-3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python pypy-3.8
uses: actions/setup-python@v5
with:
python-version: "pypy-3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e pypy3-test-instrumentation-urllib -- -ra
py38-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-urllib3-0 -- -ra
py38-test-instrumentation-urllib3-1_ubuntu-latest:
name: instrumentation-urllib3-1 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-urllib3-1 -- -ra
py39-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-urllib3-0 -- -ra
py39-test-instrumentation-urllib3-1_ubuntu-latest:
name: instrumentation-urllib3-1 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-urllib3-1 -- -ra
py310-test-instrumentation-urllib3-0_ubuntu-latest:
name: instrumentation-urllib3-0 3.10 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py310-test-instrumentation-urllib3-0 -- -ra
py310-test-instrumentation-urllib3-1_ubuntu-latest:
name: instrumentation-urllib3-1 3.10 Ubuntu
runs-on: ubuntu-latest
@ -4389,129 +4515,3 @@ jobs:
- name: Run tests
run: tox -e py312-test-instrumentation-threading -- -ra
py313-test-instrumentation-threading_ubuntu-latest:
name: instrumentation-threading 3.13 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py313-test-instrumentation-threading -- -ra
pypy3-test-instrumentation-threading_ubuntu-latest:
name: instrumentation-threading pypy-3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python pypy-3.8
uses: actions/setup-python@v5
with:
python-version: "pypy-3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e pypy3-test-instrumentation-threading -- -ra
py38-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-tornado -- -ra
py39-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-tornado -- -ra
py310-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.10 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py310-test-instrumentation-tornado -- -ra
py311-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.11 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py311-test-instrumentation-tornado -- -ra
py312-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.12 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py312-test-instrumentation-tornado -- -ra

View File

@ -16,6 +16,132 @@ env:
jobs:
py313-test-instrumentation-threading_ubuntu-latest:
name: instrumentation-threading 3.13 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py313-test-instrumentation-threading -- -ra
pypy3-test-instrumentation-threading_ubuntu-latest:
name: instrumentation-threading pypy-3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python pypy-3.8
uses: actions/setup-python@v5
with:
python-version: "pypy-3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e pypy3-test-instrumentation-threading -- -ra
py38-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.8 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py38-test-instrumentation-tornado -- -ra
py39-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.9 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py39-test-instrumentation-tornado -- -ra
py310-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.10 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py310-test-instrumentation-tornado -- -ra
py311-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.11 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py311-test-instrumentation-tornado -- -ra
py312-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.12 Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tox
run: pip install tox-uv
- name: Run tests
run: tox -e py312-test-instrumentation-tornado -- -ra
py313-test-instrumentation-tornado_ubuntu-latest:
name: instrumentation-tornado 3.13 Ubuntu
runs-on: ubuntu-latest

View File

@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
- `opentelemetry-instrumentation-asyncclick`: new instrumentation to trace asyncclick commands
([#3319](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3319))
### Fixed
- `opentelemetry-instrumentation` Fix client address is set to server address in new semconv

View File

@ -15,6 +15,7 @@ aiohttp~=3.0
aiokafka~=0.11.0
aiopg>=0.13.0,<1.3.0
asyncpg>=0.12.0
asyncclick~=8.0
boto~=2.0
botocore~=1.0
boto3~=1.0

View File

@ -0,0 +1,7 @@
.. include:: ../../../instrumentation/opentelemetry-instrumentation-asyncclick/README.rst
:end-before: References
.. automodule:: opentelemetry.instrumentation.asyncclick
:members:
:undoc-members:
:show-inheritance:

View File

@ -7,6 +7,7 @@
| [opentelemetry-instrumentation-aiokafka](./opentelemetry-instrumentation-aiokafka) | aiokafka >= 0.8, < 1.0 | No | development
| [opentelemetry-instrumentation-aiopg](./opentelemetry-instrumentation-aiopg) | aiopg >= 0.13.0, < 2.0.0 | No | development
| [opentelemetry-instrumentation-asgi](./opentelemetry-instrumentation-asgi) | asgiref ~= 3.0 | Yes | migration
| [opentelemetry-instrumentation-asyncclick](./opentelemetry-instrumentation-asyncclick) | asyncclick ~= 8.0 | No | development
| [opentelemetry-instrumentation-asyncio](./opentelemetry-instrumentation-asyncio) | asyncio | No | development
| [opentelemetry-instrumentation-asyncpg](./opentelemetry-instrumentation-asyncpg) | asyncpg >= 0.12.0 | No | development
| [opentelemetry-instrumentation-aws-lambda](./opentelemetry-instrumentation-aws-lambda) | aws_lambda | No | development

View File

@ -0,0 +1,24 @@
OpenTelemetry asyncclick Instrumentation
========================================
|pypi|
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-asyncclick.svg
:target: https://pypi.org/project/opentelemetry-instrumentation-asyncclick/
This library allows tracing requests made by the asyncclick fork of the click library.
Installation
------------
::
pip install opentelemetry-instrumentation-asyncclick
References
----------
* `OpenTelemetry asyncclick/ Tracing <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asyncclick/asyncclick.html>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_

View File

@ -0,0 +1,57 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "opentelemetry-instrumentation-asyncclick"
dynamic = ["version"]
description = "Async Click instrumentation for OpenTelemetry"
readme = "README.rst"
license = "Apache-2.0"
requires-python = ">=3.8"
authors = [
{ name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-semantic-conventions == 0.53b0.dev",
"wrapt ~= 1.0",
"typing_extensions ~= 4.12",
]
[project.optional-dependencies]
instruments = [
"asyncclick ~= 8.0",
]
[project.entry-points.opentelemetry_instrumentor]
asyncclick = "opentelemetry.instrumentation.asyncclick:AsyncClickInstrumentor"
[project.urls]
Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-asyncclick"
Repository = "https://github.com/open-telemetry/opentelemetry-python-contrib"
[tool.hatch.version]
path = "src/opentelemetry/instrumentation/asyncclick/version.py"
[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
]
[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]

View File

@ -0,0 +1,163 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Instrument `asyncclick`_ CLI applications. The instrumentor will avoid instrumenting
well-known servers (e.g. *flask run* and *uvicorn*) to avoid unexpected effects
like every request having the same Trace ID.
.. _asyncclick: https://pypi.org/project/asyncclick/
Usage
-----
.. code-block:: python
import asyncio
import asyncclick
from opentelemetry.instrumentation.asyncclick import AsyncClickInstrumentor
AsyncClickInstrumentor().instrument()
@asyncclick.command()
async def hello():
asyncclick.echo(f'Hello world!')
if __name__ == "__main__":
asyncio.run(hello())
API
---
"""
from __future__ import annotations
import os
import sys
from functools import partial
from logging import getLogger
from typing import (
TYPE_CHECKING,
Any,
Awaitable,
Callable,
Collection,
TypeVar,
)
import asyncclick
from typing_extensions import ParamSpec, Unpack
from wrapt import (
wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
)
from opentelemetry import trace
from opentelemetry.instrumentation.asyncclick.package import _instruments
from opentelemetry.instrumentation.asyncclick.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.utils import (
unwrap,
)
from opentelemetry.semconv._incubating.attributes.process_attributes import (
PROCESS_COMMAND_ARGS,
PROCESS_EXECUTABLE_NAME,
PROCESS_EXIT_CODE,
PROCESS_PID,
)
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
from opentelemetry.trace.status import StatusCode
if TYPE_CHECKING:
from typing import TypedDict
class InstrumentKwargs(TypedDict, total=False):
tracer_provider: trace.TracerProvider
class UninstrumentKwargs(TypedDict, total=False):
pass
_logger = getLogger(__name__)
T = TypeVar("T")
P = ParamSpec("P")
async def _command_invoke_wrapper(
wrapped: Callable[P, Awaitable[T]],
instance: asyncclick.core.Command,
args: tuple[Any, ...],
kwargs: dict[str, Any],
tracer: trace.Tracer,
) -> T:
# Subclasses of Command include groups and CLI runners, but
# we only want to instrument the actual commands which are
# instances of Command itself.
if instance.__class__ != asyncclick.Command:
return await wrapped(*args, **kwargs)
ctx = args[0]
span_name = ctx.info_name
span_attributes = {
PROCESS_COMMAND_ARGS: sys.argv,
PROCESS_EXECUTABLE_NAME: sys.argv[0],
PROCESS_EXIT_CODE: 0,
PROCESS_PID: os.getpid(),
}
with tracer.start_as_current_span(
name=span_name,
kind=trace.SpanKind.INTERNAL,
attributes=span_attributes,
) as span:
try:
result = await wrapped(*args, **kwargs)
return result
except Exception as exc:
span.set_status(StatusCode.ERROR, str(exc))
if span.is_recording():
span.set_attribute(ERROR_TYPE, type(exc).__qualname__)
span.set_attribute(
PROCESS_EXIT_CODE, getattr(exc, "exit_code", 1)
)
raise
# pylint: disable=no-self-use
class AsyncClickInstrumentor(BaseInstrumentor):
"""An instrumentor for asyncclick"""
def instrumentation_dependencies(self) -> Collection[str]:
return _instruments
def _instrument(self, **kwargs: Unpack[InstrumentKwargs]) -> None:
tracer_provider = kwargs.get("tracer_provider")
tracer = trace.get_tracer(
__name__,
__version__,
tracer_provider,
)
wrap_function_wrapper(
asyncclick.core.Command,
"invoke",
partial(_command_invoke_wrapper, tracer=tracer),
)
def _uninstrument(self, **kwargs: Unpack["UninstrumentKwargs"]) -> None:
unwrap(asyncclick.core.Command, "invoke")

View File

@ -0,0 +1,16 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
_instruments = ("asyncclick ~= 8.0",)

View File

@ -0,0 +1,15 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.53b0.dev"

View File

@ -0,0 +1,10 @@
asyncclick~=8.0
Flask~=3.0
pytest==7.4.4
anyio~=4.5
-e opentelemetry-instrumentation
-e instrumentation/opentelemetry-instrumentation-asyncclick
-e instrumentation/opentelemetry-instrumentation-flask
-e instrumentation/opentelemetry-instrumentation-wsgi
-e util/opentelemetry-util-http

View File

@ -0,0 +1,371 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import asyncio
import os
from typing import Any
from unittest import IsolatedAsyncioTestCase, mock
import asyncclick
import asyncclick.testing
from asyncclick.testing import CliRunner
from opentelemetry.instrumentation.asyncclick import AsyncClickInstrumentor
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import SpanKind
from opentelemetry.trace.status import StatusCode
def run_asyncclick_command_test(
command: asyncclick.core.Command, args: tuple[Any, ...] = (), **kwargs: Any
) -> asyncclick.testing.Result:
"""
Run an asyncclick command and return the result.
Args:
command: The AsyncClick command to run
args: Command-line arguments
**kwargs: Additional arguments for CliRunner.invoke
Returns:
The result of invoking the command
"""
async def _run() -> asyncclick.testing.Result:
runner = CliRunner()
return await runner.invoke(command, args, **kwargs)
return asyncio.run(_run())
class ClickTestCase(TestBase, IsolatedAsyncioTestCase):
# pylint: disable=unbalanced-tuple-unpacking
def setUp(self): # pylint: disable=invalid-name
super().setUp()
AsyncClickInstrumentor().instrument()
def tearDown(self): # pylint: disable=invalid-name
super().tearDown()
AsyncClickInstrumentor().uninstrument()
@mock.patch("sys.argv", ["command.py"])
def test_cli_command_wrapping(self):
@asyncclick.command()
async def command() -> None:
pass
result = run_asyncclick_command_test(command)
self.assertEqual(result.exit_code, 0)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.status.status_code, StatusCode.UNSET)
self.assertEqual(span.kind, SpanKind.INTERNAL)
self.assertEqual(span.name, "command")
self.assertEqual(
dict(span.attributes),
{
"process.executable.name": "command.py",
"process.command_args": ("command.py",),
"process.exit.code": 0,
"process.pid": os.getpid(),
},
)
@mock.patch("sys.argv", ["command.py"])
def test_cli_command_wrapping_with_name(self):
@asyncclick.command("mycommand")
async def renamedcommand() -> None:
pass
result = run_asyncclick_command_test(renamedcommand)
self.assertEqual(result.exit_code, 0)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.status.status_code, StatusCode.UNSET)
self.assertEqual(span.kind, SpanKind.INTERNAL)
self.assertEqual(span.name, "mycommand")
self.assertEqual(
dict(span.attributes),
{
"process.executable.name": "command.py",
"process.command_args": ("command.py",),
"process.exit.code": 0,
"process.pid": os.getpid(),
},
)
@mock.patch("sys.argv", ["command.py", "--opt", "argument"])
def test_cli_command_wrapping_with_options(self):
@asyncclick.command()
@asyncclick.argument("argument")
@asyncclick.option("--opt/--no-opt", default=False)
async def command(argument: str, opt: str) -> None:
pass
argv = ["command.py", "--opt", "argument"]
result = run_asyncclick_command_test(command, argv[1:])
self.assertEqual(result.exit_code, 0)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.status.status_code, StatusCode.UNSET)
self.assertEqual(span.kind, SpanKind.INTERNAL)
self.assertEqual(span.name, "command")
self.assertEqual(
dict(span.attributes),
{
"process.executable.name": "command.py",
"process.command_args": tuple(argv),
"process.exit.code": 0,
"process.pid": os.getpid(),
},
)
@mock.patch("sys.argv", ["command-raises.py"])
def test_cli_command_raises_error(self):
@asyncclick.command()
async def command_raises() -> None:
raise ValueError()
result = run_asyncclick_command_test(command_raises)
self.assertEqual(result.exit_code, 1)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.status.status_code, StatusCode.ERROR)
self.assertEqual(span.kind, SpanKind.INTERNAL)
self.assertEqual(span.name, "command-raises")
self.assertEqual(
dict(span.attributes),
{
"process.executable.name": "command-raises.py",
"process.command_args": ("command-raises.py",),
"process.exit.code": 1,
"process.pid": os.getpid(),
"error.type": "ValueError",
},
)
def test_uninstrument(self):
AsyncClickInstrumentor().uninstrument()
@asyncclick.command()
async def notracecommand() -> None:
pass
result = run_asyncclick_command_test(notracecommand)
self.assertEqual(result.exit_code, 0)
self.assertFalse(self.memory_exporter.get_finished_spans())
AsyncClickInstrumentor().instrument()
@mock.patch("sys.argv", ["command.py", "sub1", "sub2"])
def test_nested_command_groups(self):
"""Test instrumentation of nested command groups."""
@asyncclick.group()
async def cli() -> None:
pass
@cli.group()
async def sub1() -> None:
pass
@sub1.command()
async def sub2() -> None:
pass
result = run_asyncclick_command_test(cli, ["sub1", "sub2"])
self.assertEqual(result.exit_code, 0)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(
len(spans), 1
) # Only the leaf command should be instrumented
span = spans[0]
self.assertEqual(span.name, "sub2")
self.assertEqual(
dict(span.attributes),
{
"process.executable.name": "command.py",
"process.command_args": ("command.py", "sub1", "sub2"),
"process.exit.code": 0,
"process.pid": os.getpid(),
},
)
@mock.patch("sys.argv", ["command.py"])
def test_command_with_callback(self):
"""Test instrumentation of commands with callbacks."""
callback_called = False
def callback_func(
ctx: asyncclick.Context, param: asyncclick.Parameter, value: bool
) -> bool:
nonlocal callback_called
callback_called = True
return value
@asyncclick.command()
@asyncclick.option(
"--option", callback=callback_func, default="default"
)
async def command(option: str) -> None:
pass
result = run_asyncclick_command_test(command)
self.assertEqual(result.exit_code, 0)
self.assertTrue(callback_called)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.name, "command")
self.assertEqual(span.status.status_code, StatusCode.UNSET)
@mock.patch("sys.argv", ["command.py"])
def test_command_with_result_callback(self):
"""Test instrumentation with result callbacks."""
callback_called = False
@asyncclick.group(chain=True)
async def cli() -> None:
pass
@cli.result_callback()
async def process_result(result: asyncclick.testing.Result) -> None:
nonlocal callback_called
callback_called = True
@cli.command()
async def command() -> None:
pass
result = run_asyncclick_command_test(cli, ["command"])
self.assertEqual(result.exit_code, 0)
self.assertTrue(callback_called)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
span = spans[0]
self.assertEqual(span.name, "command")
@mock.patch("sys.argv", ["command.py"])
def test_command_chaining(self):
"""Test instrumentation with command chaining."""
@asyncclick.group(chain=True)
async def cli() -> None:
pass
@cli.command()
async def cmd1() -> None:
return "result1"
@cli.command()
async def cmd2() -> None:
return "result2"
result = run_asyncclick_command_test(cli, ["cmd1", "cmd2"])
self.assertEqual(result.exit_code, 0)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
# Spans should be ordered by execution
self.assertEqual(spans[0].name, "cmd1")
self.assertEqual(spans[1].name, "cmd2")
@mock.patch("sys.argv", ["command.py"])
def test_custom_exit_codes(self) -> None:
"""Test instrumentation with custom exit codes."""
@asyncclick.command()
async def command() -> None:
raise asyncclick.exceptions.Exit(code=42)
result = run_asyncclick_command_test(command)
self.assertEqual(result.exit_code, 42)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.name, "command")
self.assertEqual(span.status.status_code, StatusCode.ERROR)
self.assertEqual(span.attributes["process.exit.code"], 42)
self.assertEqual(span.attributes["error.type"], "Exit")
@mock.patch("sys.argv", ["command.py"])
def test_context_object_passing(self):
"""Test that instrumentation preserves context object passing."""
@asyncclick.group()
@asyncclick.option("--debug/--no-debug", default=False)
@asyncclick.pass_context
async def cli(ctx: asyncclick.Context, debug: bool) -> None:
ctx.ensure_object(dict)
ctx.obj["DEBUG"] = debug
@cli.command()
@asyncclick.pass_context
async def command(ctx: asyncclick.Context) -> None:
assert isinstance(ctx.obj, dict)
assert "DEBUG" in ctx.obj
result = run_asyncclick_command_test(cli, ["--debug", "command"])
self.assertEqual(result.exit_code, 0)
(span,) = self.memory_exporter.get_finished_spans()
self.assertEqual(span.name, "command")
def test_multiple_instrumentation(self):
"""Test that instrumenting multiple times only applies once."""
# Already instrumented in setUp, instrument again
AsyncClickInstrumentor().instrument()
@asyncclick.command()
async def command() -> None:
pass
result = run_asyncclick_command_test(command)
self.assertEqual(result.exit_code, 0)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
def test_concurrency(self):
"""Test instrumentation with concurrent command execution."""
@asyncclick.command()
async def command1() -> None:
pass
@asyncclick.command()
async def command2() -> None:
pass
async def run_both() -> (
tuple[asyncclick.testing.Result, asyncclick.testing.Result]
):
runner = CliRunner()
task1 = asyncio.create_task(runner.invoke(command1))
task2 = asyncio.create_task(runner.invoke(command2))
results = await asyncio.gather(task1, task2)
return results
results = asyncio.run(run_both())
self.assertEqual(results[0].exit_code, 0)
self.assertEqual(results[1].exit_code, 0)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
span_names = [span.name for span in spans]
self.assertIn("command1", span_names)
self.assertIn("command2", span_names)

View File

@ -36,6 +36,7 @@ dependencies = [
"opentelemetry-instrumentation-aiokafka==0.53b0.dev",
"opentelemetry-instrumentation-aiopg==0.53b0.dev",
"opentelemetry-instrumentation-asgi==0.53b0.dev",
"opentelemetry-instrumentation-asyncclick==0.53b0.dev",
"opentelemetry-instrumentation-asyncio==0.53b0.dev",
"opentelemetry-instrumentation-asyncpg==0.53b0.dev",
"opentelemetry-instrumentation-aws-lambda==0.53b0.dev",

View File

@ -48,6 +48,10 @@ libraries = [
"library": "asgiref ~= 3.0",
"instrumentation": "opentelemetry-instrumentation-asgi==0.53b0.dev",
},
{
"library": "asyncclick ~= 8.0",
"instrumentation": "opentelemetry-instrumentation-asyncclick==0.53b0.dev",
},
{
"library": "asyncpg >= 0.12.0",
"instrumentation": "opentelemetry-instrumentation-asyncpg==0.53b0.dev",

View File

@ -196,12 +196,14 @@ pythonVersion = "3.8"
reportPrivateUsage = false # Ignore private attributes added by instrumentation packages.
# Add progressively instrumentation packages here.
include = [
"instrumentation/opentelemetry-instrumentation-asyncclick",
"instrumentation/opentelemetry-instrumentation-threading",
"instrumentation-genai/opentelemetry-instrumentation-vertexai",
]
# We should also add type hints to the test suite - It helps on finding bugs.
# We are excluding for now because it's easier, and more important to add to the instrumentation packages.
exclude = [
"instrumentation/opentelemetry-instrumentation-asyncclick/tests/**/*.py",
"instrumentation/opentelemetry-instrumentation-threading/tests/**",
"instrumentation-genai/opentelemetry-instrumentation-vertexai/tests/**/*.py",
"instrumentation-genai/opentelemetry-instrumentation-vertexai/examples/**/*.py",

13
tox.ini
View File

@ -109,6 +109,11 @@ envlist =
; pypy3-test-instrumentation-boto
lint-instrumentation-boto
; opentelemetry-instrumentation-asyncclick
py3{8,9,10,11,12,13}-test-instrumentation-asyncclick
pypy3-test-instrumentation-asyncclick
lint-instrumentation-asyncclick
; opentelemetry-instrumentation-click
py3{8,9,10,11,12,13}-test-instrumentation-click
pypy3-test-instrumentation-click
@ -466,6 +471,9 @@ deps =
pypy3-test-instrumentation-celery: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-celery/test-requirements-1.txt
lint-instrumentation-celery: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-celery/test-requirements-1.txt
asyncclick: {[testenv]test_deps}
asyncclick: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncclick/test-requirements.txt
click: {[testenv]test_deps}
click: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-click/test-requirements.txt
@ -741,6 +749,9 @@ commands =
test-instrumentation-asgi: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi/tests {posargs}
lint-instrumentation-asgi: sh -c "cd instrumentation && pylint --rcfile ../.pylintrc opentelemetry-instrumentation-asgi"
test-instrumentation-asyncclick: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncclick/tests {posargs}
lint-instrumentation-asyncclick: sh -c "cd instrumentation && pylint --rcfile ../.pylintrc opentelemetry-instrumentation-asyncclick"
test-instrumentation-asyncpg: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg/tests {posargs}
lint-instrumentation-asyncpg: sh -c "cd instrumentation && pylint --rcfile ../.pylintrc opentelemetry-instrumentation-asyncpg"
@ -1036,5 +1047,7 @@ deps =
{toxinidir}/util/opentelemetry-util-http
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-vertexai[instruments]
{toxinidir}/instrumentation-genai/opentelemetry-instrumentation-google-genai[instruments]
{toxinidir}/instrumentation/opentelemetry-instrumentation-asyncclick[instruments]
commands =
pyright

63
uv.lock
View File

@ -20,6 +20,7 @@ members = [
"opentelemetry-instrumentation-aiokafka",
"opentelemetry-instrumentation-aiopg",
"opentelemetry-instrumentation-asgi",
"opentelemetry-instrumentation-asyncclick",
"opentelemetry-instrumentation-asyncio",
"opentelemetry-instrumentation-asyncpg",
"opentelemetry-instrumentation-aws-lambda",
@ -590,6 +591,42 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721 },
]
[[package]]
name = "asyncclick"
version = "8.1.7.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.9'",
]
dependencies = [
{ name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/bf/59d836c3433d7aa07f76c2b95c4eb763195ea8a5d7f9ad3311ed30c2af61/asyncclick-8.1.7.2.tar.gz", hash = "sha256:219ea0f29ccdc1bb4ff43bcab7ce0769ac6d48a04f997b43ec6bee99a222daa0", size = 349073 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/6e/9acdbb25733e1de411663b59abe521bec738e72fe4e85843f6ff8b212832/asyncclick-8.1.7.2-py3-none-any.whl", hash = "sha256:1ab940b04b22cb89b5b400725132b069d01b0c3472a9702c7a2c9d5d007ded02", size = 99191 },
]
[[package]]
name = "asyncclick"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.13'",
"python_full_version >= '3.11' and python_full_version < '3.13'",
"python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
dependencies = [
{ name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/e1e5fdf1c1bb7e6e614987c120a98d9324bf8edfaa5f5cd16a6235c9d91b/asyncclick-8.1.8.tar.gz", hash = "sha256:0f0eb0f280e04919d67cf71b9fcdfb4db2d9ff7203669c40284485c149578e4c", size = 232900 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/cc/a436f0fc2d04e57a0697e0f87a03b9eaed03ad043d2d5f887f8eebcec95f/asyncclick-8.1.8-py3-none-any.whl", hash = "sha256:eb1ccb44bc767f8f0695d592c7806fdf5bd575605b4ee246ffd5fadbcfdbd7c6", size = 99093 },
{ url = "https://files.pythonhosted.org/packages/92/c4/ae9e9d25522c6dc96ff167903880a0fe94d7bd31ed999198ee5017d977ed/asyncclick-8.1.8.0-py3-none-any.whl", hash = "sha256:be146a2d8075d4fe372ff4e877f23c8b5af269d16705c1948123b9415f6fd678", size = 99115 },
]
[[package]]
name = "asyncpg"
version = "0.30.0"
@ -2861,6 +2898,32 @@ requires-dist = [
]
provides-extras = ["instruments"]
[[package]]
name = "opentelemetry-instrumentation-asyncclick"
source = { editable = "instrumentation/opentelemetry-instrumentation-asyncclick" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
{ name = "wrapt" },
]
[package.optional-dependencies]
instruments = [
{ name = "asyncclick", version = "8.1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "asyncclick", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
[package.metadata]
requires-dist = [
{ name = "asyncclick", marker = "extra == 'instruments'", specifier = "~=8.0" },
{ name = "opentelemetry-api", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-api&branch=main" },
{ name = "opentelemetry-semantic-conventions", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-semantic-conventions&branch=main" },
{ name = "typing-extensions", specifier = "~=4.12" },
{ name = "wrapt", specifier = "~=1.0" },
]
provides-extras = ["instruments"]
[[package]]
name = "opentelemetry-instrumentation-asyncio"
source = { editable = "instrumentation/opentelemetry-instrumentation-asyncio" }