From cc26e822a1e275604bb30444175712bcff834ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Tue, 19 Sep 2023 18:05:48 +0200 Subject: [PATCH 01/15] Add otlp exporters --- examples/init.py | 23 + examples/otlp/otel-collector-config.yml | 19 + examples/otlp/start.py | 17 + poetry.lock | 647 ++++++++++++++-------- pyproject.toml | 33 +- src/autometrics/decorator.py | 32 +- src/autometrics/{tracker => }/exemplar.py | 0 src/autometrics/exposition.py | 102 ++++ src/autometrics/init.py | 23 +- src/autometrics/settings.py | 48 +- src/autometrics/tracker/__init__.py | 1 + src/autometrics/tracker/opentelemetry.py | 19 +- src/autometrics/tracker/prometheus.py | 11 +- src/autometrics/tracker/temporary.py | 73 +++ src/autometrics/tracker/tracker.py | 95 +--- src/autometrics/tracker/types.py | 57 ++ 16 files changed, 849 insertions(+), 351 deletions(-) create mode 100644 examples/init.py create mode 100644 examples/otlp/otel-collector-config.yml create mode 100644 examples/otlp/start.py rename src/autometrics/{tracker => }/exemplar.py (100%) create mode 100644 src/autometrics/exposition.py create mode 100644 src/autometrics/tracker/temporary.py create mode 100644 src/autometrics/tracker/types.py diff --git a/examples/init.py b/examples/init.py new file mode 100644 index 0000000..9826afe --- /dev/null +++ b/examples/init.py @@ -0,0 +1,23 @@ +from time import sleep +from autometrics import autometrics, init + +from prometheus_client import generate_latest + + +@autometrics +def foo(): + return "foo" + + +foo() + +init( + version="1.0.0", + commit="123456789", + branch="main", + exporter={ + "type": "otlp-proto-grpc", + "endpoint": "localhost:4317", + "insecure": True, + }, +) diff --git a/examples/otlp/otel-collector-config.yml b/examples/otlp/otel-collector-config.yml new file mode 100644 index 0000000..731c5d0 --- /dev/null +++ b/examples/otlp/otel-collector-config.yml @@ -0,0 +1,19 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + +exporters: + logging: + loglevel: debug + +processors: + batch: + +service: + pipelines: + metrics: + receivers: [otlp] + processors: [] + exporters: [logging] diff --git a/examples/otlp/start.py b/examples/otlp/start.py new file mode 100644 index 0000000..985b9ce --- /dev/null +++ b/examples/otlp/start.py @@ -0,0 +1,17 @@ +import time +from autometrics import autometrics, init + +init( + exporter={"type": "otlp-proto-grpc", "endpoint": "http://localhost:4317"}, + service_name="my-service", +) + + +@autometrics +def my_function(): + pass + + +while True: + my_function() + time.sleep(1) diff --git a/poetry.lock b/poetry.lock index de9da7d..232d4af 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "anyio" @@ -37,6 +37,17 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = true +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "backports-zoneinfo" version = "0.2.1" @@ -67,33 +78,33 @@ tzdata = ["tzdata"] [[package]] name = "black" -version = "23.7.0" +version = "23.9.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, ] [package.dependencies] @@ -103,7 +114,7 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -142,93 +153,94 @@ files = [ [[package]] name = "brotli" -version = "1.0.9" +version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" files = [ - {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, - {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, - {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, - {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, - {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, - {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, - {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, - {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, - {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, - {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, - {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, - {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, - {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, - {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, - {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, - {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, - {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, - {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, - {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, - {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, + {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, + {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, + {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, + {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, + {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, + {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, + {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, ] [[package]] @@ -529,13 +541,13 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "django" -version = "4.2.4" +version = "4.2.5" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.4-py3-none-any.whl", hash = "sha256:860ae6a138a238fc4f22c99b52f3ead982bb4b1aad8c0122bcd8c8a3a02e409d"}, - {file = "Django-4.2.4.tar.gz", hash = "sha256:7e4225ec065e0f354ccf7349a22d209de09cc1c074832be9eb84c51c1799c432"}, + {file = "Django-4.2.5-py3-none-any.whl", hash = "sha256:b6b2b5cae821077f137dc4dade696a1c2aa292f892eca28fa8d7bfdf2608ddd4"}, + {file = "Django-4.2.5.tar.gz", hash = "sha256:5e5c1c9548ffb7796b4a8a4782e9a2e5a3df3615259fc1bfd3ebc73b646146c1"}, ] [package.dependencies] @@ -695,49 +707,58 @@ Flask = ">=0.9" [[package]] name = "gevent" -version = "23.7.0" +version = "23.9.1" description = "Coroutine-based network library" optional = false python-versions = ">=3.8" files = [ - {file = "gevent-23.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:add904a7ef960cd4e133e61eb7413982c5e4203928160be1c09752ac06a25e71"}, - {file = "gevent-23.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bd9ea1b5fbdc7e5921a9e515f34a450eb3927a902253a33caedcce2d19d7d96"}, - {file = "gevent-23.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c7c349aa23d67cf5cc3b2c87aaedcfead976d0577b1cfcd07ffeba63baba79c"}, - {file = "gevent-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92b837b60e850c50fc6d723d1e363e786d37fd9d51e564e07df52ad5e8a86d4"}, - {file = "gevent-23.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a51a8e3cdaa6901e47d56f84cb5f92b1bf3deea920bce69cf7a245df16159ac"}, - {file = "gevent-23.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1dba07207b15b371e50372369edf256a142cb5cdf8599849cbf8660327efa06"}, - {file = "gevent-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:34086bcc1252ae41e1cb81cf13c4a7678031595c12f4e9a1c3d0ab433f20826a"}, - {file = "gevent-23.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5da07d65dfa23fe419c37cea110bf951b42af6bf3a1fff244043a75c9185dbd5"}, - {file = "gevent-23.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4d7be3352126458cc818309ca6a3b678c209b1ae33e56b6975c6a8309f2068"}, - {file = "gevent-23.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76ca6f893953ab898ebbff5d772103318a85044e55d0bad401d6b49d71bb76e7"}, - {file = "gevent-23.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aeb1511cf0786152af741c47ee462dac81b57bbd1fbbe08ab562b6c8c9ad75ed"}, - {file = "gevent-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919423e803939726c99ab2d29ea46b8676af549cee72d263f2b24758ec607b2c"}, - {file = "gevent-23.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cea93f4f77badbddc711620cca164ad75c74056603908e621a5ba1b97adbc39c"}, - {file = "gevent-23.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dec7b08daf08385fb281b81ec2e7e703243975d867f40ae0a8a3e30b380eb9ea"}, - {file = "gevent-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f522b6b015f1bfa9d8d3716ddffb23e3d4a8933df3e4ebf0a29a65a9fa74382b"}, - {file = "gevent-23.7.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:746a1e12f280dab07389e6709164b1e1a6caaf50493ea5b1dcaa73cff005174c"}, - {file = "gevent-23.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b230007a665d2cf5cf8878c9f56a2b8bacbdc4fe0235afc5269b71cd00528e5"}, - {file = "gevent-23.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1d2f1e67d04fde47ca2deac89733df28ef3a7ec1d7359a79f57d4778cced16d"}, - {file = "gevent-23.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:debc177e88a8c876cb1a4d974f985d03670177bdc61e1c084a8d525f1a50b12d"}, - {file = "gevent-23.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b3dd449c80814357f6568eb095a2be2421b805d59fa97c65094707e04a181f9"}, - {file = "gevent-23.7.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:769e8811ded08fe7d8b09ad8ebb72d47aecc112411e0726e7296b7ed187ed629"}, - {file = "gevent-23.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11b9bb0bce45170ff992760385a86e6955ccb88dba4a82a64d5ce9459290d8d6"}, - {file = "gevent-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0d76a7848726e0646324a1adc011355dcd91875e7913badd1ada2e5eeb8a6e"}, - {file = "gevent-23.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a226b42cb9a49580ca7729572a4f8289d1fa28cd2529c9f4eed3e14b995d1c9c"}, - {file = "gevent-23.7.0-cp38-cp38-win32.whl", hash = "sha256:1234849b0bc4df560924aa92f7c01ca3f310677735fb508a2b0d7a61bb946916"}, - {file = "gevent-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:a8f62e8d37913512823923e05607a296389aeb50ccca8a271ae7cedb5b17faeb"}, - {file = "gevent-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369241d1a6a3fe3ef4eba454b71e0168026560c5344fc4bc37196867041982ac"}, - {file = "gevent-23.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:94b013587f7c4697d620c129627f7b12d7d9f6e40ab198635891ca2098cd8556"}, - {file = "gevent-23.7.0-cp39-cp39-win32.whl", hash = "sha256:83b6d61a8e9da25edb304ca7fba19ee57bb1ffa801f9df3e668bfed7bb8386cb"}, - {file = "gevent-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:8c284390f0f6d0b5be3bf805fa8e0ae1329065f2b0ac5af5423c67183197deb8"}, - {file = "gevent-23.7.0.tar.gz", hash = "sha256:d0d3630674c1b344b256a298ab1ff43220f840b12af768131b5d74e485924237"}, + {file = "gevent-23.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae"}, + {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6"}, + {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7"}, + {file = "gevent-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e"}, + {file = "gevent-23.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7"}, + {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71"}, + {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e"}, + {file = "gevent-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a"}, + {file = "gevent-23.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599"}, + {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"}, + {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d"}, + {file = "gevent-23.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1"}, + {file = "gevent-23.9.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe"}, + {file = "gevent-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5"}, + {file = "gevent-23.9.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397"}, + {file = "gevent-23.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507"}, + {file = "gevent-23.9.1-cp38-cp38-win32.whl", hash = "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a"}, + {file = "gevent-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f"}, + {file = "gevent-23.9.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a"}, + {file = "gevent-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653"}, + {file = "gevent-23.9.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd"}, + {file = "gevent-23.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543"}, + {file = "gevent-23.9.1-cp39-cp39-win32.whl", hash = "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2"}, + {file = "gevent-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b"}, + {file = "gevent-23.9.1.tar.gz", hash = "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34"}, ] [package.dependencies] cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} greenlet = [ - {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""}, - {version = ">=3.0a1", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.12\""}, + {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}, + {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""}, ] "zope.event" = "*" "zope.interface" = "*" @@ -872,6 +893,23 @@ certifi = "*" gevent = ">=0.13" six = "*" +[[package]] +name = "googleapis-common-protos" +version = "1.60.0" +description = "Common protobufs used in Google APIs" +optional = true +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.60.0.tar.gz", hash = "sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708"}, + {file = "googleapis_common_protos-1.60.0-py2.py3-none-any.whl", hash = "sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "2.0.2" @@ -947,76 +985,132 @@ test = ["objgraph", "psutil"] [[package]] name = "greenlet" -version = "3.0.0a1" +version = "3.0.0rc3" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.0a1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8dd92fd76a61af2abc8ccad0c6c6069b3c4ebd4727ecc9a7c33aae37651c8c7"}, - {file = "greenlet-3.0.0a1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:889934aa8d72b6bfc46babd1dc4b817a56c97ec0f4a10ae7551fb60ab1f96fae"}, - {file = "greenlet-3.0.0a1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b767930af686551dc96a5eb70af3736709d547ffa275c11a5e820bfb3ae61d8d"}, - {file = "greenlet-3.0.0a1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a1f4d256b81f59ba87bb7a29b9b38b1c018e052dba60a543cb0ddb5062d159"}, - {file = "greenlet-3.0.0a1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3fb459ced6c5e3b2a895f23f1400f93e9b24d85c30fbe2d637d4f7706a1116b"}, - {file = "greenlet-3.0.0a1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:180ec55cb127bc745669eddc9793ffab6e0cf7311e67e1592f183d6ca00d88c1"}, - {file = "greenlet-3.0.0a1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab81f9ff3e3c2ca65e824454214c10985a846cd9bee5f4d04e15cd875d9fe13b"}, - {file = "greenlet-3.0.0a1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:21ebcb570e0d8501457d6a2695a44c5af3b6c2143dc6644ec73574beba067c90"}, - {file = "greenlet-3.0.0a1-cp310-cp310-win_amd64.whl", hash = "sha256:4d0c0ffd732466ff324ced144fad55ed5deca36f6036c1d8f04cec69b084c9d6"}, - {file = "greenlet-3.0.0a1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4a2d6ed0515c05afd5cc435361ced0baabd9ba4536ddfe8ad9a95bcb702c8ce"}, - {file = "greenlet-3.0.0a1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffb9f8969789771e95d3c982a36be81f0adfaa7302a1d56e29f168ca15e284b8"}, - {file = "greenlet-3.0.0a1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3f3568478bc21b85968e8038c4f98f4bf0039a692791bc324b5e0d1522f4b1"}, - {file = "greenlet-3.0.0a1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e160a65cc6023a237be870f2072513747d512a1d018efa083acce0b673cccc0"}, - {file = "greenlet-3.0.0a1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e31d1a33dc9006b278f72cb0aacfe397606c2693aa2fdc0c2f2dcddbad9e0b53"}, - {file = "greenlet-3.0.0a1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00550757fca1b9cbc479f8eb1cf3514dbc0103b3f76eae46341c26ddcca67a9"}, - {file = "greenlet-3.0.0a1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2840187a94e258445e62ff1545e34f0b1a14aef4d0078e5c88246688d2b6515e"}, - {file = "greenlet-3.0.0a1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:271ed380389d2f7e4c1545b6e0837986e62504ab561edbaff05da9c9f3f98f96"}, - {file = "greenlet-3.0.0a1-cp311-cp311-win_amd64.whl", hash = "sha256:4ff2a765f4861fc018827eab4df1992f7508d06c62de5d2fe8a6ac2233d4f1d0"}, - {file = "greenlet-3.0.0a1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:463d63ca5d8c236788284a9a44b9715372a64d5318a6b5eee36815df1ea0ba3d"}, - {file = "greenlet-3.0.0a1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3530c0ec1fc98c43d5b7061781a8c55bd0db44f789f8152e19d9526cbed6021"}, - {file = "greenlet-3.0.0a1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bce5cf2b0f0b29680396c5c98ab39a011bd70f2dfa8b8a6811a69ee6d920cf9f"}, - {file = "greenlet-3.0.0a1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5672082576d0e9f52fa0fa732ff57254d65faeb4a471bc339fe54b58b3e79d2"}, - {file = "greenlet-3.0.0a1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5552d7be37d878e9b6359bbffa0512d857bb9703616a4c0656b49c10739d5971"}, - {file = "greenlet-3.0.0a1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:36cebce1f30964d5672fd956860e7e7b69772da69658d5743cb676b442eeff36"}, - {file = "greenlet-3.0.0a1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:665942d3a954c3e4c976581715f57fb3b86f4cf6bae3ac30b133f8ff777ac6c7"}, - {file = "greenlet-3.0.0a1-cp312-cp312-win_amd64.whl", hash = "sha256:ce70aa089ec589b5d5fab388af9f8c9f9dfe8fe4ad844820a92eb240d8628ddf"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:17503397bf6cbb5e364217143b6150c540020c51a3f6b08f9a20cd67c25e2ca8"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d61bad421c1f496f9fb6114dbd7c30a1dac0e9ff90e9be06f4472cbd8f7a1704"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab71f73001cd15723c4e2ca398f2f48e0a3f584c619eefddb1525e8986e06eb"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f61df4fe07864561f49b45c8bd4d2c42e3f03d2872ed05c844902a58b875028"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c02e514c72e745e49a3ae7e672a1018ba9b68460c21e0361054e956e5d595bc6"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd31ab223e43ac64fd23f8f5dad249addadac2a459f040546200acbf7e84e353"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6aac94ff957b5dea0216af71ab59c602e1b947b394e4f5e878a5a65643090038"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7ba2e5cb119eddbc10874b41047ad99525e39e397f7aef500e6da0d6f46ab91"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-win32.whl", hash = "sha256:ac10196b8cde7a082e4e371ff171407270d3337c8d57ed43030094eb01d9c95c"}, - {file = "greenlet-3.0.0a1-cp37-cp37m-win_amd64.whl", hash = "sha256:0a9dfcadc1d79696e90ccb1275c30ad4ec5fd3d1ab3ae6671286fac78ef33435"}, - {file = "greenlet-3.0.0a1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5942b1d6ba447cff1ec23a21ec525dde2288f00464950bc647f4e0f03bd537d1"}, - {file = "greenlet-3.0.0a1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:450a7e52a515402fd110ba807f1a7d464424bfa703be4effbcb97e1dfbfcc621"}, - {file = "greenlet-3.0.0a1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:df34b52aa50a38d7a79f3abc9fda7e400791447aa0400ed895f275f6d8b0bb1f"}, - {file = "greenlet-3.0.0a1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cda110faee67613fed221f90467003f477088ef1cc84c8fc88537785a5b4de9"}, - {file = "greenlet-3.0.0a1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f313771cb8ee0a04dfdf586b7d4076180d80c94be09049daeea018089b5b957"}, - {file = "greenlet-3.0.0a1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42bfe67824a9b53e73f568f982f0d1d4c7ac0f587d2e702a23f8a7b505d7b7c2"}, - {file = "greenlet-3.0.0a1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0fc20e6e6b298861035a5fc5dcf9fbaa0546318e8bda81112591861a7dcc28f"}, - {file = "greenlet-3.0.0a1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f34ec09702be907727fd479046193725441aaaf7ed4636ca042734f469bb7451"}, - {file = "greenlet-3.0.0a1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:270432cfdd6a50016b8259b3bbf398a3f7c06a06f2c68c7b93e49f53bc193bcf"}, - {file = "greenlet-3.0.0a1-cp38-cp38-win32.whl", hash = "sha256:d47b2e1ad1429da9aa459ef189fbcd8a74ec28a16bc4c3f5f3cf3f88e36535eb"}, - {file = "greenlet-3.0.0a1-cp38-cp38-win_amd64.whl", hash = "sha256:e7b192c3df761d0fdd17c2d42d41c28460f124f5922e8bd524018f1d35610682"}, - {file = "greenlet-3.0.0a1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e20d5e8dc76b73db9280464d6e81bea05e51a99f4d4dd29c5f78dc79f294a5d3"}, - {file = "greenlet-3.0.0a1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:ba94c08321b5d345100fc64eb1ab235f42faf9aabba805cface55ebe677f1c2c"}, - {file = "greenlet-3.0.0a1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:24071eee113d75fedebaeb86264d94f04b5a24e311c5ba3e8003c07d00112a7e"}, - {file = "greenlet-3.0.0a1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:585810056a8adacd3152945ebfcd25deb58335d41f16ae4e0f3d768918957f9a"}, - {file = "greenlet-3.0.0a1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3a99f890f2cc5535e1b3a90049c6ca9ff9da9ec251cc130c8d269997f9d32ee"}, - {file = "greenlet-3.0.0a1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c355c99be5bb23e85d899b059a4f22fdf8a0741c57e7029425ee63eb436f689"}, - {file = "greenlet-3.0.0a1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dde0ab052c7a1deee8d13d72c37f2afecee30ebdf6eb139790157eaddf04dd61"}, - {file = "greenlet-3.0.0a1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ed0f4fad4c3656e34d20323a789b6a2d210a6bb82647d9c86dded372f55c58a1"}, - {file = "greenlet-3.0.0a1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53abf19b7dc62795c67b8d0a3d8ef866db166b21017632fff2624cf8fbf3481c"}, - {file = "greenlet-3.0.0a1-cp39-cp39-win32.whl", hash = "sha256:2fcf7af83516db35af3d0ed5d182dea8585eddd891977adff1b74212f4bfd2fd"}, - {file = "greenlet-3.0.0a1-cp39-cp39-win_amd64.whl", hash = "sha256:68368e908f14887fb202a81960bfbe3a02d97e6d3fa62b821556463084ffb131"}, - {file = "greenlet-3.0.0a1.tar.gz", hash = "sha256:1bd4ea36f0aeb14ca335e0c9594a5aaefa1ac4e2db7d86ba38f0be96166b3102"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a2affddff9b2f846f40799673e41b29f0500582415c860fca8f146858e9de1a"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd00046dfd00767fce18f9933658d126652a500caf7af9dbfbd43818e4b484c2"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e787b00002cef3b98c7cf700fb85c2c01b0d202b1c6731706e5baa4b3325aa1e"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ffc7538bc66766a8b551888903d415773481c4bd13560a4fb24887222e3cc9"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dde5deb355b34bbf44b15789e27c56862f51f417207be49eedc58fce34681fe6"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fb703a102a02361a0cc6a3d9a7958e1584fdeb536bd37ca9aca529d3356bedd"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f8661d14d3e07f2ceeb850e4cbcc7114bdf90a8dc82d63d37b08a50bb6955a77"}, + {file = "greenlet-3.0.0rc3-cp310-cp310-win_amd64.whl", hash = "sha256:997456b74efee91ceeb39d63818909da5dbb712a07f7742f4378986ac3473463"}, + {file = "greenlet-3.0.0rc3-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:d3cd3957af8cec1fcfd87d92ca71b7d434d798036e14ae878f9ab1e07d99da0d"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:895b689fc52a5bc402f8d624705110df5c265b1410ffe8e0769a66db9d2e7851"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a84a88422b5a0360fae57ad6b3b20fc17c9462880929810b0a26ee43aa05982e"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d79cf299ba1996d8a4f133b317e709a0a3ce87181308280e40664e12cb512c54"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9789aea735004eba559c7919a73a3b475d0c28e2c1e9de464c6bc761bf69f4"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66790e1537382e53bce64de3a695d1b12a04b00104df45f7ef472a10561936c2"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:68349987bf2ce274953f9f9b28458869bd8770a0c5461e1ef91d8107b1bae361"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30ffaa6c020a615c8f4be3abfc6029982fda026a3bf9a6dc7205afb033251506"}, + {file = "greenlet-3.0.0rc3-cp311-cp311-win_amd64.whl", hash = "sha256:864619b058f573058cd77f6944cf63d7f42157fe30be494798721bd8ac256d7b"}, + {file = "greenlet-3.0.0rc3-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:7c887ecb55374d585d71ff8f9d07c137637694e88fa2b5d5b1450a05ece62ae9"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:686821157368c1c4ef53aa68e6801280010da92ab0e4265dad37003341fca6a1"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:242d56d2d5f6859f0f086ce62555a2c692c8053c89721d41fead5e1e8dffdb36"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81d653ae6c64b85ce4c7bccbea7b630de8799da751b73e55b4c68875b6eb19d6"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beeb5cfbd8f3792c37db4e3c5665aa750d78bbdabe758161a34e7dfe27075e69"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30daee988fc83078b016fa95a7a1f78a7c86534a44238748b9748675814eb1dc"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:977898b8c24159467c66ed1a8f62aacd33f3d85f852cf413d0d2e2a87a6b3091"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:097a2f75c79c3fa76fea2e5d48a637233722fe72a5ebb1213c55f0a0898f481c"}, + {file = "greenlet-3.0.0rc3-cp312-cp312-win_amd64.whl", hash = "sha256:5770d43b08dfa10f4460c1bd51f8c80e6f2c47611054e9fb80d4d7976d07e560"}, + {file = "greenlet-3.0.0rc3-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:f33e7ff85775cb0ec6abb0950ffc631960bae5a203da38166fc3dfde826e0d0a"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f6d1ce31a1db5102a42b4afa609af330edfd8a81d10faba3e47ae33a07cbdf"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e651fa59263f7ff1d4657b086c48cfe7e26db2a36e2d74069f3b5aeab478e6"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef7c6e49a9a020d56349c6a769352709bfbe35d3ee7f98bd5efcac6cedbdc162"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5585bf8d1d2d3712010ee74988c2ed85c54b127b97f2778fbdcc5b3ea8e801a2"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c706041cd92e1b9d2b602eaa31e94aad14453bdbf186ce77530f25167c173a0e"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:37213e72058d2e6231d18417adc63c698c040fbb47dc59a3fd633973214ab1ab"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95bc6ec8dd73f8f36e9dfc61a7fa5a2819d1cd52d0bfdb70a43434d6b2aeb239"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-win32.whl", hash = "sha256:e83c4c7a0814dcfd7e2fe4b74a371f3ce489b62ff02e81d0c5cacc8ba4750395"}, + {file = "greenlet-3.0.0rc3-cp37-cp37m-win_amd64.whl", hash = "sha256:4c35608918f331256be199d3712552fa8a1d12f87ac171a86a31488c60d298f5"}, + {file = "greenlet-3.0.0rc3-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:215bdb33e85fd89fe55f9984dc6f0a96b5774bace663e1a6d051e65d66170ef8"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69db00f775ed9d233f53ef67c66ea40a7add0c0929eb528f633982e27595dd37"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fcc7162944c2fedfb2253ca2171267e016a3b065c73369d0d4a27f601e7f162"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c0082d7b83312c59127811367089f812f8f1386fad7e8cf321fd732b4a6ace6"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66f1131c17dba115ea7cb3b257b6751b3c4cfd324f2121447e2483f57abbbf3c"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0c5508582339090b99e2863a157fc2708ab9c8b5cd21619bdcb04edcdc6c28d"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f1c9ad8d6500f7b142a94054281d9628bc8652a14b0923d02e0dfd87392fbc74"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bd586284bbf18ca3068e1fcc67ef54538e1bb74cb605ebdac9e62048237839f5"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-win32.whl", hash = "sha256:1c16f1bbaf9c75dfac3e52bb778d2fd6099fd5aa59fafa678eca5853eedd99ec"}, + {file = "greenlet-3.0.0rc3-cp38-cp38-win_amd64.whl", hash = "sha256:e388ceb55b8f3f388afea4d4a17a64b619040f0e8e9fa3e17e7c34f4d0fbe103"}, + {file = "greenlet-3.0.0rc3-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:68bd35ad9f99df0ef18836fd0fb34278dca6b3350bdcf1e8809822fc4f57a82e"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:256b748fc1e6c97012f217e0a403116cb0dd369bf1cff51c07a9c52899d4a8a8"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4936e6e051932848c4b237a874da8dbb47bfbb5ae5104497fb78c4f4cf184989"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a31b3a4bd10c540a7eb7d4b43d16779813ca4c79b615ed6d4ebf0e5a782d9fa0"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6f8253fdb00e74b928ab5d04f88ddbc8beb0cc26aa978bb4a12c1513166d481"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a7831d04a0f8a14645c010e3fb3fa36b8d2df304dd837948427ccfec2524ddf"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae84d2f2658990f29df4ea753061b25c337bd70f805128af328098e5b8afc454"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cd51cc2528a2985f3bc0564c1b1ce5b2e6fa4ee9924503010428256fa95b0e3c"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-win32.whl", hash = "sha256:e8698f341e78dd0f149511929e92d1507cc26647f047db13987169d244db10fb"}, + {file = "greenlet-3.0.0rc3-cp39-cp39-win_amd64.whl", hash = "sha256:f059457db4e2ae4a4fdae455453c5e5765aa08efcb804e2a106c69c31bd438ba"}, + {file = "greenlet-3.0.0rc3-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:c80cac2776df3dd08f27b7338f467a62ee6cb29668a8f4f408b8da1f981aae9e"}, + {file = "greenlet-3.0.0rc3.tar.gz", hash = "sha256:0df5c2ad154f457fd372e39723493b3df519330a4c1bff3ca901be66130f379b"}, ] [package.extras] docs = ["Sphinx"] test = ["objgraph", "psutil"] +[[package]] +name = "grpcio" +version = "1.58.0" +description = "HTTP/2-based RPC framework" +optional = true +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.58.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:3e6bebf1dfdbeb22afd95650e4f019219fef3ab86d3fca8ebade52e4bc39389a"}, + {file = "grpcio-1.58.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:cde11577d5b6fd73a00e6bfa3cf5f428f3f33c2d2878982369b5372bbc4acc60"}, + {file = "grpcio-1.58.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a2d67ff99e70e86b2be46c1017ae40b4840d09467d5455b2708de6d4c127e143"}, + {file = "grpcio-1.58.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ed979b273a81de36fc9c6716d9fb09dd3443efa18dcc8652501df11da9583e9"}, + {file = "grpcio-1.58.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:458899d2ebd55d5ca2350fd3826dfd8fcb11fe0f79828ae75e2b1e6051d50a29"}, + {file = "grpcio-1.58.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc7ffef430b80345729ff0a6825e9d96ac87efe39216e87ac58c6c4ef400de93"}, + {file = "grpcio-1.58.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5b23d75e5173faa3d1296a7bedffb25afd2fddb607ef292dfc651490c7b53c3d"}, + {file = "grpcio-1.58.0-cp310-cp310-win32.whl", hash = "sha256:fad9295fe02455d4f158ad72c90ef8b4bcaadfdb5efb5795f7ab0786ad67dd58"}, + {file = "grpcio-1.58.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc325fed4d074367bebd465a20763586e5e1ed5b943e9d8bc7c162b1f44fd602"}, + {file = "grpcio-1.58.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:652978551af02373a5a313e07bfef368f406b5929cf2d50fa7e4027f913dbdb4"}, + {file = "grpcio-1.58.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:9f13a171281ebb4d7b1ba9f06574bce2455dcd3f2f6d1fbe0fd0d84615c74045"}, + {file = "grpcio-1.58.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8774219e21b05f750eef8adc416e9431cf31b98f6ce9def288e4cea1548cbd22"}, + {file = "grpcio-1.58.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09206106848462763f7f273ca93d2d2d4d26cab475089e0de830bb76be04e9e8"}, + {file = "grpcio-1.58.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62831d5e251dd7561d9d9e83a0b8655084b2a1f8ea91e4bd6b3cedfefd32c9d2"}, + {file = "grpcio-1.58.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:212f38c6a156862098f6bdc9a79bf850760a751d259d8f8f249fc6d645105855"}, + {file = "grpcio-1.58.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4b12754af201bb993e6e2efd7812085ddaaef21d0a6f0ff128b97de1ef55aa4a"}, + {file = "grpcio-1.58.0-cp311-cp311-win32.whl", hash = "sha256:3886b4d56bd4afeac518dbc05933926198aa967a7d1d237a318e6fbc47141577"}, + {file = "grpcio-1.58.0-cp311-cp311-win_amd64.whl", hash = "sha256:002f228d197fea12797a14e152447044e14fb4fdb2eb5d6cfa496f29ddbf79ef"}, + {file = "grpcio-1.58.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:b5e8db0aff0a4819946215f156bd722b6f6c8320eb8419567ffc74850c9fd205"}, + {file = "grpcio-1.58.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:201e550b7e2ede113b63e718e7ece93cef5b0fbf3c45e8fe4541a5a4305acd15"}, + {file = "grpcio-1.58.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:d79b660681eb9bc66cc7cbf78d1b1b9e335ee56f6ea1755d34a31108b80bd3c8"}, + {file = "grpcio-1.58.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ef8d4a76d2c7d8065aba829f8d0bc0055495c998dce1964ca5b302d02514fb3"}, + {file = "grpcio-1.58.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cba491c638c76d3dc6c191d9c75041ca5b8f5c6de4b8327ecdcab527f130bb4"}, + {file = "grpcio-1.58.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6801ff6652ecd2aae08ef994a3e49ff53de29e69e9cd0fd604a79ae4e545a95c"}, + {file = "grpcio-1.58.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:24edec346e69e672daf12b2c88e95c6f737f3792d08866101d8c5f34370c54fd"}, + {file = "grpcio-1.58.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7e473a7abad9af48e3ab5f3b5d237d18208024d28ead65a459bd720401bd2f8f"}, + {file = "grpcio-1.58.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:4891bbb4bba58acd1d620759b3be11245bfe715eb67a4864c8937b855b7ed7fa"}, + {file = "grpcio-1.58.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:e9f995a8a421405958ff30599b4d0eec244f28edc760de82f0412c71c61763d2"}, + {file = "grpcio-1.58.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2f85f87e2f087d9f632c085b37440a3169fda9cdde80cb84057c2fc292f8cbdf"}, + {file = "grpcio-1.58.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb6b92036ff312d5b4182fa72e8735d17aceca74d0d908a7f08e375456f03e07"}, + {file = "grpcio-1.58.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d81c2b2b24c32139dd2536972f1060678c6b9fbd106842a9fcdecf07b233eccd"}, + {file = "grpcio-1.58.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fbcecb6aedd5c1891db1d70efbfbdc126c986645b5dd616a045c07d6bd2dfa86"}, + {file = "grpcio-1.58.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92ae871a902cf19833328bd6498ec007b265aabf2fda845ab5bd10abcaf4c8c6"}, + {file = "grpcio-1.58.0-cp38-cp38-win32.whl", hash = "sha256:dc72e04620d49d3007771c0e0348deb23ca341c0245d610605dddb4ac65a37cb"}, + {file = "grpcio-1.58.0-cp38-cp38-win_amd64.whl", hash = "sha256:1c1c5238c6072470c7f1614bf7c774ffde6b346a100521de9ce791d1e4453afe"}, + {file = "grpcio-1.58.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fe643af248442221db027da43ed43e53b73e11f40c9043738de9a2b4b6ca7697"}, + {file = "grpcio-1.58.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:128eb1f8e70676d05b1b0c8e6600320fc222b3f8c985a92224248b1367122188"}, + {file = "grpcio-1.58.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:039003a5e0ae7d41c86c768ef8b3ee2c558aa0a23cf04bf3c23567f37befa092"}, + {file = "grpcio-1.58.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f061722cad3f9aabb3fbb27f3484ec9d4667b7328d1a7800c3c691a98f16bb0"}, + {file = "grpcio-1.58.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0af11938acf8cd4cf815c46156bcde36fa5850518120920d52620cc3ec1830"}, + {file = "grpcio-1.58.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d4cef77ad2fed42b1ba9143465856d7e737279854e444925d5ba45fc1f3ba727"}, + {file = "grpcio-1.58.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24765a627eb4d9288ace32d5104161c3654128fe27f2808ecd6e9b0cfa7fc8b9"}, + {file = "grpcio-1.58.0-cp39-cp39-win32.whl", hash = "sha256:f0241f7eb0d2303a545136c59bc565a35c4fc3b924ccbd69cb482f4828d6f31c"}, + {file = "grpcio-1.58.0-cp39-cp39-win_amd64.whl", hash = "sha256:dcfba7befe3a55dab6fe1eb7fc9359dc0c7f7272b30a70ae0af5d5b063842f28"}, + {file = "grpcio-1.58.0.tar.gz", hash = "sha256:532410c51ccd851b706d1fbc00a87be0f5312bd6f8e5dbf89d4e99c7f79d7499"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.58.0)"] + [[package]] name = "h11" version = "0.14.0" @@ -1435,19 +1529,82 @@ files = [ [[package]] name = "opentelemetry-api" -version = "1.19.0" +version = "1.20.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.7" files = [ - {file = "opentelemetry_api-1.19.0-py3-none-any.whl", hash = "sha256:dcd2a0ad34b691964947e1d50f9e8c415c32827a1d87f0459a72deb9afdf5597"}, - {file = "opentelemetry_api-1.19.0.tar.gz", hash = "sha256:db374fb5bea00f3c7aa290f5d94cea50b659e6ea9343384c5f6c2bb5d5e8db65"}, + {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-exporter-otlp-proto-common" +version = "1.20.0" +description = "OpenTelemetry Protobuf encoding" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.20.0-py3-none-any.whl", hash = "sha256:dd63209b40702636ab6ae76a06b401b646ad7b008a906ecb41222d4af24fbdef"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.20.0.tar.gz", hash = "sha256:df60c681bd61812e50b3a39a7a1afeeb6d4066117583249fcc262269374e7a49"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +opentelemetry-proto = "1.20.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.20.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.20.0-py3-none-any.whl", hash = "sha256:7c3f066065891b56348ba2c7f9df6ec635a712841cae0a36f2f6a81642ae7dec"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.20.0.tar.gz", hash = "sha256:6c06d43c3771bda1795226e327722b4b980fa1ca1ec9e985f2ef3e29795bdd52"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.20.0" +opentelemetry-proto = "1.20.0" +opentelemetry-sdk = ">=1.20.0,<1.21.0" + +[package.extras] +test = ["pytest-grpc"] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.20.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.20.0-py3-none-any.whl", hash = "sha256:03f6e768ad25f1c3a9586e8c695db4a4adf978f8546a1285fa962e16bfbb0bd6"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.20.0.tar.gz", hash = "sha256:500f42821420fdf0759193d6438edc0f4e984a83e14c08a23023c06a188861b4"}, +] + +[package.dependencies] +backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.20.0" +opentelemetry-proto = "1.20.0" +opentelemetry-sdk = ">=1.20.0,<1.21.0" +requests = ">=2.7,<3.0" + +[package.extras] +test = ["responses (==0.22.0)"] + [[package]] name = "opentelemetry-exporter-prometheus" version = "1.12.0rc1" @@ -1464,31 +1621,45 @@ opentelemetry-api = ">=1.10.0" opentelemetry-sdk = ">=1.10.0" prometheus-client = ">=0.5.0,<1.0.0" +[[package]] +name = "opentelemetry-proto" +version = "1.20.0" +description = "OpenTelemetry Python Proto" +optional = true +python-versions = ">=3.7" +files = [ + {file = "opentelemetry_proto-1.20.0-py3-none-any.whl", hash = "sha256:512c3d2c6864fb7547a69577c3907348e6c985b7a204533563cb4c4c5046203b"}, + {file = "opentelemetry_proto-1.20.0.tar.gz", hash = "sha256:cf01f49b3072ee57468bccb1a4f93bdb55411f4512d0ac3f97c5c04c0040b5a2"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + [[package]] name = "opentelemetry-sdk" -version = "1.19.0" +version = "1.20.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.7" files = [ - {file = "opentelemetry_sdk-1.19.0-py3-none-any.whl", hash = "sha256:bb67ad676b1bc671766a40d7fc9d9563854c186fa11f0dc8fa2284e004bd4263"}, - {file = "opentelemetry_sdk-1.19.0.tar.gz", hash = "sha256:765928956262c7a7766eaba27127b543fb40ef710499cad075f261f52163a87f"}, + {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.19.0" -opentelemetry-semantic-conventions = "0.40b0" +opentelemetry-api = "1.20.0" +opentelemetry-semantic-conventions = "0.41b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.40b0" +version = "0.41b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.7" files = [ - {file = "opentelemetry_semantic_conventions-0.40b0-py3-none-any.whl", hash = "sha256:7ebbaf86755a0948902e68637e3ae516c50222c30455e55af154ad3ffe283839"}, - {file = "opentelemetry_semantic_conventions-0.40b0.tar.gz", hash = "sha256:5a7a491873b15ab7c4907bbfd8737645cc87ca55a0a326c1755d1b928d8a0fae"}, + {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]] @@ -1571,6 +1742,28 @@ files = [ [package.extras] twisted = ["twisted"] +[[package]] +name = "protobuf" +version = "4.24.3" +description = "" +optional = true +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.3-cp310-abi3-win32.whl", hash = "sha256:20651f11b6adc70c0f29efbe8f4a94a74caf61b6200472a9aea6e19898f9fcf4"}, + {file = "protobuf-4.24.3-cp310-abi3-win_amd64.whl", hash = "sha256:3d42e9e4796a811478c783ef63dc85b5a104b44aaaca85d4864d5b886e4b05e3"}, + {file = "protobuf-4.24.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959"}, + {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675"}, + {file = "protobuf-4.24.3-cp37-cp37m-win32.whl", hash = "sha256:1b182c7181a2891e8f7f3a1b5242e4ec54d1f42582485a896e4de81aa17540c2"}, + {file = "protobuf-4.24.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b0271a701e6782880d65a308ba42bc43874dabd1a0a0f41f72d2dac3b57f8e76"}, + {file = "protobuf-4.24.3-cp38-cp38-win32.whl", hash = "sha256:e29d79c913f17a60cf17c626f1041e5288e9885c8579832580209de8b75f2a52"}, + {file = "protobuf-4.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:067f750169bc644da2e1ef18c785e85071b7c296f14ac53e0900e605da588719"}, + {file = "protobuf-4.24.3-cp39-cp39-win32.whl", hash = "sha256:2da777d34b4f4f7613cdf85c70eb9a90b1fbef9d36ae4a0ccfe014b0b07906f1"}, + {file = "protobuf-4.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:f631bb982c5478e0c1c70eab383af74a84be66945ebf5dd6b06fc90079668d0b"}, + {file = "protobuf-4.24.3-py3-none-any.whl", hash = "sha256:f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a"}, + {file = "protobuf-4.24.3.tar.gz", hash = "sha256:12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d"}, +] + [[package]] name = "psutil" version = "5.9.5" @@ -1690,13 +1883,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -2015,19 +2208,19 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {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,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +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]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "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" @@ -2413,7 +2606,11 @@ docs = ["Sphinx", "repoze.sphinx.autointerface"] test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +[extras] +exporter-otlp-proto-grpc = ["opentelemetry-exporter-otlp-proto-grpc"] +exporter-otlp-proto-http = ["opentelemetry-exporter-otlp-proto-http"] + [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b50f133668cffe99e6c147dba74e91b335a7258ce4d0d8028c084c2374567871" +content-hash = "4ad70b49c3a6a8bced1e24947f226e37bad2ffaae7ac58fe759944d5314dc673" diff --git a/pyproject.toml b/pyproject.toml index 018db0e..b68435c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,30 +8,36 @@ readme = "README.md" repository = "https://github.com/autometrics-dev/autometrics-py" homepage = "https://github.com/autometrics-dev/autometrics-py" keywords = [ - "metrics", - "telemetry", - "prometheus", - "monitoring", - "observability", - "instrumentation", - "tracing", + "metrics", + "telemetry", + "prometheus", + "monitoring", + "observability", + "instrumentation", + "tracing", ] classifiers = [ - "Topic :: Software Development :: Build Tools", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: System :: Monitoring", - "Typing :: Typed" + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Monitoring", + "Typing :: Typed", ] packages = [{ include = "autometrics", from = "src" }] [tool.poetry.dependencies] opentelemetry-exporter-prometheus = "^1.12.0rc1" +opentelemetry-exporter-otlp-proto-http = { version = "^1.20.0", optional = true } +opentelemetry-exporter-otlp-proto-grpc = { version = "^1.20.0", optional = true } opentelemetry-sdk = "^1.17.0" prometheus-client = "^0.16.0 || ^0.17.0" python = "^3.8" python-dotenv = "^1.0.0" typing-extensions = "^4.5.0" +[tool.poetry.extras] +exporter-otlp-proto-http = ["opentelemetry-exporter-otlp-proto-http"] +exporter-otlp-proto-grpc = ["opentelemetry-exporter-otlp-proto-grpc"] + [tool.poetry.group.dev] optional = true @@ -46,9 +52,7 @@ enable_incomplete_feature = "Unpack" # Which at the time of writing is a line that states ignore types: # `# type: ignore` [[tool.mypy.overrides]] -module = [ - "opentelemetry.attributes", -] +module = ["opentelemetry.attributes"] follow_imports = "skip" [tool.poetry.group.dev.dependencies] @@ -102,7 +106,6 @@ locust = "^2.15.1" django-stubs = "4.2.3" - [tool.poetry.group.development.dependencies] types-requests = "^2.31.0.2" django-stubs = "^4.2.3" diff --git a/src/autometrics/decorator.py b/src/autometrics/decorator.py index 03dfb1d..33544e9 100644 --- a/src/autometrics/decorator.py +++ b/src/autometrics/decorator.py @@ -86,14 +86,14 @@ def track_start(function: str, module: str): ) def track_result_ok( - start_time: float, + duration: float, function: str, module: str, caller_module: str, caller_function: str, ): get_tracker().finish( - start_time, + duration, function=function, module=module, caller_module=caller_module, @@ -104,14 +104,14 @@ def track_result_ok( ) def track_result_error( - start_time: float, + duration: float, function: str, module: str, caller_module: str, caller_function: str, ): get_tracker().finish( - start_time, + duration, function=function, module=module, caller_module=caller_module, @@ -130,11 +130,11 @@ def sync_decorator(func: Callable[Params, R]) -> Callable[Params, R]: @wraps(func) def sync_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: - start_time = time.time() caller_module = caller_module_var.get() caller_function = caller_function_var.get() context_token_module: Optional[Token] = None context_token_function: Optional[Token] = None + start_time = time.time() try: context_token_module = caller_module_var.set(module_name) @@ -142,9 +142,10 @@ def sync_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: if track_concurrency: track_start(module=module_name, function=func_name) result = func(*args, **kwds) + duration = time.time() - start_time if record_error_if and record_error_if(result): track_result_error( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -152,7 +153,7 @@ def sync_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) else: track_result_ok( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -160,9 +161,10 @@ def sync_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) except Exception as exception: + duration = time.time() - start_time if record_success_if and record_success_if(exception): track_result_ok( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -170,7 +172,7 @@ def sync_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) else: track_result_error( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -201,11 +203,11 @@ def async_decorator( @wraps(func) async def async_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: - start_time = time.time() caller_module = caller_module_var.get() caller_function = caller_function_var.get() context_token_module: Optional[Token] = None context_token_function: Optional[Token] = None + start_time = time.time() try: context_token_module = caller_module_var.set(module_name) @@ -213,9 +215,10 @@ async def async_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: if track_concurrency: track_start(module=module_name, function=func_name) result = await func(*args, **kwds) + duration = time.time() - start_time if record_error_if and record_error_if(result): track_result_error( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -223,7 +226,7 @@ async def async_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) else: track_result_ok( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -231,9 +234,10 @@ async def async_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) except Exception as exception: + duration = time.time() - start_time if record_success_if and record_success_if(exception): track_result_ok( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, @@ -241,7 +245,7 @@ async def async_wrapper(*args: Params.args, **kwds: Params.kwargs) -> R: ) else: track_result_error( - start_time, + duration, function=func_name, module=module_name, caller_module=caller_module, diff --git a/src/autometrics/tracker/exemplar.py b/src/autometrics/exemplar.py similarity index 100% rename from src/autometrics/tracker/exemplar.py rename to src/autometrics/exemplar.py diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py new file mode 100644 index 0000000..e32e634 --- /dev/null +++ b/src/autometrics/exposition.py @@ -0,0 +1,102 @@ +from typing import cast, Literal, TypedDict, Optional, Union +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + MetricReader, + PeriodicExportingMetricReader, +) +from opentelemetry.exporter.prometheus import PrometheusMetricReader + +# GRPC is optional so we'll only type it if it's available +try: + from grpc import ChannelCredentials +except ImportError: + ChannelCredentials = None + + +class OTLPExporterOptions(TypedDict): + """Configuration for OTLP exporters.""" + + type: Literal["otlp-proto-http", "otlp-proto-grpc"] + endpoint: str + insecure: bool + headers: dict[str, str] + credentials: ChannelCredentials + push_interval: int + timeout: int + preferred_temporality: dict[type, AggregationTemporality] + + +class OTELPrometheusExporterOptions(TypedDict): + type: Literal["otel-prometheus"] + prefix: str + + +class PrometheusExporterOptions(TypedDict): + """Configuration for Prometheus exporter.""" + + type: Literal["prometheus-client"] + address: str + port: int + prefix: str + + +ExporterOptions = Union[ + OTLPExporterOptions, + OTELPrometheusExporterOptions, + PrometheusExporterOptions, + MetricReader, +] + + +def get_exporter(config: ExporterOptions) -> MetricReader: + if isinstance(config, MetricReader): + return config + if config["type"] == "otel-prometheus": + config = cast(OTELPrometheusExporterOptions, config) + return PrometheusMetricReader(config.get("prefix", "")) + if config["type"] == "otlp-proto-http": + config = cast(OTLPExporterOptions, config) + try: + from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + OTLPMetricExporter as OTLPHTTPMetricExporter, + ) + + http_exporter = OTLPHTTPMetricExporter( + endpoint=config.get("endpoint", None), + headers=config.get("headers", None), + timeout=config.get("timeout", None), + preferred_temporality=config.get("preferred_temporality", None), + ) + http_reader = PeriodicExportingMetricReader( + http_exporter, + export_interval_millis=config.get("push_interval", None), + export_timeout_millis=config.get("timeout", None), + ) + return http_reader + except ImportError: + raise ImportError("OTLP exporter (HTTP) not installed") + if config["type"] == "otlp-proto-grpc": + config = cast(OTLPExporterOptions, config) + try: + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + OTLPMetricExporter as OTLPGRPCMetricExporter, + ) + + grpc_exporter = OTLPGRPCMetricExporter( + endpoint=config.get("endpoint", None), + insecure=config.get("insecure", None), + credentials=config.get("credentials", None), + headers=config.get("headers", None), + timeout=config.get("timeout", None), + preferred_temporality=config.get("preferred_temporality", None), + ) + grpc_reader = PeriodicExportingMetricReader( + grpc_exporter, + export_interval_millis=config.get("push_interval", None), + export_timeout_millis=config.get("timeout", None), + ) + return grpc_reader + except ImportError: + raise ImportError("OTLP exporter (GRPC) not installed") + else: + raise ValueError("Invalid exporter type") diff --git a/src/autometrics/init.py b/src/autometrics/init.py index afeca45..7189fbe 100644 --- a/src/autometrics/init.py +++ b/src/autometrics/init.py @@ -1,11 +1,24 @@ from typing_extensions import Unpack + +from .tracker import init_tracker, get_tracker +from .tracker.temporary import TemporaryTracker from .settings import AutometricsOptions, init_settings -from .tracker import set_tracker, get_tracker_type + +has_inited = False def init(**kwargs: Unpack[AutometricsOptions]): - """Optional initialization function to be used instead of setting environment variables. You can override settings by calling init with the new settings.""" - init_settings(**kwargs) - tracker_type = get_tracker_type() - set_tracker(tracker_type) + """Initialization function to be used instead of setting environment variables. You can override settings by calling init with the new settings.""" + global has_inited + if has_inited: + print("Warning: init() has already been called. This call will be ignored.") + return + has_inited = True + temp_tracker = get_tracker() + if not isinstance(temp_tracker, TemporaryTracker): + print("Expected tracker to be TemporaryTracker. This call will be ignored.") + return + settings = init_settings(**kwargs) + tracker = init_tracker(settings["tracker"], settings) + temp_tracker.replay_queue(tracker) diff --git a/src/autometrics/settings.py b/src/autometrics/settings.py index 6cc24a9..4872507 100644 --- a/src/autometrics/settings.py +++ b/src/autometrics/settings.py @@ -1,8 +1,11 @@ import os +from opentelemetry.sdk.metrics.export import MetricReader from typing import List, TypedDict, Optional from typing_extensions import Unpack +from .tracker.types import TrackerType +from .exposition import ExporterOptions from .objectives import ObjectiveLatency @@ -10,7 +13,8 @@ class AutometricsSettings(TypedDict): """Settings for autometrics.""" histogram_buckets: List[float] - tracker: str + tracker: TrackerType + exporter: Optional[ExporterOptions] enable_exemplars: bool service_name: str commit: str @@ -23,6 +27,7 @@ class AutometricsOptions(TypedDict, total=False): histogram_buckets: List[float] tracker: str + exporter: ExporterOptions enable_exemplars: bool service_name: str commit: str @@ -39,15 +44,23 @@ def get_objective_boundaries(): def init_settings(**overrides: Unpack[AutometricsOptions]) -> AutometricsSettings: + tracker_setting = ( + overrides.get("tracker") or os.getenv("AUTOMETRICS_TRACKER") or "opentelemetry" + ) + tracker_type = ( + TrackerType.PROMETHEUS + if tracker_setting.lower() == "prometheus" + else TrackerType.OPENTELEMETRY + ) + config: AutometricsSettings = { "histogram_buckets": overrides.get("histogram_buckets") or get_objective_boundaries(), - "tracker": overrides.get("tracker") - or os.getenv("AUTOMETRICS_TRACKER") - or "opentelemetry", "enable_exemplars": overrides.get( "enable_exemplars", os.getenv("AUTOMETRICS_EXEMPLARS") == "true" ), + "tracker": tracker_type, + "exporter": overrides.get("exporter"), "service_name": overrides.get( "service_name", os.getenv( @@ -63,6 +76,8 @@ def init_settings(**overrides: Unpack[AutometricsOptions]) -> AutometricsSetting ), "version": overrides.get("version", os.getenv("AUTOMETRICS_VERSION", "")), } + validate_settings(config) + global settings settings = config return settings @@ -72,5 +87,28 @@ def get_settings() -> AutometricsSettings: """Get the current settings.""" global settings if settings is None: - return init_settings() + settings = init_settings() return settings + + +def validate_settings(settings: AutometricsSettings): + """Ensure that the settings are valid. For example, we don't support Prometheus exporter with OpenTelemetry tracker.""" + if settings["exporter"]: + if settings["tracker"] == TrackerType.OPENTELEMETRY and not isinstance( + settings["exporter"], MetricReader + ): + if settings["exporter"]["type"] == "prometheus": + raise ValueError( + "Prometheus exporter is not supported with OpenTelemetry tracker" + ) + if settings["tracker"] == TrackerType.PROMETHEUS: + if isinstance(settings["exporter"], MetricReader): + raise ValueError( + "OpenTelemetry exporter is not supported with Prometheus tracker" + ) + if ( + settings["exporter"]["type"] in ["otlp-proto-http", "otlp-proto-grpc"] + ) or (isinstance(settings["exporter"], MetricReader)): + raise ValueError( + "OTLP exporter is not supported with Prometheus tracker" + ) diff --git a/src/autometrics/tracker/__init__.py b/src/autometrics/tracker/__init__.py index f3e5944..9e0f53f 100644 --- a/src/autometrics/tracker/__init__.py +++ b/src/autometrics/tracker/__init__.py @@ -1 +1,2 @@ from .tracker import * +from .types import * diff --git a/src/autometrics/tracker/opentelemetry.py b/src/autometrics/tracker/opentelemetry.py index 57804c3..5c8f662 100644 --- a/src/autometrics/tracker/opentelemetry.py +++ b/src/autometrics/tracker/opentelemetry.py @@ -10,10 +10,11 @@ ) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation +from opentelemetry.sdk.metrics.export import MetricReader from opentelemetry.exporter.prometheus import PrometheusMetricReader -from .exemplar import get_exemplar -from .tracker import Result +from ..exemplar import get_exemplar +from .types import Result from ..objectives import Objective, ObjectiveLatency from ..constants import ( CONCURRENCY_NAME, @@ -40,8 +41,7 @@ class OpenTelemetryTracker: __up_down_counter_build_info_instance: UpDownCounter __up_down_counter_concurrency_instance: UpDownCounter - def __init__(self): - exporter = PrometheusMetricReader("") + def __init__(self, reader: Optional[MetricReader] = None): view = View( name=HISTOGRAM_NAME, description=HISTOGRAM_DESCRIPTION, @@ -50,7 +50,8 @@ def __init__(self): boundaries=get_settings()["histogram_buckets"] ), ) - meter_provider = MeterProvider(metric_readers=[exporter], views=[view]) + readers = [reader or PrometheusMetricReader("")] + meter_provider = MeterProvider(metric_readers=readers, views=[view]) set_meter_provider(meter_provider) meter = meter_provider.get_meter(name="autometrics") self.__counter_instance = meter.create_counter( @@ -106,12 +107,10 @@ def __histogram( self, function: str, module: str, - start_time: float, + duration: float, objective: Optional[Objective], exemplar: Optional[dict], ): - duration = time.time() - start_time - objective_name = "" if objective is None else objective.name latency = None if objective is None else objective.latency percentile = "" @@ -165,7 +164,7 @@ def start( def finish( self, - start_time: float, + duration: float, function: str, module: str, caller_module: str, @@ -190,7 +189,7 @@ def finish( exemplar, result, ) - self.__histogram(function, module, start_time, objective, exemplar) + self.__histogram(function, module, duration, objective, exemplar) if track_concurrency: self.__up_down_counter_concurrency_instance.add( -1.0, diff --git a/src/autometrics/tracker/prometheus.py b/src/autometrics/tracker/prometheus.py index 61e914a..0c92084 100644 --- a/src/autometrics/tracker/prometheus.py +++ b/src/autometrics/tracker/prometheus.py @@ -20,8 +20,8 @@ BRANCH_KEY, ) -from .exemplar import get_exemplar -from .tracker import Result +from ..exemplar import get_exemplar +from .types import Result from ..objectives import Objective from ..settings import get_settings @@ -110,12 +110,11 @@ def _histogram( self, func_name: str, module_name: str, - start_time: float, + duration: float, objective: Optional[Objective] = None, exemplar: Optional[dict] = None, ): """Observe the duration of the function call.""" - duration = time.time() - start_time objective_name = "" if objective is None else objective.name latency = None if objective is None else objective.latency @@ -153,7 +152,7 @@ def start( def finish( self, - start_time: float, + duration: float, function: str, module: str, caller_module: str, @@ -176,7 +175,7 @@ def finish( exemplar, result, ) - self._histogram(function, module, start_time, objective, exemplar) + self._histogram(function, module, duration, objective, exemplar) if track_concurrency: service_name = get_settings()["service_name"] diff --git a/src/autometrics/tracker/temporary.py b/src/autometrics/tracker/temporary.py new file mode 100644 index 0000000..0a4c4ea --- /dev/null +++ b/src/autometrics/tracker/temporary.py @@ -0,0 +1,73 @@ +from typing import Optional + +from .types import Result, TrackerMessage, MessageQueue, TrackMetrics +from ..objectives import Objective + + +class TemporaryTracker: + """A tracker that temporarily stores metrics only to hand them off to another tracker.""" + + _queue: MessageQueue = [] + _is_filled: bool = False + + def set_build_info(self, commit: str, version: str, branch: str): + """Observe the build info. Should only be called once per tracker instance""" + pass + + def start( + self, function: str, module: str, track_concurrency: Optional[bool] = False + ): + """Start tracking metrics for a function call.""" + self.append_to_queue(("start", function, module, track_concurrency)) + + def finish( + self, + duration: float, + function: str, + module: str, + caller_module: str, + caller_function: str, + result: Result = Result.OK, + objective: Optional[Objective] = None, + track_concurrency: Optional[bool] = False, + ): + """Finish tracking metrics for a function call.""" + self.append_to_queue( + ( + "finish", + duration, + function, + module, + caller_module, + caller_function, + result, + objective, + track_concurrency, + ) + ) + + def initialize_counters( + self, + function: str, + module: str, + objective: Optional[Objective] = None, + ): + """Initialize (counter) metrics for a function at zero.""" + self.append_to_queue(("initialize_counters", function, module, objective)) + + def append_to_queue(self, message: TrackerMessage): + """Append a message to the queue.""" + if not self._is_filled: + self._queue.append(message) + else: + raise Exception( + "Cannot store more messages in TemporaryTracker, please run init() to select a tracker before the queue is filled." + ) + if len(self._queue) > 999: + self._is_filled = True + + def replay_queue(self, tracker: TrackMetrics): + """Replay a queue of messages on a different tracker.""" + for function_name, *args in self._queue: + function = getattr(tracker, function_name) + function(*args) diff --git a/src/autometrics/tracker/tracker.py b/src/autometrics/tracker/tracker.py index 50ec3b8..cca021e 100644 --- a/src/autometrics/tracker/tracker.py +++ b/src/autometrics/tracker/tracker.py @@ -1,58 +1,16 @@ -from typing import Protocol, Optional +from typing import Protocol, Optional, cast from enum import Enum +from opentelemetry.sdk.metrics.export import MetricReader -from ..objectives import Objective -from ..settings import get_settings +from .types import TrackerType, TrackMetrics +from .temporary import TemporaryTracker +from ..exposition import PrometheusExporterOptions, get_exporter +from ..settings import get_settings, AutometricsSettings -class Result(Enum): - """Result of the function call.""" - - OK = "ok" - ERROR = "error" - - -class TrackMetrics(Protocol): - """Protocol for tracking metrics.""" - - def set_build_info(self, commit: str, version: str, branch: str): - """Observe the build info. Should only be called once per tracker instance""" - - def start( - self, function: str, module: str, track_concurrency: Optional[bool] = False - ): - """Start tracking metrics for a function call.""" - - def finish( - self, - start_time: float, - function: str, - module: str, - caller_module: str, - caller_function: str, - result: Result = Result.OK, - objective: Optional[Objective] = None, - track_concurrency: Optional[bool] = False, - ): - """Finish tracking metrics for a function call.""" - - def initialize_counters( - self, - function: str, - module: str, - objective: Optional[Objective] = None, - ): - """Initialize (counter) metrics for a function at zero.""" - - -class TrackerType(Enum): - """Type of tracker.""" - - OPENTELEMETRY = "opentelemetry" - PROMETHEUS = "prometheus" - - -def init_tracker(tracker_type: TrackerType) -> TrackMetrics: +def init_tracker( + tracker_type: TrackerType, settings: AutometricsSettings +) -> TrackMetrics: """Create a tracker""" tracker_instance: TrackMetrics @@ -60,14 +18,22 @@ def init_tracker(tracker_type: TrackerType) -> TrackMetrics: # pylint: disable=import-outside-toplevel from .opentelemetry import OpenTelemetryTracker - tracker_instance = OpenTelemetryTracker() + exporter: Optional[MetricReader] = None + if settings["exporter"]: + exporter = get_exporter(settings["exporter"]) + tracker_instance = OpenTelemetryTracker(exporter) elif tracker_type == TrackerType.PROMETHEUS: # pylint: disable=import-outside-toplevel from .prometheus import PrometheusTracker - tracker_instance = PrometheusTracker() + if settings["exporter"]: + from prometheus_client import start_http_server - settings = get_settings() + if settings["exporter"]["type"] != "prometheus-client": + raise Exception("Invalid exporter type for Prometheus tracker") + exporter_settings = cast(PrometheusExporterOptions, settings["exporter"]) + start_http_server(exporter_settings["port"], exporter_settings["address"]) + tracker_instance = PrometheusTracker() # NOTE - Only set the build info when the tracker is initialized tracker_instance.set_build_info( commit=settings["commit"], @@ -78,22 +44,7 @@ def init_tracker(tracker_type: TrackerType) -> TrackMetrics: return tracker_instance -def get_tracker_type() -> TrackerType: - """Get the tracker type.""" - tracker_type = get_settings()["tracker"] - - if tracker_type.lower() == "prometheus": - return TrackerType.PROMETHEUS - return TrackerType.OPENTELEMETRY - - -def default_tracker(): - """Setup the default tracker.""" - preferred_tracker = get_tracker_type() - return init_tracker(preferred_tracker) - - -tracker: TrackMetrics = default_tracker() +tracker: TrackMetrics = TemporaryTracker() def get_tracker() -> TrackMetrics: @@ -104,4 +55,6 @@ def get_tracker() -> TrackMetrics: def set_tracker(tracker_type: TrackerType): """Set the tracker type.""" global tracker - tracker = init_tracker(tracker_type) + settings = get_settings() + settings["tracker"] = tracker_type + tracker = init_tracker(tracker_type, settings) diff --git a/src/autometrics/tracker/types.py b/src/autometrics/tracker/types.py new file mode 100644 index 0000000..559b4b5 --- /dev/null +++ b/src/autometrics/tracker/types.py @@ -0,0 +1,57 @@ +from enum import Enum +from typing import Literal, Optional, Protocol + +from ..objectives import Objective + +TrackerMessage = tuple[Literal["start", "finish", "initialize_counters"], ...] +MessageQueue = list[TrackerMessage] + + +class Result(Enum): + """Result of the function call.""" + + OK = "ok" + ERROR = "error" + + +class TrackMetrics(Protocol): + """Protocol for tracking metrics.""" + + def set_build_info(self, commit: str, version: str, branch: str): + """Observe the build info. Should only be called once per tracker instance""" + + def start( + self, function: str, module: str, track_concurrency: Optional[bool] = False + ): + """Start tracking metrics for a function call.""" + + def finish( + self, + duration: float, + function: str, + module: str, + caller_module: str, + caller_function: str, + result: Result = Result.OK, + objective: Optional[Objective] = None, + track_concurrency: Optional[bool] = False, + ): + """Finish tracking metrics for a function call.""" + + def initialize_counters( + self, + function: str, + module: str, + objective: Optional[Objective] = None, + ): + """Initialize (counter) metrics for a function at zero.""" + + def replay_queue(self, queue: MessageQueue): + """Replay a queue of messages""" + + +class TrackerType(Enum): + """Type of tracker.""" + + OPENTELEMETRY = "opentelemetry" + PROMETHEUS = "prometheus" From 5c61050ffc94b632a7bdc1e3e8469dae4e18c502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Mon, 25 Sep 2023 09:04:43 +0200 Subject: [PATCH 02/15] Add test isolation, fix lint/test, add docs --- .github/workflows/main.yml | 2 +- README.md | 135 ++++++++++++------ Tiltfile | 1 + .../otel-collector-config.yaml | 2 +- docker-compose.yaml | 32 +++++ examples/init.py | 23 --- examples/otlp/start.py | 4 +- pyproject.toml | 3 + src/autometrics/__init__.py | 3 +- src/autometrics/conftest.py | 17 +++ src/autometrics/exposition.py | 6 +- src/autometrics/init.py | 24 ---- src/autometrics/initialization.py | 38 +++++ src/autometrics/test_caller.py | 2 + src/autometrics/test_decorator.py | 7 +- src/autometrics/test_initialization.py | 126 ++++++++++++++++ src/autometrics/tracker/opentelemetry.py | 18 ++- src/autometrics/tracker/temporary.py | 21 ++- src/autometrics/tracker/test_concurrency.py | 7 +- src/autometrics/tracker/test_format.py | 4 +- src/autometrics/tracker/test_tracker.py | 57 ++++---- src/autometrics/tracker/tracker.py | 39 +++-- src/autometrics/tracker/types.py | 26 +++- 23 files changed, 419 insertions(+), 178 deletions(-) create mode 100644 Tiltfile rename examples/otlp/otel-collector-config.yml => configs/otel-collector-config.yaml (87%) create mode 100644 docker-compose.yaml delete mode 100644 examples/init.py create mode 100644 src/autometrics/conftest.py delete mode 100644 src/autometrics/init.py create mode 100644 src/autometrics/initialization.py create mode 100644 src/autometrics/test_initialization.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae1c4f3..64acd95 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: poetry - name: Install dependencies - run: poetry install --no-interaction --no-root --with dev,examples + run: poetry install --no-interaction --no-root --with dev,examples --all-extras - name: Check code formatting run: poetry run black . - name: Lint lib code diff --git a/README.md b/README.md index 0dad816..1d959b3 100644 --- a/README.md +++ b/README.md @@ -29,65 +29,76 @@ See [Why Autometrics?](https://github.com/autometrics-dev#why-autometrics) for m ## Quickstart 1. Add `autometrics` to your project's dependencies: - ```shell - pip install autometrics - ``` + + ```shell + pip install autometrics + ``` 2. Instrument your functions with the `@autometrics` decorator - ```python - from autometrics import autometrics - - @autometrics - def my_function(): - # ... - ``` - -3. Export the metrics for Prometheus - ```python - # This example uses FastAPI, but you can use any web framework - from fastapi import FastAPI, Response - from prometheus_client import generate_latest - - # Set up a metrics endpoint for Prometheus to scrape - # `generate_latest` returns metrics data in the Prometheus text format - @app.get("/metrics") - def metrics(): - return Response(generate_latest()) - ``` - -4. Run Prometheus locally with the [Autometrics CLI](https://docs.autometrics.dev/local-development#getting-started-with-am) or [configure it manually](https://github.com/autometrics-dev#5-configuring-prometheus) to scrape your metrics endpoint - ```sh - # Replace `8080` with the port that your app runs on - am start :8080 - ``` - -5. (Optional) If you have Grafana, import the [Autometrics dashboards](https://github.com/autometrics-dev/autometrics-shared#dashboards) for an overview and detailed view of all the function metrics you've collected + ```python + from autometrics import autometrics + + @autometrics + def my_function(): + # ... + ``` + +3. Configure autometrics by calling the `init` function: + +```python +from autometrics import init + +init(tracker="prometheus", service_name="my-service") +``` + +4. Export the metrics for Prometheus + + ```python + # This example uses FastAPI, but you can use any web framework + from fastapi import FastAPI, Response + from prometheus_client import generate_latest + + # Set up a metrics endpoint for Prometheus to scrape + # `generate_latest` returns metrics data in the Prometheus text format + @app.get("/metrics") + def metrics(): + return Response(generate_latest()) + ``` + +5. Run Prometheus locally with the [Autometrics CLI](https://docs.autometrics.dev/local-development#getting-started-with-am) or [configure it manually](https://github.com/autometrics-dev#5-configuring-prometheus) to scrape your metrics endpoint + + ```sh + # Replace `8080` with the port that your app runs on + am start :8080 + ``` + +6. (Optional) If you have Grafana, import the [Autometrics dashboards](https://github.com/autometrics-dev/autometrics-shared#dashboards) for an overview and detailed view of all the function metrics you've collected ## Using `autometrics-py` - You can import the library in your code and use the decorator for any function: - ```py - from autometrics import autometrics + ```python + from autometrics import autometrics - @autometrics - def sayHello: - return "hello" + @autometrics + def sayHello: + return "hello" - ``` + ``` - To show tooltips over decorated functions in VSCode, with links to Prometheus queries, try installing [the VSCode extension](https://marketplace.visualstudio.com/items?itemName=Fiberplane.autometrics). - > **Note**: We cannot support tooltips without a VSCode extension due to behavior of the [static analyzer](https://github.com/davidhalter/jedi/issues/1921) used in VSCode. + > **Note**: We cannot support tooltips without a VSCode extension due to behavior of the [static analyzer](https://github.com/davidhalter/jedi/issues/1921) used in VSCode. -- You can also track the number of concurrent calls to a function by using the `track_concurrency` argument: `@autometrics(track_concurrency=True)`. +- You can also track the number of concurrent calls to a function by using the `track_concurrency` argument: `@autometrics(track_concurrency=True)`. - > **Note**: Concurrency tracking is only supported when you set with the environment variable `AUTOMETRICS_TRACKER=prometheus`. + > **Note**: Concurrency tracking is only supported when you set with the environment variable `AUTOMETRICS_TRACKER=prometheus`. - To access the PromQL queries for your decorated functions, run `help(yourfunction)` or `print(yourfunction.__doc__)`. - > For these queries to work, include a `.env` file in your project with your prometheus endpoint `PROMETHEUS_URL=your endpoint`. If this is not defined, the default endpoint will be `http://localhost:9090/` + > For these queries to work, include a `.env` file in your project with your prometheus endpoint `PROMETHEUS_URL=your endpoint`. If this is not defined, the default endpoint will be `http://localhost:9090/` ## Dashboards @@ -119,15 +130,15 @@ The library uses the concept of Service-Level Objectives (SLOs) to define the ac In order to receive alerts, **you need to add a special set of rules to your Prometheus setup**. These are configured automatically when you use the [Autometrics CLI](https://docs.autometrics.dev/local-development#getting-started-with-am) to run Prometheus. -> Already running Prometheus yourself? [Read about how to load the autometrics alerting rules into Prometheus here](https://github.com/autometrics-dev/autometrics-shared#prometheus-recording--alerting-rules). +> Already running Prometheus yourself? [Read about how to load the autometrics alerting rules into Prometheus here](https://github.com/autometrics-dev/autometrics-shared#prometheus-recording--alerting-rules). Once the alerting rules are in Prometheus, you're ready to go. -To use autometrics SLOs and alerts, create one or multiple `Objective`s based on the function(s) success rate and/or latency, as shown above. +To use autometrics SLOs and alerts, create one or multiple `Objective`s based on the function(s) success rate and/or latency, as shown above. The `Objective` can be passed as an argument to the `autometrics` decorator, which will include the given function in that objective. -The example above used a success rate objective. (I.e., we wanted to be alerted when the error rate started to increase.) +The example above used a success rate objective. (I.e., we wanted to be alerted when the error rate started to increase.) You can also create an objective for the latency of your functions like so: @@ -191,8 +202,7 @@ Autometrics makes it easy to identify if a specific version or commit introduced > > autometrics-py will track support for build_info using the OpenTelemetry tracker via [this issue](https://github.com/autometrics-dev/autometrics-py/issues/38) - -The library uses a separate metric (`build_info`) to track the version and, optionally, the git commit of your service. +The library uses a separate metric (`build_info`) to track the version and, optionally, the git commit of your service. It then writes queries that group metrics by the `version`, `commit` and `branch` labels so you can spot correlations between code changes and potential issues. @@ -230,7 +240,38 @@ exemplar collection by setting `AUTOMETRICS_EXEMPLARS=true`. You also need to en ## Exporting metrics -After collecting metrics with Autometrics, you need to export them to Prometheus. You can either add a separate route to your server and use the `generate_latest` function from the `prometheus_client` package, or you can use the `start_http_server` function from the same package to start a separate server that will expose the metrics. Autometrics also re-exports the `start_http_server` function with a preselected port 9464 for compatibility with other Autometrics packages. +There are multiple ways to export metrics from your application depending on your setup. If you use `prometheus` tracker you can either create a route inside your app and respond with `generate_latest()` or specify +`prometheus-client` as exporter type and a separate server will be started to expose metrics from your app: + +```python +exporter = { + "type": "prometheus-client", + "address": "localhost", + "port": 9464 +} +init(tracker="prometheus", service_name="my-service", exporter=exporter) +``` + +For OpenTelemetry tracker you have more options, including a custom metric reader. By default, when using this tracker autometrics +will export metrics to Prometheus registry via `prometheus-client` from which you can export it via one of the ways described above. +You can also specify exporter type to be `otlp-proto-http` or `otlp-proto-grpc` and metrics will be exported to a remote OpenTelemetry collector via a specified protocol. You will need to install a corresponding extra dependency for this to work, you can do it +when you install autometrics: + +```sh +pip install autometrics[exporter-otlp-proto-http] +pip install autometrics[exporter-otlp-proto-grpc] +``` + +After installing it you can configure the exporter: + +```python +exporter = { + "type": "otlp-proto-grpc", + "address": "http://localhost:4317", + "insecure": True +} +init(tracker="opentelemetry", service_name="my-service", exporter=exporter) +``` ## Development of the package diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..70e0b30 --- /dev/null +++ b/Tiltfile @@ -0,0 +1 @@ +docker_compose('docker-compose.yaml') diff --git a/examples/otlp/otel-collector-config.yml b/configs/otel-collector-config.yaml similarity index 87% rename from examples/otlp/otel-collector-config.yml rename to configs/otel-collector-config.yaml index 731c5d0..af3788b 100644 --- a/examples/otlp/otel-collector-config.yml +++ b/configs/otel-collector-config.yaml @@ -2,7 +2,7 @@ receivers: otlp: protocols: grpc: - endpoint: 0.0.0.0:4317 + http: exporters: logging: diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..5649f7e --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,32 @@ +version: '3.9' + +volumes: + app-logs: + + +services: + am: + image: autometrics/am:latest + ports: + - "6789:6789" + container_name: am + command: "start host.docker.internal:8080" + environment: + - LISTEN_ADDRESS=0.0.0.0:6789 + restart: unless-stopped + volumes: + - app-logs:/var/log + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + container_name: otel-collector + command: ["--config=/etc/otel-collector-config.yaml"] + volumes: + - ./configs/otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - "4317:4317" + - "4318:4318" + - "55680:55680" + - "55679:55679" + restart: unless-stopped + push-gateway: + image: ghcr.io/zapier/prom-aggregation-gateway:v0.7.0 \ No newline at end of file diff --git a/examples/init.py b/examples/init.py deleted file mode 100644 index 9826afe..0000000 --- a/examples/init.py +++ /dev/null @@ -1,23 +0,0 @@ -from time import sleep -from autometrics import autometrics, init - -from prometheus_client import generate_latest - - -@autometrics -def foo(): - return "foo" - - -foo() - -init( - version="1.0.0", - commit="123456789", - branch="main", - exporter={ - "type": "otlp-proto-grpc", - "endpoint": "localhost:4317", - "insecure": True, - }, -) diff --git a/examples/otlp/start.py b/examples/otlp/start.py index 985b9ce..7982d1a 100644 --- a/examples/otlp/start.py +++ b/examples/otlp/start.py @@ -2,7 +2,9 @@ from autometrics import autometrics, init init( - exporter={"type": "otlp-proto-grpc", "endpoint": "http://localhost:4317"}, + exporter={ + "type": "otlp-proto-http", + }, service_name="my-service", ) diff --git a/pyproject.toml b/pyproject.toml index b68435c..e566a12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,9 @@ enable_incomplete_feature = "Unpack" module = ["opentelemetry.attributes"] follow_imports = "skip" +[tool.pytest.ini_options] +usefixtures = "reset_environment" + [tool.poetry.group.dev.dependencies] pytest = "^7.3.0" pytest-asyncio = "^0.21.0" diff --git a/src/autometrics/__init__.py b/src/autometrics/__init__.py index 672cf95..75a4a49 100644 --- a/src/autometrics/__init__.py +++ b/src/autometrics/__init__.py @@ -1,3 +1,2 @@ from .decorator import * -from .init import init -from .utils import start_http_server +from .initialization import init diff --git a/src/autometrics/conftest.py b/src/autometrics/conftest.py new file mode 100644 index 0000000..7575c87 --- /dev/null +++ b/src/autometrics/conftest.py @@ -0,0 +1,17 @@ +import pytest + + +@pytest.fixture() +def reset_environment(monkeypatch): + import importlib + import opentelemetry + import prometheus_client + from . import initialization + from .tracker import tracker + + importlib.reload(opentelemetry) + importlib.reload(prometheus_client) + importlib.reload(initialization) + importlib.reload(tracker) + # we'll set debug to true to ensure calling init more than once will fail whole test + monkeypatch.setenv("AUTOMETRICS_DEBUG", "true") diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index e32e634..abc41b4 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -8,7 +8,7 @@ # GRPC is optional so we'll only type it if it's available try: - from grpc import ChannelCredentials + from grpc import ChannelCredentials # type: ignore except ImportError: ChannelCredentials = None @@ -65,7 +65,7 @@ def get_exporter(config: ExporterOptions) -> MetricReader: endpoint=config.get("endpoint", None), headers=config.get("headers", None), timeout=config.get("timeout", None), - preferred_temporality=config.get("preferred_temporality", None), + preferred_temporality=config.get("preferred_temporality", {}), ) http_reader = PeriodicExportingMetricReader( http_exporter, @@ -88,7 +88,7 @@ def get_exporter(config: ExporterOptions) -> MetricReader: credentials=config.get("credentials", None), headers=config.get("headers", None), timeout=config.get("timeout", None), - preferred_temporality=config.get("preferred_temporality", None), + preferred_temporality=config.get("preferred_temporality", {}), ) grpc_reader = PeriodicExportingMetricReader( grpc_exporter, diff --git a/src/autometrics/init.py b/src/autometrics/init.py deleted file mode 100644 index 7189fbe..0000000 --- a/src/autometrics/init.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing_extensions import Unpack - - -from .tracker import init_tracker, get_tracker -from .tracker.temporary import TemporaryTracker -from .settings import AutometricsOptions, init_settings - -has_inited = False - - -def init(**kwargs: Unpack[AutometricsOptions]): - """Initialization function to be used instead of setting environment variables. You can override settings by calling init with the new settings.""" - global has_inited - if has_inited: - print("Warning: init() has already been called. This call will be ignored.") - return - has_inited = True - temp_tracker = get_tracker() - if not isinstance(temp_tracker, TemporaryTracker): - print("Expected tracker to be TemporaryTracker. This call will be ignored.") - return - settings = init_settings(**kwargs) - tracker = init_tracker(settings["tracker"], settings) - temp_tracker.replay_queue(tracker) diff --git a/src/autometrics/initialization.py b/src/autometrics/initialization.py new file mode 100644 index 0000000..037dd7d --- /dev/null +++ b/src/autometrics/initialization.py @@ -0,0 +1,38 @@ +import logging +import os + +from typing_extensions import Unpack + + +from .tracker import init_tracker, get_tracker, set_tracker +from .tracker.temporary import TemporaryTracker +from .settings import AutometricsOptions, init_settings + +has_inited = False +double_init_error = "Cannot call init() more than once." +not_temp_tracker_error = "Expected tracker to be TemporaryTracker." + + +def init(**kwargs: Unpack[AutometricsOptions]): + """Initialization function that is used to configure autometrics. This function should be called + immediately after starting your app. You cannot call this function more than once. + """ + global has_inited + if has_inited: + if os.environ.get("AUTOMETRICS_DEBUG") == "true": + raise RuntimeError(double_init_error) + else: + logging.warn(f"{double_init_error} This init() call will be ignored.") + return + has_inited = True + + temp_tracker = get_tracker() + if not isinstance(temp_tracker, TemporaryTracker): + if os.environ.get("AUTOMETRICS_DEBUG") == "true": + raise RuntimeError(not_temp_tracker_error) + else: + logging.warn(f"{not_temp_tracker_error} This init() call will be ignored.") + return + settings = init_settings(**kwargs) + tracker = init_tracker(settings["tracker"], settings) + temp_tracker.replay_queue(tracker) diff --git a/src/autometrics/test_caller.py b/src/autometrics/test_caller.py index dbcfdb4..d8509ab 100644 --- a/src/autometrics/test_caller.py +++ b/src/autometrics/test_caller.py @@ -3,10 +3,12 @@ from prometheus_client.exposition import generate_latest from .decorator import autometrics +from .initialization import init def test_caller_detection(): """This is a test to see if the caller is properly detected.""" + init() def dummy_decorator(func): @wraps(func) diff --git a/src/autometrics/test_decorator.py b/src/autometrics/test_decorator.py index 281c45e..97a6411 100644 --- a/src/autometrics/test_decorator.py +++ b/src/autometrics/test_decorator.py @@ -7,8 +7,9 @@ from requests import HTTPError from .decorator import autometrics +from .initialization import init from .objectives import ObjectiveLatency, Objective, ObjectivePercentile -from .tracker import set_tracker, TrackerType +from .tracker import TrackerType from .utils import get_function_name, get_module_name HTTP_ERROR_TEXT = "This is an http error" @@ -71,10 +72,10 @@ async def never_called_async_function(): tracker_types = [TrackerType.PROMETHEUS, TrackerType.OPENTELEMETRY] -@pytest.fixture(scope="class", params=tracker_types) +@pytest.fixture(params=tracker_types) def setup_tracker_type(request): """Force the use of a specific metrics tracker""" - set_tracker(request.param) + init(tracker=request.param.value) @pytest.mark.usefixtures("setup_tracker_type") diff --git a/src/autometrics/test_initialization.py b/src/autometrics/test_initialization.py new file mode 100644 index 0000000..18da901 --- /dev/null +++ b/src/autometrics/test_initialization.py @@ -0,0 +1,126 @@ +import pytest + +from autometrics import init +from autometrics.tracker.opentelemetry import OpenTelemetryTracker +from autometrics.tracker.prometheus import PrometheusTracker +from autometrics.tracker.tracker import get_tracker +from autometrics.tracker.types import TrackerType +from autometrics.settings import get_settings + + +def test_init(): + """Test that the default settings are set correctly""" + init() + settings = get_settings() + assert settings == { + "histogram_buckets": [ + 0.005, + 0.01, + 0.025, + 0.05, + 0.075, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0, + 2.5, + 5.0, + 7.5, + 10.0, + ], + "enable_exemplars": False, + "tracker": TrackerType.OPENTELEMETRY, + "exporter": None, + "service_name": "autometrics", + "commit": "", + "branch": "", + "version": "", + } + tracker = get_tracker() + assert isinstance(tracker, OpenTelemetryTracker) + + +def test_init_custom(): + """Test that setting custom settings works correctly""" + init( + tracker="prometheus", + service_name="test", + enable_exemplars=True, + version="1.0.0", + commit="123456", + branch="main", + ) + settings = get_settings() + assert settings == { + "histogram_buckets": [ + 0.005, + 0.01, + 0.025, + 0.05, + 0.075, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0, + 2.5, + 5.0, + 7.5, + 10.0, + ], + "enable_exemplars": True, + "tracker": TrackerType.PROMETHEUS, + "exporter": None, + "service_name": "test", + "commit": "123456", + "branch": "main", + "version": "1.0.0", + } + tracker = get_tracker() + assert isinstance(tracker, PrometheusTracker) + + +def test_init_env_vars(monkeypatch): + """Test that setting custom settings via environment variables works correctly""" + monkeypatch.setenv("AUTOMETRICS_TRACKER", "prometheus") + monkeypatch.setenv("AUTOMETRICS_SERVICE_NAME", "test") + monkeypatch.setenv("AUTOMETRICS_EXEMPLARS", "true") + monkeypatch.setenv("AUTOMETRICS_VERSION", "1.0.0") + monkeypatch.setenv("AUTOMETRICS_COMMIT", "123456") + monkeypatch.setenv("AUTOMETRICS_BRANCH", "main") + init() + settings = get_settings() + + assert settings == { + "histogram_buckets": [ + 0.005, + 0.01, + 0.025, + 0.05, + 0.075, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0, + 2.5, + 5.0, + 7.5, + 10.0, + ], + "enable_exemplars": True, + "tracker": TrackerType.PROMETHEUS, + "exporter": None, + "service_name": "test", + "commit": "123456", + "branch": "main", + "version": "1.0.0", + } + + +def test_double_init(): + """Test that calling init twice fails""" + init() + with pytest.raises(RuntimeError): + init() diff --git a/src/autometrics/tracker/opentelemetry.py b/src/autometrics/tracker/opentelemetry.py index 5c8f662..5cf54e4 100644 --- a/src/autometrics/tracker/opentelemetry.py +++ b/src/autometrics/tracker/opentelemetry.py @@ -1,17 +1,18 @@ import time from typing import Optional +from opentelemetry.exporter.prometheus import PrometheusMetricReader from opentelemetry.metrics import ( - Meter, Counter, Histogram, UpDownCounter, set_meter_provider, ) +from opentelemetry.semconv.resource import ResourceAttributes from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation from opentelemetry.sdk.metrics.export import MetricReader -from opentelemetry.exporter.prometheus import PrometheusMetricReader +from opentelemetry.sdk.resources import Resource from ..exemplar import get_exemplar from .types import Result @@ -50,8 +51,18 @@ def __init__(self, reader: Optional[MetricReader] = None): boundaries=get_settings()["histogram_buckets"] ), ) + attrs = {} + if get_settings()["service_name"] is not None: + attrs[ResourceAttributes.SERVICE_NAME] = get_settings()["service_name"] + if get_settings()["version"] is not None: + attrs[ResourceAttributes.SERVICE_VERSION] = get_settings()["version"] + resource = Resource.create(attrs) readers = [reader or PrometheusMetricReader("")] - meter_provider = MeterProvider(metric_readers=readers, views=[view]) + meter_provider = MeterProvider( + views=[view], + resource=resource, + metric_readers=readers, + ) set_meter_provider(meter_provider) meter = meter_provider.get_meter(name="autometrics") self.__counter_instance = meter.create_counter( @@ -174,7 +185,6 @@ def finish( track_concurrency: Optional[bool] = False, ): """Finish tracking metrics for a function call.""" - exemplar = None # Currently, exemplars are only supported by prometheus-client # https://github.com/autometrics-dev/autometrics-py/issues/41 diff --git a/src/autometrics/tracker/temporary.py b/src/autometrics/tracker/temporary.py index 0a4c4ea..ffe0bcf 100644 --- a/src/autometrics/tracker/temporary.py +++ b/src/autometrics/tracker/temporary.py @@ -1,3 +1,5 @@ +import logging + from typing import Optional from .types import Result, TrackerMessage, MessageQueue, TrackMetrics @@ -8,7 +10,8 @@ class TemporaryTracker: """A tracker that temporarily stores metrics only to hand them off to another tracker.""" _queue: MessageQueue = [] - _is_filled: bool = False + _is_closed: bool = False + _new_tracker: Optional[TrackMetrics] = None def set_build_info(self, commit: str, version: str, branch: str): """Observe the build info. Should only be called once per tracker instance""" @@ -57,17 +60,23 @@ def initialize_counters( def append_to_queue(self, message: TrackerMessage): """Append a message to the queue.""" - if not self._is_filled: + if not self._is_closed: self._queue.append(message) + if len(self._queue) > 999: + self._is_closed = True + elif self._new_tracker is not None: + function_name, *args = message + function = getattr(self._new_tracker, function_name) + function(*args) else: - raise Exception( - "Cannot store more messages in TemporaryTracker, please run init() to select a tracker before the queue is filled." + logging.error( + "Temporary tracker queue is filled, this metric will be dropped. Please run init() when your application starts." ) - if len(self._queue) > 999: - self._is_filled = True def replay_queue(self, tracker: TrackMetrics): """Replay a queue of messages on a different tracker.""" + self._new_tracker = tracker + self._is_closed = True for function_name, *args in self._queue: function = getattr(tracker, function_name) function(*args) diff --git a/src/autometrics/tracker/test_concurrency.py b/src/autometrics/tracker/test_concurrency.py index d10703f..ec375b7 100644 --- a/src/autometrics/tracker/test_concurrency.py +++ b/src/autometrics/tracker/test_concurrency.py @@ -2,9 +2,8 @@ import asyncio import pytest -from .tracker import set_tracker, TrackerType - from ..decorator import autometrics +from ..initialization import init from ..utils import get_function_name, get_module_name @@ -15,9 +14,7 @@ async def sleep(time: float): @pytest.mark.asyncio async def test_concurrency_tracking_prometheus(monkeypatch): - # HACK - We need to set the tracker explicitly here, instead of using `init_tracker` - # because the library was already initialized with the OpenTelemetry tracker - set_tracker(TrackerType.PROMETHEUS) + init(tracker="prometheus") func_name = get_function_name(sleep) module_name = get_module_name(sleep) diff --git a/src/autometrics/tracker/test_format.py b/src/autometrics/tracker/test_format.py index afa006f..a5a4c14 100644 --- a/src/autometrics/tracker/test_format.py +++ b/src/autometrics/tracker/test_format.py @@ -3,13 +3,13 @@ from . import TrackerType from ..decorator import autometrics -from ..init import init +from ..initialization import init @pytest.mark.parametrize("tracker", TrackerType) def test_metrics_format(tracker): """Test that the metrics are formatted correctly.""" - init(tracker=tracker.value) + init(tracker=tracker.value, version="1.0") @autometrics def test_function(): diff --git a/src/autometrics/tracker/test_tracker.py b/src/autometrics/tracker/test_tracker.py index d6b2129..894ab73 100644 --- a/src/autometrics/tracker/test_tracker.py +++ b/src/autometrics/tracker/test_tracker.py @@ -1,36 +1,36 @@ -from prometheus_client.exposition import generate_latest import pytest +from prometheus_client.exposition import generate_latest + from .opentelemetry import OpenTelemetryTracker from .prometheus import PrometheusTracker - -from .tracker import get_tracker, default_tracker - -from ..init import init - - -def test_default_tracker(monkeypatch): - """Test the default tracker type.""" - - monkeypatch.delenv("AUTOMETRICS_TRACKER", raising=False) - init() - tracker = default_tracker() - assert isinstance(tracker, OpenTelemetryTracker) - - monkeypatch.setenv("AUTOMETRICS_TRACKER", "prometheus") - init() - tracker = default_tracker() - assert isinstance(tracker, PrometheusTracker) - monkeypatch.setenv("AUTOMETRICS_TRACKER", "PROMETHEUS") - init() - tracker = default_tracker() - assert isinstance(tracker, PrometheusTracker) - - # Should use open telemetry when the tracker is not recognized - monkeypatch.setenv("AUTOMETRICS_TRACKER", "something_else") +from .tracker import get_tracker + +from ..initialization import init + + +@pytest.fixture( + params=[ + (None, OpenTelemetryTracker), + ("prometheus", PrometheusTracker), + ("PROMETHEUS", PrometheusTracker), + ("something_else", OpenTelemetryTracker), + ] +) +def tracker_var(request): + return request.param + + +def test_default_tracker(monkeypatch, tracker_var): + """Test that the default tracker is set correctly.""" + (env_value, Tracker) = tracker_var + if env_value is not None: + monkeypatch.setenv("AUTOMETRICS_TRACKER", env_value) + else: + monkeypatch.delenv("AUTOMETRICS_TRACKER", raising=False) init() - tracker = default_tracker() - assert isinstance(tracker, OpenTelemetryTracker) + tracker = get_tracker() + assert isinstance(tracker, Tracker) def test_init_prometheus_tracker_set_build_info(monkeypatch): @@ -53,7 +53,6 @@ def test_init_prometheus_tracker_set_build_info(monkeypatch): blob = generate_latest() assert blob is not None data = blob.decode("utf-8") - print(data) prom_build_info = f"""build_info{{branch="{branch}",commit="{commit}",service_name="autometrics",version="{version}"}} 1.0""" assert prom_build_info in data diff --git a/src/autometrics/tracker/tracker.py b/src/autometrics/tracker/tracker.py index cca021e..60e1851 100644 --- a/src/autometrics/tracker/tracker.py +++ b/src/autometrics/tracker/tracker.py @@ -1,11 +1,25 @@ -from typing import Protocol, Optional, cast -from enum import Enum +from typing import Optional, cast from opentelemetry.sdk.metrics.export import MetricReader from .types import TrackerType, TrackMetrics from .temporary import TemporaryTracker from ..exposition import PrometheusExporterOptions, get_exporter -from ..settings import get_settings, AutometricsSettings +from ..settings import AutometricsSettings + + +_tracker: TrackMetrics = TemporaryTracker() + + +def get_tracker() -> TrackMetrics: + """Get the tracker type.""" + global _tracker + return _tracker + + +def set_tracker(new_tracker: TrackMetrics): + """Set the tracker type.""" + global _tracker + _tracker = new_tracker def init_tracker( @@ -26,7 +40,7 @@ def init_tracker( # pylint: disable=import-outside-toplevel from .prometheus import PrometheusTracker - if settings["exporter"]: + if settings["exporter"] and not isinstance(settings["exporter"], MetricReader): from prometheus_client import start_http_server if settings["exporter"]["type"] != "prometheus-client": @@ -41,20 +55,5 @@ def init_tracker( branch=settings["branch"], ) + set_tracker(tracker_instance) return tracker_instance - - -tracker: TrackMetrics = TemporaryTracker() - - -def get_tracker() -> TrackMetrics: - """Get the tracker type.""" - return tracker - - -def set_tracker(tracker_type: TrackerType): - """Set the tracker type.""" - global tracker - settings = get_settings() - settings["tracker"] = tracker_type - tracker = init_tracker(tracker_type, settings) diff --git a/src/autometrics/tracker/types.py b/src/autometrics/tracker/types.py index 559b4b5..d52c76f 100644 --- a/src/autometrics/tracker/types.py +++ b/src/autometrics/tracker/types.py @@ -1,11 +1,8 @@ from enum import Enum -from typing import Literal, Optional, Protocol +from typing import Union, Optional, Protocol, List, Literal from ..objectives import Objective -TrackerMessage = tuple[Literal["start", "finish", "initialize_counters"], ...] -MessageQueue = list[TrackerMessage] - class Result(Enum): """Result of the function call.""" @@ -46,12 +43,27 @@ def initialize_counters( ): """Initialize (counter) metrics for a function at zero.""" - def replay_queue(self, queue: MessageQueue): - """Replay a queue of messages""" - class TrackerType(Enum): """Type of tracker.""" OPENTELEMETRY = "opentelemetry" PROMETHEUS = "prometheus" + + +TrackerMessage = Union[ + tuple[Literal["start"], str, str, Optional[bool]], + tuple[ + Literal["finish"], + float, + str, + str, + str, + str, + Result, + Optional[Objective], + Optional[bool], + ], + tuple[Literal["initialize_counters"], str, str, Optional[Objective]], +] +MessageQueue = List[TrackerMessage] From 664cb106a3bc8c6261ae23a783b44c61d8560f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Mon, 25 Sep 2023 09:25:21 +0200 Subject: [PATCH 03/15] Fix typing --- src/autometrics/exposition.py | 6 +++--- src/autometrics/tracker/opentelemetry.py | 22 +++++++++++++++------- src/autometrics/tracker/types.py | 8 ++++---- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index abc41b4..3040787 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -1,4 +1,4 @@ -from typing import cast, Literal, TypedDict, Optional, Union +from typing import cast, Dict, Literal, TypedDict, Optional, Union from opentelemetry.sdk.metrics.export import ( AggregationTemporality, MetricReader, @@ -19,11 +19,11 @@ class OTLPExporterOptions(TypedDict): type: Literal["otlp-proto-http", "otlp-proto-grpc"] endpoint: str insecure: bool - headers: dict[str, str] + headers: Dict[str, str] credentials: ChannelCredentials push_interval: int timeout: int - preferred_temporality: dict[type, AggregationTemporality] + preferred_temporality: Dict[type, AggregationTemporality] class OTELPrometheusExporterOptions(TypedDict): diff --git a/src/autometrics/tracker/opentelemetry.py b/src/autometrics/tracker/opentelemetry.py index 5cf54e4..77e6500 100644 --- a/src/autometrics/tracker/opentelemetry.py +++ b/src/autometrics/tracker/opentelemetry.py @@ -1,5 +1,5 @@ import time -from typing import Optional +from typing import Dict, Optional, Mapping from opentelemetry.exporter.prometheus import PrometheusMetricReader from opentelemetry.metrics import ( @@ -13,6 +13,7 @@ from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation from opentelemetry.sdk.metrics.export import MetricReader from opentelemetry.sdk.resources import Resource +from opentelemetry.util.types import AttributeValue from ..exemplar import get_exemplar from .types import Result @@ -33,6 +34,18 @@ ) from ..settings import get_settings +LabelValue = AttributeValue +Attributes = Dict[str, LabelValue] + + +def get_resource_attrs() -> Attributes: + attrs: Attributes = {} + if get_settings()["service_name"] is not None: + attrs[ResourceAttributes.SERVICE_NAME] = get_settings()["service_name"] + if get_settings()["version"] is not None: + attrs[ResourceAttributes.SERVICE_VERSION] = get_settings()["version"] + return attrs + class OpenTelemetryTracker: """Tracker for OpenTelemetry.""" @@ -51,12 +64,7 @@ def __init__(self, reader: Optional[MetricReader] = None): boundaries=get_settings()["histogram_buckets"] ), ) - attrs = {} - if get_settings()["service_name"] is not None: - attrs[ResourceAttributes.SERVICE_NAME] = get_settings()["service_name"] - if get_settings()["version"] is not None: - attrs[ResourceAttributes.SERVICE_VERSION] = get_settings()["version"] - resource = Resource.create(attrs) + resource = Resource.create(get_resource_attrs()) readers = [reader or PrometheusMetricReader("")] meter_provider = MeterProvider( views=[view], diff --git a/src/autometrics/tracker/types.py b/src/autometrics/tracker/types.py index d52c76f..2b310d5 100644 --- a/src/autometrics/tracker/types.py +++ b/src/autometrics/tracker/types.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Union, Optional, Protocol, List, Literal +from typing import Union, Optional, Protocol, List, Literal, Tuple from ..objectives import Objective @@ -52,8 +52,8 @@ class TrackerType(Enum): TrackerMessage = Union[ - tuple[Literal["start"], str, str, Optional[bool]], - tuple[ + Tuple[Literal["start"], str, str, Optional[bool]], + Tuple[ Literal["finish"], float, str, @@ -64,6 +64,6 @@ class TrackerType(Enum): Optional[Objective], Optional[bool], ], - tuple[Literal["initialize_counters"], str, str, Optional[Objective]], + Tuple[Literal["initialize_counters"], str, str, Optional[Objective]], ] MessageQueue = List[TrackerMessage] From aa99a4412cc33a34a561855db7dd1ce0cff1d27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Mon, 25 Sep 2023 10:11:51 +0200 Subject: [PATCH 04/15] Fix mypy --- src/autometrics/settings.py | 46 +++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/autometrics/settings.py b/src/autometrics/settings.py index 4872507..75f96ef 100644 --- a/src/autometrics/settings.py +++ b/src/autometrics/settings.py @@ -1,11 +1,16 @@ import os from opentelemetry.sdk.metrics.export import MetricReader -from typing import List, TypedDict, Optional +from typing import Any, Dict, List, TypedDict, Optional, Union from typing_extensions import Unpack from .tracker.types import TrackerType -from .exposition import ExporterOptions +from .exposition import ( + ExporterOptions, + PrometheusExporterOptions, + OTELPrometheusExporterOptions, + OTLPExporterOptions, +) from .objectives import ObjectiveLatency @@ -27,7 +32,7 @@ class AutometricsOptions(TypedDict, total=False): histogram_buckets: List[float] tracker: str - exporter: ExporterOptions + exporter: Dict[str, str] enable_exemplars: bool service_name: str commit: str @@ -52,6 +57,10 @@ def init_settings(**overrides: Unpack[AutometricsOptions]) -> AutometricsSetting if tracker_setting.lower() == "prometheus" else TrackerType.OPENTELEMETRY ) + exporter: Optional[ExporterOptions] = None + exporter_option = overrides.get("exporter") + if exporter_option is not None: + exporter = get_exporter_settings(exporter_option) config: AutometricsSettings = { "histogram_buckets": overrides.get("histogram_buckets") @@ -60,7 +69,7 @@ def init_settings(**overrides: Unpack[AutometricsOptions]) -> AutometricsSetting "enable_exemplars", os.getenv("AUTOMETRICS_EXEMPLARS") == "true" ), "tracker": tracker_type, - "exporter": overrides.get("exporter"), + "exporter": exporter, "service_name": overrides.get( "service_name", os.getenv( @@ -112,3 +121,32 @@ def validate_settings(settings: AutometricsSettings): raise ValueError( "OTLP exporter is not supported with Prometheus tracker" ) + + +def get_exporter_settings(exporter_options: Dict[str, Any]) -> ExporterOptions: + """Get the exporter settings from the user supplied options.""" + if exporter_options["type"] == "prometheus-client": + return PrometheusExporterOptions( + type="prometheus-client", + address=exporter_options["address"], + port=exporter_options["port"], + prefix=exporter_options["prefix"], + ) + if ["otlp-proto-http", "otlp-proto-grpc"].count(exporter_options["type"]) > 0: + return OTLPExporterOptions( + type=exporter_options["type"], + endpoint=exporter_options["endpoint"], + insecure=exporter_options["insecure"], + headers=exporter_options["headers"], + credentials=exporter_options["credentials"], + push_interval=exporter_options["push_interval"], + timeout=exporter_options["timeout"], + preferred_temporality=exporter_options["preferred_temporality"], + ) + + if exporter_options["type"] == "otel-prometheus": + return OTELPrometheusExporterOptions( + type="otel-prometheus", + prefix=exporter_options["prefix"], + ) + raise ValueError(f"Unsupported exporter type: {exporter_options['type']}") From a6cef91bbcdf814dd06ec7ad573c430eaa7d99fa Mon Sep 17 00:00:00 2001 From: Brett Beutell Date: Tue, 26 Sep 2023 17:42:12 +0200 Subject: [PATCH 05/15] Update poetry install command in README under Development --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d959b3..fded6a4 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ Code in this repository is: In order to run these tools locally you have to install them, you can install them using poetry: ```sh -poetry install --with dev +poetry install --with dev --all-extras ``` After that you can run the tools individually From 64bf6bea23c6531d38e01b2193ec9d4cc94b899f Mon Sep 17 00:00:00 2001 From: Brett Beutell Date: Tue, 26 Sep 2023 18:05:37 +0200 Subject: [PATCH 06/15] Readme tweaks --- README.md | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index fded6a4..d9b4ca2 100644 --- a/README.md +++ b/README.md @@ -240,29 +240,43 @@ exemplar collection by setting `AUTOMETRICS_EXEMPLARS=true`. You also need to en ## Exporting metrics -There are multiple ways to export metrics from your application depending on your setup. If you use `prometheus` tracker you can either create a route inside your app and respond with `generate_latest()` or specify -`prometheus-client` as exporter type and a separate server will be started to expose metrics from your app: +There are multiple ways to export metrics from your application, depending on your setup. -```python -exporter = { - "type": "prometheus-client", - "address": "localhost", - "port": 9464 -} -init(tracker="prometheus", service_name="my-service", exporter=exporter) -``` +If you use the `prometheus` tracker, you have two options. + +1. Create a route inside your app and respond with `generate_latest()` + ```python + # This example uses FastAPI, but you can use any web framework + from fastapi import FastAPI, Response + from prometheus_client import generate_latest + + # Set up a metrics endpoint for Prometheus to scrape + @app.get("/metrics") + def metrics(): + return Response(generate_latest()) + ``` + +2. Specify `prometheus-client` as the exporter type, and a separate server will be started to expose metrics from your app: + ```python + exporter = { + "type": "prometheus-client", + "address": "localhost", + "port": 9464 + } + init(tracker="prometheus", service_name="my-service", exporter=exporter) + ``` + +For the OpenTelemetry tracker, you have more options, including a custom metric reader. By default, when using this tracker, autometrics +will export metrics to the Prometheus registry via `prometheus-client`, from which you can export via one of the ways described above. -For OpenTelemetry tracker you have more options, including a custom metric reader. By default, when using this tracker autometrics -will export metrics to Prometheus registry via `prometheus-client` from which you can export it via one of the ways described above. -You can also specify exporter type to be `otlp-proto-http` or `otlp-proto-grpc` and metrics will be exported to a remote OpenTelemetry collector via a specified protocol. You will need to install a corresponding extra dependency for this to work, you can do it -when you install autometrics: +You can also specify the exporter type to be `otlp-proto-http` or `otlp-proto-grpc`, and metrics will be exported to a remote OpenTelemetry collector via the specified protocol. You will need to install the respective extra dependency in order for this to work, which you can do when you install autometrics: ```sh pip install autometrics[exporter-otlp-proto-http] pip install autometrics[exporter-otlp-proto-grpc] ``` -After installing it you can configure the exporter: +After installing it you can configure the exporter as follows: ```python exporter = { From fc9ec9643f23330f9a1c574b2dee90143e09a3a2 Mon Sep 17 00:00:00 2001 From: Brett Beutell Date: Tue, 26 Sep 2023 18:09:20 +0200 Subject: [PATCH 07/15] Make docker-compose file compatiable with linux and windows --- docker-compose.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 5649f7e..639a910 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,8 @@ volumes: services: am: image: autometrics/am:latest + extra_hosts: + - host.docker.internal:host-gateway ports: - "6789:6789" container_name: am From abf6be60954ad77c91402791893c4297fbbce060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Wed, 27 Sep 2023 18:04:55 +0200 Subject: [PATCH 08/15] Fix review comments --- .github/workflows/main.yml | 6 +- README.md | 40 +- docker-compose.yaml | 10 +- examples/export_metrics/otel-prometheus.py | 26 ++ examples/export_metrics/otlp.py | 26 ++ examples/export_metrics/prometheus-client.py | 27 ++ examples/otlp/start.py | 19 - poetry.lock | 421 ++++++++++++------- pyproject.toml | 9 +- src/autometrics/exposition.py | 119 +++++- src/autometrics/initialization.py | 14 +- src/autometrics/settings.py | 66 +-- src/autometrics/test_decorator.py | 14 +- src/autometrics/test_initialization.py | 49 +++ src/autometrics/tracker/tracker.py | 13 +- 15 files changed, 555 insertions(+), 304 deletions(-) create mode 100644 examples/export_metrics/otel-prometheus.py create mode 100644 examples/export_metrics/otlp.py create mode 100644 examples/export_metrics/prometheus-client.py delete mode 100644 examples/otlp/start.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64acd95..a136ffd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,8 +23,12 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: poetry - - name: Install dependencies + - name: Install dependencies (cpython) + if: ${{ matrix.python-version != 'pypy3.10' }} run: poetry install --no-interaction --no-root --with dev,examples --all-extras + - name: Install dependencies (pypy) + if: ${{ matrix.python-version == 'pypy3.10' }} + run: poetry install --no-interaction --no-root --with dev,examples --extras=exporter-otlp-proto-http - name: Check code formatting run: poetry run black . - name: Lint lib code diff --git a/README.md b/README.md index d9b4ca2..900d491 100644 --- a/README.md +++ b/README.md @@ -240,31 +240,33 @@ exemplar collection by setting `AUTOMETRICS_EXEMPLARS=true`. You also need to en ## Exporting metrics -There are multiple ways to export metrics from your application, depending on your setup. +There are multiple ways to export metrics from your application, depending on your setup. You can see examples of how to do this in the [examples/export_metrics](https://github.com/autometrics-dev/autometrics-py/tree/main/examples/export_metrics) directory of this repository. If you use the `prometheus` tracker, you have two options. 1. Create a route inside your app and respond with `generate_latest()` - ```python - # This example uses FastAPI, but you can use any web framework - from fastapi import FastAPI, Response - from prometheus_client import generate_latest - - # Set up a metrics endpoint for Prometheus to scrape - @app.get("/metrics") - def metrics(): - return Response(generate_latest()) - ``` + +```python +# This example uses FastAPI, but you can use any web framework +from fastapi import FastAPI, Response +from prometheus_client import generate_latest + +# Set up a metrics endpoint for Prometheus to scrape +@app.get("/metrics") +def metrics(): + return Response(generate_latest()) +``` 2. Specify `prometheus-client` as the exporter type, and a separate server will be started to expose metrics from your app: - ```python - exporter = { - "type": "prometheus-client", - "address": "localhost", - "port": 9464 - } - init(tracker="prometheus", service_name="my-service", exporter=exporter) - ``` + +```python +exporter = { + "type": "prometheus-client", + "address": "localhost", + "port": 9464 +} +init(tracker="prometheus", service_name="my-service", exporter=exporter) +``` For the OpenTelemetry tracker, you have more options, including a custom metric reader. By default, when using this tracker, autometrics will export metrics to the Prometheus registry via `prometheus-client`, from which you can export via one of the ways described above. diff --git a/docker-compose.yaml b/docker-compose.yaml index 639a910..1f56420 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,9 +1,8 @@ -version: '3.9' +version: "3.9" volumes: app-logs: - services: am: image: autometrics/am:latest @@ -11,9 +10,10 @@ services: - host.docker.internal:host-gateway ports: - "6789:6789" + - "9090:9090" container_name: am - command: "start host.docker.internal:8080" - environment: + command: "start host.docker.internal:9464" + environment: - LISTEN_ADDRESS=0.0.0.0:6789 restart: unless-stopped volumes: @@ -31,4 +31,4 @@ services: - "55679:55679" restart: unless-stopped push-gateway: - image: ghcr.io/zapier/prom-aggregation-gateway:v0.7.0 \ No newline at end of file + image: ghcr.io/zapier/prom-aggregation-gateway:latest diff --git a/examples/export_metrics/otel-prometheus.py b/examples/export_metrics/otel-prometheus.py new file mode 100644 index 0000000..dbc667b --- /dev/null +++ b/examples/export_metrics/otel-prometheus.py @@ -0,0 +1,26 @@ +import time +from autometrics import autometrics, init + +# Autometrics supports exporting metrics to Prometheus via the OpenTelemetry. +# This example uses the Prometheus Python client, available settings are same as the +# Prometheus Python client. By default, the Prometheus exporter will expose metrics +# on port 9464. If you don't have a Prometheus server running, you can run Tilt or +# Docker Compose from the root of this repo to start one up. + +init( + tracker="opentelemetry", + exporter={ + "type": "otel-prometheus", + }, + service_name="my-service", +) + + +@autometrics +def my_function(): + pass + + +while True: + my_function() + time.sleep(1) diff --git a/examples/export_metrics/otlp.py b/examples/export_metrics/otlp.py new file mode 100644 index 0000000..e8fef2b --- /dev/null +++ b/examples/export_metrics/otlp.py @@ -0,0 +1,26 @@ +import time +from autometrics import autometrics, init + +# Autometrics supports exporting metrics to OTLP collectors via gRPC and HTTP transports. +# This example uses the gRPC transport, available settings are similar to the OpenTelemetry +# Python SDK. By default, the OTLP exporter will send metrics to localhost:4317. +# If you don't have an OTLP collector running, you can run Tilt or Docker Compose +# to start one up. See the README for more details. + +init( + exporter={ + "type": "otlp-proto-grpc", + "push_interval": 1000, + }, + service_name="my-service", +) + + +@autometrics +def my_function(): + pass + + +while True: + my_function() + time.sleep(1) diff --git a/examples/export_metrics/prometheus-client.py b/examples/export_metrics/prometheus-client.py new file mode 100644 index 0000000..d40045d --- /dev/null +++ b/examples/export_metrics/prometheus-client.py @@ -0,0 +1,27 @@ +import time +from autometrics import autometrics, init + +# Autometrics supports exporting metrics to Prometheus via the Prometheus Python client. +# This example uses the Prometheus Python client, available settings are same as the +# Prometheus Python client. By default, the Prometheus exporter will expose metrics +# on port 9464. If you don't have a Prometheus server running, you can run Tilt or +# Docker Compose from the root of this repo to start one up. + +init( + tracker="prometheus", + exporter={ + "type": "prometheus-client", + "port": 9464, + }, + service_name="my-service", +) + + +@autometrics +def my_function(): + pass + + +while True: + my_function() + time.sleep(1) diff --git a/examples/otlp/start.py b/examples/otlp/start.py deleted file mode 100644 index 7982d1a..0000000 --- a/examples/otlp/start.py +++ /dev/null @@ -1,19 +0,0 @@ -import time -from autometrics import autometrics, init - -init( - exporter={ - "type": "otlp-proto-http", - }, - service_name="my-service", -) - - -@autometrics -def my_function(): - pass - - -while True: - my_function() - time.sleep(1) diff --git a/poetry.lock b/poetry.lock index 232d4af..fd2dd77 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,24 +1,39 @@ # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" -version = "3.6.2" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" files = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] [[package]] name = "asgiref" @@ -479,34 +494,34 @@ yaml = ["PyYAML"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -639,21 +654,23 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "fastapi" -version = "0.97.0" +version = "0.103.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.7" files = [ - {file = "fastapi-0.97.0-py3-none-any.whl", hash = "sha256:95d757511c596409930bd20673358d4a4d709004edb85c5d24d6ffc48fabcbf2"}, - {file = "fastapi-0.97.0.tar.gz", hash = "sha256:b53248ee45f64f19bb7600953696e3edf94b0f7de94df1e5433fc5c6136fa986"}, + {file = "fastapi-0.103.1-py3-none-any.whl", hash = "sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83"}, + {file = "fastapi-0.103.1.tar.gz", hash = "sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" [package.extras] -all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flask" @@ -1154,21 +1171,21 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag [[package]] name = "importlib-resources" -version = "6.0.1" +version = "6.1.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, - {file = "importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, + {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, + {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] [[package]] name = "iniconfig" @@ -1400,74 +1417,67 @@ files = [ [[package]] name = "msgpack" -version = "1.0.5" +version = "1.0.6" description = "MessagePack serializer" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {file = "msgpack-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f4321692e7f299277e55f322329b2c972d93bb612d85f3fda8741bec5c6285ce"}, + {file = "msgpack-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f0e36a5fa7a182cde391a128a64f437657d2b9371dfa42eda3436245adccbf5"}, + {file = "msgpack-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b5c8dd9a386a66e50bd7fa22b7a49fb8ead2b3574d6bd69eb1caced6caea0803"}, + {file = "msgpack-1.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f85200ea102276afdd3749ca94747f057bbb868d1c52921ee2446730b508d0f"}, + {file = "msgpack-1.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a006c300e82402c0c8f1ded11352a3ba2a61b87e7abb3054c845af2ca8d553c"}, + {file = "msgpack-1.0.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33bbf47ea5a6ff20c23426106e81863cdbb5402de1825493026ce615039cc99d"}, + {file = "msgpack-1.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04450e4b5e1e662e7c86b6aafb7c230af9334fd0becf5e6b80459a507884241c"}, + {file = "msgpack-1.0.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b06a5095a79384760625b5de3f83f40b3053a385fb893be8a106fbbd84c14980"}, + {file = "msgpack-1.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3910211b0ab20be3a38e0bb944ed45bd4265d8d9f11a3d1674b95b298e08dd5c"}, + {file = "msgpack-1.0.6-cp310-cp310-win32.whl", hash = "sha256:1dc67b40fe81217b308ab12651adba05e7300b3a2ccf84d6b35a878e308dd8d4"}, + {file = "msgpack-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:885de1ed5ea01c1bfe0a34c901152a264c3c1f8f1d382042b92ea354bd14bb0e"}, + {file = "msgpack-1.0.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:099c3d8a027367e1a6fc55d15336f04ff65c60c4f737b5739f7db4525c65fe9e"}, + {file = "msgpack-1.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b88dc97ba86c96b964c3745a445d9a65f76fe21955a953064fe04adb63e9367"}, + {file = "msgpack-1.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:00ce5f827d4f26fc094043e6f08b6069c1b148efa2631c47615ae14fb6cafc89"}, + {file = "msgpack-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd6af61388be65a8701f5787362cb54adae20007e0cc67ca9221a4b95115583b"}, + {file = "msgpack-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:652e4b7497825b0af6259e2c54700e6dc33d2fc4ed92b8839435090d4c9cc911"}, + {file = "msgpack-1.0.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b08676a17e3f791daad34d5fcb18479e9c85e7200d5a17cbe8de798643a7e37"}, + {file = "msgpack-1.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:229ccb6713c8b941eaa5cf13dc7478eba117f21513b5893c35e44483e2f0c9c8"}, + {file = "msgpack-1.0.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:95ade0bd4cf69e04e8b8f8ec2d197d9c9c4a9b6902e048dc7456bf6d82e12a80"}, + {file = "msgpack-1.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b16344032a27b2ccfd341f89dadf3e4ef6407d91e4b93563c14644a8abb3ad7"}, + {file = "msgpack-1.0.6-cp311-cp311-win32.whl", hash = "sha256:55bb4a1bf94e39447bc08238a2fb8a767460388a8192f67c103442eb36920887"}, + {file = "msgpack-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:ae97504958d0bc58c1152045c170815d5c4f8af906561ce044b6358b43d0c97e"}, + {file = "msgpack-1.0.6-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ecf431786019a7bfedc28281531d706627f603e3691d64eccdbce3ecd353823"}, + {file = "msgpack-1.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a635aecf1047255576dbb0927cbf9a7aa4a68e9d54110cc3c926652d18f144e0"}, + {file = "msgpack-1.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:102cfb54eaefa73e8ca1e784b9352c623524185c98e057e519545131a56fb0af"}, + {file = "msgpack-1.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c5e05e4f5756758c58a8088aa10dc70d851c89f842b611fdccfc0581c1846bc"}, + {file = "msgpack-1.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68569509dd015fcdd1e6b2b3ccc8c51fd27d9a97f461ccc909270e220ee09685"}, + {file = "msgpack-1.0.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf652839d16de91fe1cfb253e0a88db9a548796939533894e07f45d4bdf90a5f"}, + {file = "msgpack-1.0.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14db7e1b7a7ed362b2f94897bf2486c899c8bb50f6e34b2db92fe534cdab306f"}, + {file = "msgpack-1.0.6-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:159cfec18a6e125dd4723e2b1de6f202b34b87c850fb9d509acfd054c01135e9"}, + {file = "msgpack-1.0.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6a01a072b2219b65a6ff74df208f20b2cac9401c60adb676ee34e53b4c651077"}, + {file = "msgpack-1.0.6-cp312-cp312-win32.whl", hash = "sha256:e36560d001d4ba469d469b02037f2dd404421fd72277d9474efe9f03f83fced5"}, + {file = "msgpack-1.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:5e7fae9ca93258a956551708cf60dc6c8145574e32ce8c8c4d894e63bcb04341"}, + {file = "msgpack-1.0.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:40b801b768f5a765e33c68f30665d3c6ee1c8623a2d2bb78e6e59f2db4e4ceb7"}, + {file = "msgpack-1.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da057d3652e698b00746e47f06dbb513314f847421e857e32e1dc61c46f6c052"}, + {file = "msgpack-1.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f75114c05ec56566da6b55122791cf5bb53d5aada96a98c016d6231e03132f76"}, + {file = "msgpack-1.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61213482b5a387ead9e250e9e3cb290292feca39dc83b41c3b1b7b8ffc8d8ecb"}, + {file = "msgpack-1.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae6c561f11b444b258b1b4be2bdd1e1cf93cd1d80766b7e869a79db4543a8a8"}, + {file = "msgpack-1.0.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:619a63753ba9e792fe3c6c0fc2b9ee2cfbd92153dd91bee029a89a71eb2942cd"}, + {file = "msgpack-1.0.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:70843788c85ca385846a2d2f836efebe7bb2687ca0734648bf5c9dc6c55602d2"}, + {file = "msgpack-1.0.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fb4571efe86545b772a4630fee578c213c91cbcfd20347806e47fd4e782a18fe"}, + {file = "msgpack-1.0.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bbb4448a05d261fae423d5c0b0974ad899f60825bc77eabad5a0c518e78448c2"}, + {file = "msgpack-1.0.6-cp38-cp38-win32.whl", hash = "sha256:5cd67674db3c73026e0a2c729b909780e88bd9cbc8184256f9567640a5d299a8"}, + {file = "msgpack-1.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:a1cf98afa7ad5e7012454ca3fde254499a13f9d92fd50cb46118118a249a1355"}, + {file = "msgpack-1.0.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d6d25b8a5c70e2334ed61a8da4c11cd9b97c6fbd980c406033f06e4463fda006"}, + {file = "msgpack-1.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88cdb1da7fdb121dbb3116910722f5acab4d6e8bfcacab8fafe27e2e7744dc6a"}, + {file = "msgpack-1.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b5658b1f9e486a2eec4c0c688f213a90085b9cf2fec76ef08f98fdf6c62f4b9"}, + {file = "msgpack-1.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76820f2ece3b0a7c948bbb6a599020e29574626d23a649476def023cbb026787"}, + {file = "msgpack-1.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c780d992f5d734432726b92a0c87bf1857c3d85082a8dea29cbf56e44a132b3"}, + {file = "msgpack-1.0.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0ed35d6d6122d0baa9a1b59ebca4ee302139f4cfb57dab85e4c73ab793ae7ed"}, + {file = "msgpack-1.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32c0aff31f33033f4961abc01f78497e5e07bac02a508632aef394b384d27428"}, + {file = "msgpack-1.0.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:35ad5aed9b52217d4cea739d0ea3a492a18dd86fecb4b132668a69f27fb0363b"}, + {file = "msgpack-1.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47275ff73005a3e5e146e50baa2378e1730cba6e292f0222bc496a8e4c4adfc8"}, + {file = "msgpack-1.0.6-cp39-cp39-win32.whl", hash = "sha256:7baf16fd8908a025c4a8d7b699103e72d41f967e2aee5a2065432bcdbd9fd06e"}, + {file = "msgpack-1.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:fc97aa4b4fb928ff4d3b74da7c30b360d0cb3ede49a5a6e1fd9705f49aea1deb"}, + {file = "msgpack-1.0.6.tar.gz", hash = "sha256:25d3746da40f3c8c59c3b1d001e49fd2aa17904438f980d9a391370366df001e"}, ] [[package]] @@ -1803,55 +1813,140 @@ files = [ [[package]] name = "pydantic" -version = "1.10.6" -description = "Data validation and settings management using python type hints" +version = "2.4.1" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9289065611c48147c1dd1fd344e9d57ab45f1d99b0fb26c51f1cf72cd9bcd31"}, - {file = "pydantic-1.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c32b6bba301490d9bb2bf5f631907803135e8085b6aa3e5fe5a770d46dd0160"}, - {file = "pydantic-1.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd9b9e98068fa1068edfc9eabde70a7132017bdd4f362f8b4fd0abed79c33083"}, - {file = "pydantic-1.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c84583b9df62522829cbc46e2b22e0ec11445625b5acd70c5681ce09c9b11c4"}, - {file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b41822064585fea56d0116aa431fbd5137ce69dfe837b599e310034171996084"}, - {file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61f1f08adfaa9cc02e0cbc94f478140385cbd52d5b3c5a657c2fceb15de8d1fb"}, - {file = "pydantic-1.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:32937835e525d92c98a1512218db4eed9ddc8f4ee2a78382d77f54341972c0e7"}, - {file = "pydantic-1.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd5c531b22928e63d0cb1868dee76123456e1de2f1cb45879e9e7a3f3f1779b"}, - {file = "pydantic-1.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e277bd18339177daa62a294256869bbe84df1fb592be2716ec62627bb8d7c81d"}, - {file = "pydantic-1.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f15277d720aa57e173954d237628a8d304896364b9de745dcb722f584812c7"}, - {file = "pydantic-1.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b243b564cea2576725e77aeeda54e3e0229a168bc587d536cd69941e6797543d"}, - {file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3ce13a558b484c9ae48a6a7c184b1ba0e5588c5525482681db418268e5f86186"}, - {file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ac1cd4deed871dfe0c5f63721e29debf03e2deefa41b3ed5eb5f5df287c7b70"}, - {file = "pydantic-1.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:b1eb6610330a1dfba9ce142ada792f26bbef1255b75f538196a39e9e90388bf4"}, - {file = "pydantic-1.10.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4ca83739c1263a044ec8b79df4eefc34bbac87191f0a513d00dd47d46e307a65"}, - {file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea4e2a7cb409951988e79a469f609bba998a576e6d7b9791ae5d1e0619e1c0f2"}, - {file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53de12b4608290992a943801d7756f18a37b7aee284b9ffa794ee8ea8153f8e2"}, - {file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:60184e80aac3b56933c71c48d6181e630b0fbc61ae455a63322a66a23c14731a"}, - {file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:415a3f719ce518e95a92effc7ee30118a25c3d032455d13e121e3840985f2efd"}, - {file = "pydantic-1.10.6-cp37-cp37m-win_amd64.whl", hash = "sha256:72cb30894a34d3a7ab6d959b45a70abac8a2a93b6480fc5a7bfbd9c935bdc4fb"}, - {file = "pydantic-1.10.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3091d2eaeda25391405e36c2fc2ed102b48bac4b384d42b2267310abae350ca6"}, - {file = "pydantic-1.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:751f008cd2afe812a781fd6aa2fb66c620ca2e1a13b6a2152b1ad51553cb4b77"}, - {file = "pydantic-1.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12e837fd320dd30bd625be1b101e3b62edc096a49835392dcf418f1a5ac2b832"}, - {file = "pydantic-1.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d92831d0115874d766b1f5fddcdde0c5b6c60f8c6111a394078ec227fca6d"}, - {file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:476f6674303ae7965730a382a8e8d7fae18b8004b7b69a56c3d8fa93968aa21c"}, - {file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a2be0a0f32c83265fd71a45027201e1278beaa82ea88ea5b345eea6afa9ac7f"}, - {file = "pydantic-1.10.6-cp38-cp38-win_amd64.whl", hash = "sha256:0abd9c60eee6201b853b6c4be104edfba4f8f6c5f3623f8e1dba90634d63eb35"}, - {file = "pydantic-1.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6195ca908045054dd2d57eb9c39a5fe86409968b8040de8c2240186da0769da7"}, - {file = "pydantic-1.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43cdeca8d30de9a897440e3fb8866f827c4c31f6c73838e3a01a14b03b067b1d"}, - {file = "pydantic-1.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c19eb5163167489cb1e0161ae9220dadd4fc609a42649e7e84a8fa8fff7a80f"}, - {file = "pydantic-1.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62"}, - {file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:528dcf7ec49fb5a84bf6fe346c1cc3c55b0e7603c2123881996ca3ad79db5bfc"}, - {file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:163e79386c3547c49366e959d01e37fc30252285a70619ffc1b10ede4758250a"}, - {file = "pydantic-1.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:189318051c3d57821f7233ecc94708767dd67687a614a4e8f92b4a020d4ffd06"}, - {file = "pydantic-1.10.6-py3-none-any.whl", hash = "sha256:acc6783751ac9c9bc4680379edd6d286468a1dc8d7d9906cd6f1186ed682b2b0"}, - {file = "pydantic-1.10.6.tar.gz", hash = "sha256:cf95adb0d1671fc38d8c43dd921ad5814a735e7d9b4d9e437c088002863854fd"}, + {file = "pydantic-2.4.1-py3-none-any.whl", hash = "sha256:2b2240c8d54bb8f84b88e061fac1bdfa1761c2859c367f9d3afe0ec2966deddc"}, + {file = "pydantic-2.4.1.tar.gz", hash = "sha256:b172505886028e4356868d617d2d1a776d7af1625d1313450fd51bdd19d9d61f"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" @@ -2313,35 +2408,35 @@ urllib3 = ">=1.26.0" [[package]] name = "types-pytz" -version = "2023.3.0.1" +version = "2023.3.1.1" description = "Typing stubs for pytz" optional = false python-versions = "*" files = [ - {file = "types-pytz-2023.3.0.1.tar.gz", hash = "sha256:1a7b8d4aac70981cfa24478a41eadfcd96a087c986d6f150d77e3ceb3c2bdfab"}, - {file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"}, + {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"}, + {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"}, ] [[package]] name = "types-pyyaml" -version = "6.0.12.11" +version = "6.0.12.12" description = "Typing stubs for PyYAML" optional = false python-versions = "*" files = [ - {file = "types-PyYAML-6.0.12.11.tar.gz", hash = "sha256:7d340b19ca28cddfdba438ee638cd4084bde213e501a3978738543e27094775b"}, - {file = "types_PyYAML-6.0.12.11-py3-none-any.whl", hash = "sha256:a461508f3096d1d5810ec5ab95d7eeecb651f3a15b71959999988942063bf01d"}, + {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, + {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, ] [[package]] name = "types-requests" -version = "2.31.0.2" +version = "2.31.0.6" description = "Typing stubs for requests" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "types-requests-2.31.0.2.tar.gz", hash = "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40"}, - {file = "types_requests-2.31.0.2-py3-none-any.whl", hash = "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a"}, + {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, + {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, ] [package.dependencies] @@ -2360,13 +2455,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] @@ -2613,4 +2708,4 @@ exporter-otlp-proto-http = ["opentelemetry-exporter-otlp-proto-http"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "4ad70b49c3a6a8bced1e24947f226e37bad2ffaae7ac58fe759944d5314dc673" +content-hash = "8beb7528e4a1f0e0057ec8268b82fddca69f0fa4fcaad6eb5390e6a9a3c7f506" diff --git a/pyproject.toml b/pyproject.toml index e566a12..d61d673 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ opentelemetry-exporter-otlp-proto-http = { version = "^1.20.0", optional = true opentelemetry-exporter-otlp-proto-grpc = { version = "^1.20.0", optional = true } opentelemetry-sdk = "^1.17.0" prometheus-client = "^0.16.0 || ^0.17.0" +pydantic = "^2.4.1" python = "^3.8" python-dotenv = "^1.0.0" typing-extensions = "^4.5.0" @@ -66,11 +67,13 @@ pytest-xdist = "^3.3.1" mypy = "^1.5.1" twine = "4.0.2" + + [tool.poetry.group.examples] optional = true [tool.poetry.group.examples.dependencies] -anyio = "3.6.2" +anyio = "3.7.1" bleach = "6.0.0" build = "0.10.0" certifi = "2023.7.22" @@ -78,7 +81,7 @@ charset-normalizer = "3.1.0" click = "8.1.3" django = "^4.2" docutils = "0.19" -fastapi = "0.97.0" +fastapi = "^0.103.1" h11 = "0.14.0" idna = "3.4" # pinned importlib-metadat to version ~6.0.0 because of opentelemetry-api @@ -90,7 +93,6 @@ mdurl = "0.1.2" more-itertools = "9.1.0" packaging = "23.0" pkginfo = "1.9.6" -pydantic = "1.10.6" pygments = "2.16.1" pyproject-hooks = "1.0.0" readme-renderer = "37.3" @@ -109,6 +111,7 @@ locust = "^2.15.1" django-stubs = "4.2.3" + [tool.poetry.group.development.dependencies] types-requests = "^2.31.0.2" django-stubs = "^4.2.3" diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index 3040787..e322557 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -1,10 +1,12 @@ -from typing import cast, Dict, Literal, TypedDict, Optional, Union from opentelemetry.sdk.metrics.export import ( AggregationTemporality, MetricReader, PeriodicExportingMetricReader, ) from opentelemetry.exporter.prometheus import PrometheusMetricReader +from pydantic import ConfigDict, TypeAdapter +from typing import Dict, Literal, Optional, Union +from typing_extensions import TypedDict # GRPC is optional so we'll only type it if it's available try: @@ -13,10 +15,16 @@ ChannelCredentials = None -class OTLPExporterOptions(TypedDict): - """Configuration for OTLP exporters.""" +class OtlpGrpcExporterBase(TypedDict): + """Base type for OTLP GRPC exporter configuration.""" - type: Literal["otlp-proto-http", "otlp-proto-grpc"] + type: Literal["otlp-proto-grpc"] + + +class OtlpGrpcExporterOptions(OtlpGrpcExporterBase, total=False): + """Configuration for OTLP GRPC exporter.""" + + __pydantic_config__ = ConfigDict(arbitrary_types_allowed=True) # type: ignore endpoint: str insecure: bool headers: Dict[str, str] @@ -26,36 +34,104 @@ class OTLPExporterOptions(TypedDict): preferred_temporality: Dict[type, AggregationTemporality] -class OTELPrometheusExporterOptions(TypedDict): +OtlpGrpcExporterValidator = TypeAdapter(OtlpGrpcExporterOptions) + + +class OtlpHttpExporterBase(TypedDict): + """Base type for OTLP HTTP exporter configuration.""" + + type: Literal["otlp-proto-http"] + + +class OtlpHttpExporterOptions(OtlpHttpExporterBase, total=False): + """Configuration for OTLP HTTP exporter.""" + + endpoint: str + headers: Dict[str, str] + push_interval: int + timeout: int + preferred_temporality: Dict[type, AggregationTemporality] + + +OtlpHttpExporterValidator = TypeAdapter(OtlpHttpExporterOptions) + + +class OtelPrometheusExporterBase(TypedDict): + """Base type for OTLP Prometheus exporter configuration.""" + type: Literal["otel-prometheus"] + + +class OtelPrometheusExporterOptions(OtelPrometheusExporterBase, total=False): + """Configuration for OpenTelemetry Prometheus exporter.""" + prefix: str -class PrometheusExporterOptions(TypedDict): - """Configuration for Prometheus exporter.""" +OtelPrometheusValidator = TypeAdapter(OtelPrometheusExporterOptions) + + +class OtelCustomExporterBase(TypedDict): + """Base type for OTLP Prometheus exporter configuration.""" + + type: Literal["otel-custom"] + + +class OtelCustomExporterOptions(OtelCustomExporterBase, total=False): + """Configuration for OpenTelemetry Prometheus exporter.""" + + __pydantic_config__ = ConfigDict(arbitrary_types_allowed=True) # type: ignore + exporter: MetricReader + + +OtelCustomValidator = TypeAdapter(OtelCustomExporterOptions) + + +class PrometheusClientExporterBase(TypedDict): + """Base type for Prometheus exporter configuration.""" type: Literal["prometheus-client"] + + +class PrometheusClientExporterOptions(PrometheusClientExporterBase, total=False): + """Configuration for Prometheus exporter.""" + address: str port: int prefix: str +PrometheusExporterValidator = TypeAdapter(PrometheusClientExporterOptions) + + ExporterOptions = Union[ - OTLPExporterOptions, - OTELPrometheusExporterOptions, - PrometheusExporterOptions, - MetricReader, + PrometheusClientExporterOptions, + OtlpGrpcExporterOptions, + OtlpHttpExporterOptions, + OtelPrometheusExporterOptions, + OtelCustomExporterOptions, ] +ExporterOptionsValidator = TypeAdapter(ExporterOptions) -def get_exporter(config: ExporterOptions) -> MetricReader: - if isinstance(config, MetricReader): - return config +def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: + """Create an exporter based on the configuration.""" + if config["type"] == "prometheus-client": + from prometheus_client import start_http_server + + config = PrometheusExporterValidator.validate_python(config) + start_http_server( + config.get("port", 9464), + config.get("address", "0.0.0.0"), + ) + return None if config["type"] == "otel-prometheus": - config = cast(OTELPrometheusExporterOptions, config) - return PrometheusMetricReader(config.get("prefix", "")) + config = OtelPrometheusValidator.validate_python(config) + return PrometheusMetricReader( + config.get("prefix", ""), + ) if config["type"] == "otlp-proto-http": - config = cast(OTLPExporterOptions, config) + config = OtlpHttpExporterValidator.validate_python(config) try: from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( OTLPMetricExporter as OTLPHTTPMetricExporter, @@ -76,11 +152,11 @@ def get_exporter(config: ExporterOptions) -> MetricReader: except ImportError: raise ImportError("OTLP exporter (HTTP) not installed") if config["type"] == "otlp-proto-grpc": - config = cast(OTLPExporterOptions, config) + config = OtlpGrpcExporterValidator.validate_python(config) try: - from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( # type: ignore OTLPMetricExporter as OTLPGRPCMetricExporter, - ) + ) grpc_exporter = OTLPGRPCMetricExporter( endpoint=config.get("endpoint", None), @@ -98,5 +174,8 @@ def get_exporter(config: ExporterOptions) -> MetricReader: return grpc_reader except ImportError: raise ImportError("OTLP exporter (GRPC) not installed") + if config["type"] == "otel-custom": + config = OtelCustomValidator.validate_python(config) + return config.get("exporter", None) else: raise ValueError("Invalid exporter type") diff --git a/src/autometrics/initialization.py b/src/autometrics/initialization.py index 037dd7d..0d99cb4 100644 --- a/src/autometrics/initialization.py +++ b/src/autometrics/initialization.py @@ -4,13 +4,13 @@ from typing_extensions import Unpack -from .tracker import init_tracker, get_tracker, set_tracker +from .tracker import init_tracker, get_tracker from .tracker.temporary import TemporaryTracker from .settings import AutometricsOptions, init_settings has_inited = False -double_init_error = "Cannot call init() more than once." -not_temp_tracker_error = "Expected tracker to be TemporaryTracker." +DOUBLE_INIT_ERROR = "Cannot call init() more than once." +NOT_TEMP_TRACKER_ERROR = "Expected tracker to be TemporaryTracker." def init(**kwargs: Unpack[AutometricsOptions]): @@ -20,18 +20,18 @@ def init(**kwargs: Unpack[AutometricsOptions]): global has_inited if has_inited: if os.environ.get("AUTOMETRICS_DEBUG") == "true": - raise RuntimeError(double_init_error) + raise RuntimeError(DOUBLE_INIT_ERROR) else: - logging.warn(f"{double_init_error} This init() call will be ignored.") + logging.warn(f"{DOUBLE_INIT_ERROR} This init() call will be ignored.") return has_inited = True temp_tracker = get_tracker() if not isinstance(temp_tracker, TemporaryTracker): if os.environ.get("AUTOMETRICS_DEBUG") == "true": - raise RuntimeError(not_temp_tracker_error) + raise RuntimeError(NOT_TEMP_TRACKER_ERROR) else: - logging.warn(f"{not_temp_tracker_error} This init() call will be ignored.") + logging.warn(f"{NOT_TEMP_TRACKER_ERROR} This init() call will be ignored.") return settings = init_settings(**kwargs) tracker = init_tracker(settings["tracker"], settings) diff --git a/src/autometrics/settings.py b/src/autometrics/settings.py index 75f96ef..041e434 100644 --- a/src/autometrics/settings.py +++ b/src/autometrics/settings.py @@ -1,16 +1,10 @@ import os -from opentelemetry.sdk.metrics.export import MetricReader -from typing import Any, Dict, List, TypedDict, Optional, Union +from typing import cast, Dict, List, TypedDict, Optional, Union from typing_extensions import Unpack from .tracker.types import TrackerType -from .exposition import ( - ExporterOptions, - PrometheusExporterOptions, - OTELPrometheusExporterOptions, - OTLPExporterOptions, -) +from .exposition import ExporterOptions from .objectives import ObjectiveLatency @@ -32,7 +26,7 @@ class AutometricsOptions(TypedDict, total=False): histogram_buckets: List[float] tracker: str - exporter: Dict[str, str] + exporter: Dict[str, Union[str, int, bool]] enable_exemplars: bool service_name: str commit: str @@ -59,8 +53,8 @@ def init_settings(**overrides: Unpack[AutometricsOptions]) -> AutometricsSetting ) exporter: Optional[ExporterOptions] = None exporter_option = overrides.get("exporter") - if exporter_option is not None: - exporter = get_exporter_settings(exporter_option) + if exporter_option: + exporter = cast(ExporterOptions, exporter_option) config: AutometricsSettings = { "histogram_buckets": overrides.get("histogram_buckets") @@ -103,50 +97,16 @@ def get_settings() -> AutometricsSettings: def validate_settings(settings: AutometricsSettings): """Ensure that the settings are valid. For example, we don't support Prometheus exporter with OpenTelemetry tracker.""" if settings["exporter"]: - if settings["tracker"] == TrackerType.OPENTELEMETRY and not isinstance( - settings["exporter"], MetricReader - ): - if settings["exporter"]["type"] == "prometheus": + exporter_type = settings["exporter"]["type"] + if settings["tracker"] == TrackerType.OPENTELEMETRY: + if not exporter_type.startswith("otel") and not exporter_type.startswith( + "otlp" + ): raise ValueError( - "Prometheus exporter is not supported with OpenTelemetry tracker" + f"Exporter type {exporter_type} is not supported with OpenTelemetry tracker." ) if settings["tracker"] == TrackerType.PROMETHEUS: - if isinstance(settings["exporter"], MetricReader): + if not exporter_type.startswith("prometheus"): raise ValueError( - "OpenTelemetry exporter is not supported with Prometheus tracker" + f"Exporter type {exporter_type} is not supported with Prometheus tracker." ) - if ( - settings["exporter"]["type"] in ["otlp-proto-http", "otlp-proto-grpc"] - ) or (isinstance(settings["exporter"], MetricReader)): - raise ValueError( - "OTLP exporter is not supported with Prometheus tracker" - ) - - -def get_exporter_settings(exporter_options: Dict[str, Any]) -> ExporterOptions: - """Get the exporter settings from the user supplied options.""" - if exporter_options["type"] == "prometheus-client": - return PrometheusExporterOptions( - type="prometheus-client", - address=exporter_options["address"], - port=exporter_options["port"], - prefix=exporter_options["prefix"], - ) - if ["otlp-proto-http", "otlp-proto-grpc"].count(exporter_options["type"]) > 0: - return OTLPExporterOptions( - type=exporter_options["type"], - endpoint=exporter_options["endpoint"], - insecure=exporter_options["insecure"], - headers=exporter_options["headers"], - credentials=exporter_options["credentials"], - push_interval=exporter_options["push_interval"], - timeout=exporter_options["timeout"], - preferred_temporality=exporter_options["preferred_temporality"], - ) - - if exporter_options["type"] == "otel-prometheus": - return OTELPrometheusExporterOptions( - type="otel-prometheus", - prefix=exporter_options["prefix"], - ) - raise ValueError(f"Unsupported exporter type: {exporter_options['type']}") diff --git a/src/autometrics/test_decorator.py b/src/autometrics/test_decorator.py index 97a6411..d8707f5 100644 --- a/src/autometrics/test_decorator.py +++ b/src/autometrics/test_decorator.py @@ -4,7 +4,7 @@ from typing import Optional, Coroutine from prometheus_client.exposition import generate_latest import pytest -from requests import HTTPError +from requests import HTTPError, Response from .decorator import autometrics from .initialization import init @@ -22,7 +22,9 @@ def basic_http_error_function(status_code: Optional[int] = 404): if status_code is None: return CODE_NOT_SET_TEXT - raise HTTPError(HTTP_ERROR_TEXT, response=status_code) + response = Response() + response.status_code = status_code + raise HTTPError(HTTP_ERROR_TEXT, response=response) async def async_http_error_function(status_code: Optional[int] = 404): @@ -32,7 +34,9 @@ async def async_http_error_function(status_code: Optional[int] = 404): if status_code is None: return CODE_NOT_SET_TEXT - raise HTTPError(HTTP_ERROR_TEXT, response=status_code) + response = Response() + response.status_code = status_code + raise HTTPError(HTTP_ERROR_TEXT, response=response) def basic_function(sleep_duration: float = 0.0): @@ -254,7 +258,7 @@ def record_error_if(result: str): wrapped_function(status_code=404) assert HTTP_ERROR_TEXT in str(exception.value) - assert exception.value.response == 404 + assert exception.value.response.status_code == 404 # get the metrics blob = generate_latest() @@ -309,7 +313,7 @@ def record_error_if(result: str): await wrapped_function(status_code=404) assert HTTP_ERROR_TEXT in str(exception.value) - assert exception.value.response == 404 + assert exception.value.response.status_code == 404 # get the metrics blob = generate_latest() diff --git a/src/autometrics/test_initialization.py b/src/autometrics/test_initialization.py index 18da901..9d946fb 100644 --- a/src/autometrics/test_initialization.py +++ b/src/autometrics/test_initialization.py @@ -1,6 +1,7 @@ import pytest from autometrics import init +from autometrics.exposition import PrometheusClientExporterOptions from autometrics.tracker.opentelemetry import OpenTelemetryTracker from autometrics.tracker.prometheus import PrometheusTracker from autometrics.tracker.tracker import get_tracker @@ -124,3 +125,51 @@ def test_double_init(): init() with pytest.raises(RuntimeError): init() + + +def test_init_with_exporter(): + """Test that setting exporter works correctly""" + init( + tracker="prometheus", + exporter={ + "type": "prometheus-client", + }, + ) + settings = get_settings() + assert settings == { + "histogram_buckets": [ + 0.005, + 0.01, + 0.025, + 0.05, + 0.075, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0, + 2.5, + 5.0, + 7.5, + 10.0, + ], + "enable_exemplars": False, + "tracker": TrackerType.PROMETHEUS, + "exporter": PrometheusClientExporterOptions(type="prometheus-client"), + "service_name": "autometrics", + "commit": "", + "branch": "", + "version": "", + } + tracker = get_tracker() + assert isinstance(tracker, PrometheusTracker) + + +def test_init_exporter_validation(): + with pytest.raises(ValueError): + init( + tracker="opentelemetry", + exporter={ + "type": "prometheus-client", + }, + ) diff --git a/src/autometrics/tracker/tracker.py b/src/autometrics/tracker/tracker.py index 60e1851..c8d3594 100644 --- a/src/autometrics/tracker/tracker.py +++ b/src/autometrics/tracker/tracker.py @@ -3,7 +3,7 @@ from .types import TrackerType, TrackMetrics from .temporary import TemporaryTracker -from ..exposition import PrometheusExporterOptions, get_exporter +from ..exposition import create_exporter from ..settings import AutometricsSettings @@ -34,19 +34,14 @@ def init_tracker( exporter: Optional[MetricReader] = None if settings["exporter"]: - exporter = get_exporter(settings["exporter"]) + exporter = create_exporter(settings["exporter"]) tracker_instance = OpenTelemetryTracker(exporter) elif tracker_type == TrackerType.PROMETHEUS: # pylint: disable=import-outside-toplevel from .prometheus import PrometheusTracker - if settings["exporter"] and not isinstance(settings["exporter"], MetricReader): - from prometheus_client import start_http_server - - if settings["exporter"]["type"] != "prometheus-client": - raise Exception("Invalid exporter type for Prometheus tracker") - exporter_settings = cast(PrometheusExporterOptions, settings["exporter"]) - start_http_server(exporter_settings["port"], exporter_settings["address"]) + if settings["exporter"]: + exporter = create_exporter(settings["exporter"]) tracker_instance = PrometheusTracker() # NOTE - Only set the build info when the tracker is initialized tracker_instance.set_build_info( From de26827d729f1aa2e38e1eb1091533ff0c75e95f Mon Sep 17 00:00:00 2001 From: Brett Beutell Date: Thu, 28 Sep 2023 10:34:51 +0200 Subject: [PATCH 09/15] Fix otel collector config for otlp examples --- configs/otel-collector-config.yaml | 6 +++++- docker-compose.yaml | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configs/otel-collector-config.yaml b/configs/otel-collector-config.yaml index af3788b..6bec501 100644 --- a/configs/otel-collector-config.yaml +++ b/configs/otel-collector-config.yaml @@ -7,6 +7,10 @@ receivers: exporters: logging: loglevel: debug + prometheus: + endpoint: "0.0.0.0:9464" # This is where Prometheus will scrape the metrics from. + # namespace: # Replace with your namespace. + processors: batch: @@ -16,4 +20,4 @@ service: metrics: receivers: [otlp] processors: [] - exporters: [logging] + exporters: [logging, prometheus] diff --git a/docker-compose.yaml b/docker-compose.yaml index 1f56420..e69c6b1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,7 +12,7 @@ services: - "6789:6789" - "9090:9090" container_name: am - command: "start host.docker.internal:9464" + command: "start http://otel-collector:9464/metrics" environment: - LISTEN_ADDRESS=0.0.0.0:6789 restart: unless-stopped @@ -27,6 +27,8 @@ services: ports: - "4317:4317" - "4318:4318" + - "8888:8888" # expose container metrics in prometheus format + - "9464:9464" # expose the collected metrics in prometheus format - "55680:55680" - "55679:55679" restart: unless-stopped From 31c1ef8cffc06a735bb35b1af9164ea4a3c12477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Thu, 28 Sep 2023 15:16:37 +0200 Subject: [PATCH 10/15] Fix otel-prometheus exporter --- examples/export_metrics/otel-prometheus.py | 1 + src/autometrics/exposition.py | 8 +++++++- src/autometrics/settings.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/export_metrics/otel-prometheus.py b/examples/export_metrics/otel-prometheus.py index dbc667b..c814321 100644 --- a/examples/export_metrics/otel-prometheus.py +++ b/examples/export_metrics/otel-prometheus.py @@ -11,6 +11,7 @@ tracker="opentelemetry", exporter={ "type": "otel-prometheus", + "port": 9464, }, service_name="my-service", ) diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index e322557..c7abefb 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -4,6 +4,7 @@ PeriodicExportingMetricReader, ) from opentelemetry.exporter.prometheus import PrometheusMetricReader +from prometheus_client import start_http_server from pydantic import ConfigDict, TypeAdapter from typing import Dict, Literal, Optional, Union from typing_extensions import TypedDict @@ -65,6 +66,8 @@ class OtelPrometheusExporterBase(TypedDict): class OtelPrometheusExporterOptions(OtelPrometheusExporterBase, total=False): """Configuration for OpenTelemetry Prometheus exporter.""" + address: str + port: int prefix: str @@ -117,7 +120,6 @@ class PrometheusClientExporterOptions(PrometheusClientExporterBase, total=False) def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: """Create an exporter based on the configuration.""" if config["type"] == "prometheus-client": - from prometheus_client import start_http_server config = PrometheusExporterValidator.validate_python(config) start_http_server( @@ -127,6 +129,10 @@ def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: return None if config["type"] == "otel-prometheus": config = OtelPrometheusValidator.validate_python(config) + start_http_server( + config.get("port", 9464), + config.get("address", "0.0.0.0"), + ) return PrometheusMetricReader( config.get("prefix", ""), ) diff --git a/src/autometrics/settings.py b/src/autometrics/settings.py index 041e434..b45b219 100644 --- a/src/autometrics/settings.py +++ b/src/autometrics/settings.py @@ -1,6 +1,6 @@ import os -from typing import cast, Dict, List, TypedDict, Optional, Union +from typing import cast, Dict, List, TypedDict, Optional, Any from typing_extensions import Unpack from .tracker.types import TrackerType @@ -26,7 +26,7 @@ class AutometricsOptions(TypedDict, total=False): histogram_buckets: List[float] tracker: str - exporter: Dict[str, Union[str, int, bool]] + exporter: Dict[str, Any] enable_exemplars: bool service_name: str commit: str From e1f8f6f6c1a3ee36d82129ae8241f3ca5a715dae Mon Sep 17 00:00:00 2001 From: Brett Beutell Date: Thu, 28 Sep 2023 19:28:51 +0200 Subject: [PATCH 11/15] Fix examples --- docker-compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index e69c6b1..dcc7482 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,7 +12,7 @@ services: - "6789:6789" - "9090:9090" container_name: am - command: "start http://otel-collector:9464/metrics" + command: "start http://otel-collector:9464/metrics host.docker.internal:9464" environment: - LISTEN_ADDRESS=0.0.0.0:6789 restart: unless-stopped @@ -28,7 +28,6 @@ services: - "4317:4317" - "4318:4318" - "8888:8888" # expose container metrics in prometheus format - - "9464:9464" # expose the collected metrics in prometheus format - "55680:55680" - "55679:55679" restart: unless-stopped From 33bdf360c5d699b7c758e076ce620366f433235b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Thu, 28 Sep 2023 19:40:47 +0200 Subject: [PATCH 12/15] Remove reference until readme refactor --- examples/export_metrics/otlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/export_metrics/otlp.py b/examples/export_metrics/otlp.py index e8fef2b..a5df0c1 100644 --- a/examples/export_metrics/otlp.py +++ b/examples/export_metrics/otlp.py @@ -5,7 +5,7 @@ # This example uses the gRPC transport, available settings are similar to the OpenTelemetry # Python SDK. By default, the OTLP exporter will send metrics to localhost:4317. # If you don't have an OTLP collector running, you can run Tilt or Docker Compose -# to start one up. See the README for more details. +# to start one up. init( exporter={ From 4923dcecfc4e7b65d43ee9d1534400215e5fc3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Thu, 28 Sep 2023 19:44:33 +0200 Subject: [PATCH 13/15] Remove unused validator, add a comment --- src/autometrics/exposition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index c7abefb..60c347e 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -16,6 +16,8 @@ ChannelCredentials = None +# All of these are split into two parts because having +# a wall of Optional[...] is not very readable and Required[...] is 3.11+ class OtlpGrpcExporterBase(TypedDict): """Base type for OTLP GRPC exporter configuration.""" @@ -114,13 +116,11 @@ class PrometheusClientExporterOptions(PrometheusClientExporterBase, total=False) OtelPrometheusExporterOptions, OtelCustomExporterOptions, ] -ExporterOptionsValidator = TypeAdapter(ExporterOptions) def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: """Create an exporter based on the configuration.""" if config["type"] == "prometheus-client": - config = PrometheusExporterValidator.validate_python(config) start_http_server( config.get("port", 9464), @@ -160,9 +160,9 @@ def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: if config["type"] == "otlp-proto-grpc": config = OtlpGrpcExporterValidator.validate_python(config) try: - from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( # type: ignore + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( # type: ignore OTLPMetricExporter as OTLPGRPCMetricExporter, - ) + ) grpc_exporter = OTLPGRPCMetricExporter( endpoint=config.get("endpoint", None), From fa31c6f21994f52adc9e3af8d5469f5821ab3676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Fri, 29 Sep 2023 17:15:41 +0200 Subject: [PATCH 14/15] Fold prometheus exporters into one type --- README.md | 24 ++++++++---- examples/export_metrics/otel-prometheus.py | 2 +- examples/export_metrics/prometheus-client.py | 2 +- src/autometrics/exposition.py | 41 ++++---------------- src/autometrics/settings.py | 11 +----- src/autometrics/test_initialization.py | 10 ++--- 6 files changed, 33 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 900d491..e59d91e 100644 --- a/README.md +++ b/README.md @@ -240,9 +240,9 @@ exemplar collection by setting `AUTOMETRICS_EXEMPLARS=true`. You also need to en ## Exporting metrics -There are multiple ways to export metrics from your application, depending on your setup. You can see examples of how to do this in the [examples/export_metrics](https://github.com/autometrics-dev/autometrics-py/tree/main/examples/export_metrics) directory of this repository. +There are multiple ways to export metrics from your application, depending on your setup. You can see examples of how to do this in the [examples/export_metrics](https://github.com/autometrics-dev/autometrics-py/tree/main/examples/export_metrics) directory. -If you use the `prometheus` tracker, you have two options. +If you want to export metrics to Prometheus, you have two options in case of both `opentelemetry` and `prometheus` trackers: 1. Create a route inside your app and respond with `generate_latest()` @@ -257,21 +257,18 @@ def metrics(): return Response(generate_latest()) ``` -2. Specify `prometheus-client` as the exporter type, and a separate server will be started to expose metrics from your app: +2. Specify `prometheus` as the exporter type, and a separate server will be started to expose metrics from your app: ```python exporter = { - "type": "prometheus-client", + "type": "prometheus", "address": "localhost", "port": 9464 } init(tracker="prometheus", service_name="my-service", exporter=exporter) ``` -For the OpenTelemetry tracker, you have more options, including a custom metric reader. By default, when using this tracker, autometrics -will export metrics to the Prometheus registry via `prometheus-client`, from which you can export via one of the ways described above. - -You can also specify the exporter type to be `otlp-proto-http` or `otlp-proto-grpc`, and metrics will be exported to a remote OpenTelemetry collector via the specified protocol. You will need to install the respective extra dependency in order for this to work, which you can do when you install autometrics: +For the OpenTelemetry tracker, you have more options, including a custom metric reader. You can specify the exporter type to be `otlp-proto-http` or `otlp-proto-grpc`, and metrics will be exported to a remote OpenTelemetry collector via the specified protocol. You will need to install the respective extra dependency in order for this to work, which you can do when you install autometrics: ```sh pip install autometrics[exporter-otlp-proto-http] @@ -289,6 +286,17 @@ exporter = { init(tracker="opentelemetry", service_name="my-service", exporter=exporter) ``` +To use a custom metric reader you can specify the exporter type to be `otel-custom` and provide a custom metric reader: + +```python +my_custom_metric_reader = PrometheusMetricReader("") +exporter = { + "type": "otel-custom", + "reader": my_custom_metric_reader +} +init(tracker="opentelemetry", service_name="my-service", exporter=exporter) +``` + ## Development of the package This package uses [poetry](https://python-poetry.org) as a package manager, with all dependencies separated into three groups: diff --git a/examples/export_metrics/otel-prometheus.py b/examples/export_metrics/otel-prometheus.py index c814321..6ad2fd8 100644 --- a/examples/export_metrics/otel-prometheus.py +++ b/examples/export_metrics/otel-prometheus.py @@ -10,7 +10,7 @@ init( tracker="opentelemetry", exporter={ - "type": "otel-prometheus", + "type": "prometheus", "port": 9464, }, service_name="my-service", diff --git a/examples/export_metrics/prometheus-client.py b/examples/export_metrics/prometheus-client.py index d40045d..a023f80 100644 --- a/examples/export_metrics/prometheus-client.py +++ b/examples/export_metrics/prometheus-client.py @@ -10,7 +10,7 @@ init( tracker="prometheus", exporter={ - "type": "prometheus-client", + "type": "prometheus", "port": 9464, }, service_name="my-service", diff --git a/src/autometrics/exposition.py b/src/autometrics/exposition.py index 60c347e..3d0a254 100644 --- a/src/autometrics/exposition.py +++ b/src/autometrics/exposition.py @@ -59,21 +59,21 @@ class OtlpHttpExporterOptions(OtlpHttpExporterBase, total=False): OtlpHttpExporterValidator = TypeAdapter(OtlpHttpExporterOptions) -class OtelPrometheusExporterBase(TypedDict): +class PrometheusExporterBase(TypedDict): """Base type for OTLP Prometheus exporter configuration.""" - type: Literal["otel-prometheus"] + type: Literal["prometheus"] -class OtelPrometheusExporterOptions(OtelPrometheusExporterBase, total=False): - """Configuration for OpenTelemetry Prometheus exporter.""" +class PrometheusExporterOptions(PrometheusExporterBase, total=False): + """Configuration for Prometheus exporter.""" address: str port: int prefix: str -OtelPrometheusValidator = TypeAdapter(OtelPrometheusExporterOptions) +PrometheusValidator = TypeAdapter(PrometheusExporterOptions) class OtelCustomExporterBase(TypedDict): @@ -92,43 +92,18 @@ class OtelCustomExporterOptions(OtelCustomExporterBase, total=False): OtelCustomValidator = TypeAdapter(OtelCustomExporterOptions) -class PrometheusClientExporterBase(TypedDict): - """Base type for Prometheus exporter configuration.""" - - type: Literal["prometheus-client"] - - -class PrometheusClientExporterOptions(PrometheusClientExporterBase, total=False): - """Configuration for Prometheus exporter.""" - - address: str - port: int - prefix: str - - -PrometheusExporterValidator = TypeAdapter(PrometheusClientExporterOptions) - - ExporterOptions = Union[ - PrometheusClientExporterOptions, OtlpGrpcExporterOptions, OtlpHttpExporterOptions, - OtelPrometheusExporterOptions, + PrometheusExporterOptions, OtelCustomExporterOptions, ] def create_exporter(config: ExporterOptions) -> Optional[MetricReader]: """Create an exporter based on the configuration.""" - if config["type"] == "prometheus-client": - config = PrometheusExporterValidator.validate_python(config) - start_http_server( - config.get("port", 9464), - config.get("address", "0.0.0.0"), - ) - return None - if config["type"] == "otel-prometheus": - config = OtelPrometheusValidator.validate_python(config) + if config["type"] == "prometheus": + config = PrometheusValidator.validate_python(config) start_http_server( config.get("port", 9464), config.get("address", "0.0.0.0"), diff --git a/src/autometrics/settings.py b/src/autometrics/settings.py index b45b219..3841e06 100644 --- a/src/autometrics/settings.py +++ b/src/autometrics/settings.py @@ -95,18 +95,11 @@ def get_settings() -> AutometricsSettings: def validate_settings(settings: AutometricsSettings): - """Ensure that the settings are valid. For example, we don't support Prometheus exporter with OpenTelemetry tracker.""" + """Ensure that the settings are valid. For example, we don't support OpenTelemetry exporters with Prometheus tracker.""" if settings["exporter"]: exporter_type = settings["exporter"]["type"] - if settings["tracker"] == TrackerType.OPENTELEMETRY: - if not exporter_type.startswith("otel") and not exporter_type.startswith( - "otlp" - ): - raise ValueError( - f"Exporter type {exporter_type} is not supported with OpenTelemetry tracker." - ) if settings["tracker"] == TrackerType.PROMETHEUS: - if not exporter_type.startswith("prometheus"): + if exporter_type != "prometheus": raise ValueError( f"Exporter type {exporter_type} is not supported with Prometheus tracker." ) diff --git a/src/autometrics/test_initialization.py b/src/autometrics/test_initialization.py index 9d946fb..3ab6658 100644 --- a/src/autometrics/test_initialization.py +++ b/src/autometrics/test_initialization.py @@ -1,7 +1,7 @@ import pytest from autometrics import init -from autometrics.exposition import PrometheusClientExporterOptions +from autometrics.exposition import PrometheusExporterOptions from autometrics.tracker.opentelemetry import OpenTelemetryTracker from autometrics.tracker.prometheus import PrometheusTracker from autometrics.tracker.tracker import get_tracker @@ -132,7 +132,7 @@ def test_init_with_exporter(): init( tracker="prometheus", exporter={ - "type": "prometheus-client", + "type": "prometheus", }, ) settings = get_settings() @@ -155,7 +155,7 @@ def test_init_with_exporter(): ], "enable_exemplars": False, "tracker": TrackerType.PROMETHEUS, - "exporter": PrometheusClientExporterOptions(type="prometheus-client"), + "exporter": PrometheusExporterOptions(type="prometheus"), "service_name": "autometrics", "commit": "", "branch": "", @@ -168,8 +168,8 @@ def test_init_with_exporter(): def test_init_exporter_validation(): with pytest.raises(ValueError): init( - tracker="opentelemetry", + tracker="prometheus", exporter={ - "type": "prometheus-client", + "type": "otel-custom", }, ) From c0f6ad2ab23851663abd29772d98112d86374450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=A7?= Date: Tue, 3 Oct 2023 13:44:39 +0200 Subject: [PATCH 15/15] Add changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4865741..f1e28d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Added support for `record_error_if` and `record_success_if` +- Added OTLP exporters for OpenTelemetry tracker (#89) ### Changed -- +- [💥 Breaking change] `init` function is now required to be called before using autometrics (#89) +- Prometheus exporters are now configured via `init` function (#89) ### Deprecated @@ -32,7 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Security -- +- Updated FastAPI and Pydantic dependencies in the examples group (#89) ## [0.9](https://github.com/autometrics-dev/autometrics-py/releases/tag/0.8) - 2023-07-24