This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-python.git


The following commit(s) were added to refs/heads/master by this push:
     new 320ef70  feat: add sw_grpc plugin (#362)
320ef70 is described below

commit 320ef706ddeb1cbf6230bb5adf153ed71ad211a6
Author: Tsonglew <[email protected]>
AuthorDate: Mon Nov 18 10:16:40 2024 +0800

    feat: add sw_grpc plugin (#362)
---
 CHANGELOG.md                                       |   3 +
 docs/en/setup/Configuration.md                     |   1 +
 docs/en/setup/Plugins.md                           |   1 +
 poetry.lock                                        | 366 ++++++++--------
 pyproject.toml                                     |   1 +
 skywalking/__init__.py                             |   3 +-
 skywalking/config.py                               |  11 +-
 skywalking/meter/pvm/data_source.py                |   2 +-
 skywalking/plugins/sw_grpc.py                      | 477 +++++++++++++++++++++
 skywalking/trace/context.py                        |   2 +-
 skywalking/trace/tags.py                           |  13 +
 .../plugin/web/sw_grpc/__init__.py                 |  10 -
 tests/plugin/web/sw_grpc/docker-compose.yml        | 114 +++++
 tests/plugin/web/sw_grpc/example.proto             |  41 ++
 tests/plugin/web/sw_grpc/expected.data.yml         | 384 +++++++++++++++++
 .../plugin/web/sw_grpc/services/__init__.py        |  10 -
 tests/plugin/web/sw_grpc/services/aio_consumer.py  |  54 +++
 tests/plugin/web/sw_grpc/services/aio_provider.py  |  66 +++
 tests/plugin/web/sw_grpc/services/consumer.py      |  50 +++
 tests/plugin/web/sw_grpc/services/provider.py      |  57 +++
 .../plugin/web/sw_grpc/test_grpc.py                |  31 +-
 tools/config_doc_gen.py                            |   2 +-
 22 files changed, 1484 insertions(+), 215 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a943610..190b400 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@
   - Drop support for 3.7 (#356)
   - Support sampling rate setup. Provide `SW_SAMPLE_N_PER_3_SECS` environment 
variable to control it (#357)
 
+- Plugins:
+  - Add gRPC plugin (#362)
+
 - Fixes:
   - Fix: user/password replacement is not allowed for relative URLs (#349)
   - Fix pulsar client does not support init arguments other than service_url 
(#351)
diff --git a/docs/en/setup/Configuration.md b/docs/en/setup/Configuration.md
index c8c1658..42362d9 100644
--- a/docs/en/setup/Configuration.md
+++ b/docs/en/setup/Configuration.md
@@ -97,6 +97,7 @@ export SW_AGENT_YourConfiguration=YourValue
 | plugin_fastapi_collect_http_params | SW_PLUGIN_FASTAPI_COLLECT_HTTP_PARAMS | 
<class 'bool'> | False | This config item controls that whether the FastAPI 
plugin should collect the parameters of the request. |
 | plugin_bottle_collect_http_params | SW_PLUGIN_BOTTLE_COLLECT_HTTP_PARAMS | 
<class 'bool'> | False | This config item controls that whether the Bottle 
plugin should collect the parameters of the request. |
 | plugin_celery_parameters_length | SW_PLUGIN_CELERY_PARAMETERS_LENGTH | 
<class 'int'> | 512 | The maximum length of `celery` functions parameters, 
longer than this will be truncated, 0 turns off |
+| plugin_grpc_ignored_methods | SW_PLUGIN_GRPC_IGNORED_METHODS | <class 'str'> 
|  | Comma-delimited list of user-defined grpc methods to ignore, like 
/package.Service/Method1,/package.Service/Method2 |
 ###  Sampling Configurations
 | Configuration | Environment Variable | Type | Default Value | Description |
 | :------------ | :------------ | :------------ | :------------ | 
:------------ |
diff --git a/docs/en/setup/Plugins.md b/docs/en/setup/Plugins.md
index 4cb5891..af4c017 100644
--- a/docs/en/setup/Plugins.md
+++ b/docs/en/setup/Plugins.md
@@ -26,6 +26,7 @@ or a limitation of SkyWalking auto-instrumentation (welcome 
to contribute!)
 | [hug](https://falcon.readthedocs.io/en/stable/) | Python >=3.11 - NOT 
SUPPORTED YET; Python >=3.10 - ['2.5', '2.6']; Python >=3.7 - ['2.4.1', '2.5', 
'2.6'];  | `sw_falcon` |
 | [fastapi](https://fastapi.tiangolo.com) | Python >=3.7 - ['0.89.*', 
'0.88.*'];  | `sw_fastapi` |
 | [flask](https://flask.palletsprojects.com) | Python >=3.7 - ['2.0'];  | 
`sw_flask` |
+| [grpcio](https://grpc.io/docs/languages/python) | Python >=3.8 - ['1.*'];  | 
`sw_grpc` |
 | [happybase](https://happybase.readthedocs.io) | Python >=3.7 - ['1.2.0'];  | 
`sw_happybase` |
 | [http_server](https://docs.python.org/3/library/http.server.html) | Python 
>=3.7 - ['*'];  | `sw_http_server` |
 | [werkzeug](https://werkzeug.palletsprojects.com/) | Python >=3.7 - ['1.0.1', 
'2.0'];  | `sw_http_server` |
diff --git a/poetry.lock b/poetry.lock
index 32b03ff..01a14b4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -149,13 +149,13 @@ yarl = "*"
 
 [[package]]
 name = "amqp"
-version = "5.2.0"
+version = "5.3.1"
 description = "Low-level AMQP client for Python (fork of amqplib)."
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "amqp-5.2.0-py3-none-any.whl", hash = 
"sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"},
-    {file = "amqp-5.2.0.tar.gz", hash = 
"sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"},
+    {file = "amqp-5.3.1-py3-none-any.whl", hash = 
"sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2"},
+    {file = "amqp-5.3.1.tar.gz", hash = 
"sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432"},
 ]
 
 [package.dependencies]
@@ -1329,137 +1329,137 @@ test = ["objgraph", "psutil"]
 
 [[package]]
 name = "grpcio"
-version = "1.67.1"
+version = "1.68.0"
 description = "HTTP/2-based RPC framework"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = 
"sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f"},
-    {file = "grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = 
"sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d"},
-    {file = "grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = 
"sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f"},
-    {file = 
"grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0"},
-    {file = 
"grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa"},
-    {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292"},
-    {file = "grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311"},
-    {file = "grpcio-1.67.1-cp310-cp310-win32.whl", hash = 
"sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed"},
-    {file = "grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = 
"sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e"},
-    {file = "grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = 
"sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb"},
-    {file = "grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = 
"sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e"},
-    {file = "grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = 
"sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f"},
-    {file = 
"grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc"},
-    {file = 
"grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96"},
-    {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f"},
-    {file = "grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970"},
-    {file = "grpcio-1.67.1-cp311-cp311-win32.whl", hash = 
"sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744"},
-    {file = "grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = 
"sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5"},
-    {file = "grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = 
"sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953"},
-    {file = "grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = 
"sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb"},
-    {file = "grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = 
"sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0"},
-    {file = 
"grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af"},
-    {file = 
"grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e"},
-    {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75"},
-    {file = "grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38"},
-    {file = "grpcio-1.67.1-cp312-cp312-win32.whl", hash = 
"sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78"},
-    {file = "grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = 
"sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc"},
-    {file = "grpcio-1.67.1-cp313-cp313-linux_armv7l.whl", hash = 
"sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b"},
-    {file = "grpcio-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", hash = 
"sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1"},
-    {file = "grpcio-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = 
"sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af"},
-    {file = 
"grpcio-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955"},
-    {file = 
"grpcio-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8"},
-    {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = 
"sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62"},
-    {file = "grpcio-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = 
"sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb"},
-    {file = "grpcio-1.67.1-cp313-cp313-win32.whl", hash = 
"sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121"},
-    {file = "grpcio-1.67.1-cp313-cp313-win_amd64.whl", hash = 
"sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba"},
-    {file = "grpcio-1.67.1-cp38-cp38-linux_armv7l.whl", hash = 
"sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65"},
-    {file = "grpcio-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = 
"sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426"},
-    {file = "grpcio-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = 
"sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab"},
-    {file = 
"grpcio-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085"},
-    {file = 
"grpcio-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3"},
-    {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8"},
-    {file = "grpcio-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce"},
-    {file = "grpcio-1.67.1-cp38-cp38-win32.whl", hash = 
"sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46"},
-    {file = "grpcio-1.67.1-cp38-cp38-win_amd64.whl", hash = 
"sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771"},
-    {file = "grpcio-1.67.1-cp39-cp39-linux_armv7l.whl", hash = 
"sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335"},
-    {file = "grpcio-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = 
"sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e"},
-    {file = "grpcio-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = 
"sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8"},
-    {file = 
"grpcio-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d"},
-    {file = 
"grpcio-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04"},
-    {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8"},
-    {file = "grpcio-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f"},
-    {file = "grpcio-1.67.1-cp39-cp39-win32.whl", hash = 
"sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e"},
-    {file = "grpcio-1.67.1-cp39-cp39-win_amd64.whl", hash = 
"sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98"},
-    {file = "grpcio-1.67.1.tar.gz", hash = 
"sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732"},
-]
-
-[package.extras]
-protobuf = ["grpcio-tools (>=1.67.1)"]
+    {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = 
"sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"},
+    {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = 
"sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"},
+    {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = 
"sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"},
+    {file = 
"grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"},
+    {file = 
"grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"},
+    {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"},
+    {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"},
+    {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = 
"sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"},
+    {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = 
"sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"},
+    {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = 
"sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"},
+    {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = 
"sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"},
+    {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = 
"sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"},
+    {file = 
"grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"},
+    {file = 
"grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"},
+    {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"},
+    {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"},
+    {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = 
"sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"},
+    {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = 
"sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"},
+    {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = 
"sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"},
+    {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = 
"sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"},
+    {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = 
"sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"},
+    {file = 
"grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"},
+    {file = 
"grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"},
+    {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"},
+    {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"},
+    {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = 
"sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"},
+    {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = 
"sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"},
+    {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = 
"sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"},
+    {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = 
"sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"},
+    {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = 
"sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"},
+    {file = 
"grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"},
+    {file = 
"grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"},
+    {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = 
"sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"},
+    {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = 
"sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"},
+    {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = 
"sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"},
+    {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = 
"sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"},
+    {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = 
"sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"},
+    {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = 
"sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"},
+    {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = 
"sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"},
+    {file = 
"grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"},
+    {file = 
"grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"},
+    {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"},
+    {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"},
+    {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = 
"sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"},
+    {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = 
"sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"},
+    {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = 
"sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"},
+    {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = 
"sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"},
+    {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = 
"sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"},
+    {file = 
"grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = 
"sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"},
+    {file = 
"grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash 
= "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"},
+    {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"},
+    {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"},
+    {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = 
"sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"},
+    {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = 
"sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"},
+    {file = "grpcio-1.68.0.tar.gz", hash = 
"sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.68.0)"]
 
 [[package]]
 name = "grpcio-tools"
-version = "1.67.1"
+version = "1.68.0"
 description = "Protobuf code generator for gRPC"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "grpcio_tools-1.67.1-cp310-cp310-linux_armv7l.whl", hash = 
"sha256:c701aaa51fde1f2644bd94941aa94c337adb86f25cd03cf05e37387aaea25800"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash 
= "sha256:6a722bba714392de2386569c40942566b83725fa5c5450b8910e3832a5379469"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash 
= "sha256:0c7415235cb154e40b5ae90e2a172a0eb8c774b6876f53947cf0af05c983d549"},
-    {file = 
"grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:6a4c459098c4934f9470280baf9ff8b38c365e147f33c8abc26039a948a664a5"},
-    {file = 
"grpcio_tools-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:e89bf53a268f55c16989dab1cf0b32a5bff910762f138136ffad4146129b7a10"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:f09cb3e6bcb140f57b878580cf3b848976f67faaf53d850a7da9bfac12437068"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:616dd0c6686212ca90ff899bb37eb774798677e43dc6f78c6954470782d37399"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-win32.whl", hash = 
"sha256:58a66dbb3f0fef0396737ac09d6571a7f8d96a544ce3ed04c161f3d4fa8d51cc"},
-    {file = "grpcio_tools-1.67.1-cp310-cp310-win_amd64.whl", hash = 
"sha256:89ee7c505bdf152e67c2cced6055aed4c2d4170f53a2b46a7e543d3b90e7b977"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-linux_armv7l.whl", hash = 
"sha256:6d80ddd87a2fb7131d242f7d720222ef4f0f86f53ec87b0a6198c343d8e4a86e"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash 
= "sha256:b655425b82df51f3bd9fd3ba1a6282d5c9ce1937709f059cb3d419b224532d89"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash 
= "sha256:250241e6f9d20d0910a46887dfcbf2ec9108efd3b48f3fb95bb42d50d09d03f8"},
-    {file = 
"grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:6008f5a5add0b6f03082edb597acf20d5a9e4e7c55ea1edac8296c19e6a0ec8d"},
-    {file = 
"grpcio_tools-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:5eff9818c3831fa23735db1fa39aeff65e790044d0a312260a0c41ae29cc2d9e"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:262ab7c40113f8c3c246e28e369661ddf616a351cb34169b8ba470c9a9c3b56f"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:1eebd8c746adf5786fa4c3056258c21cc470e1eca51d3ed23a7fb6a697fe4e81"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-win32.whl", hash = 
"sha256:3eff92fb8ca1dd55e3af0ef02236c648921fb7d0e8ca206b889585804b3659ae"},
-    {file = "grpcio_tools-1.67.1-cp311-cp311-win_amd64.whl", hash = 
"sha256:1ed18281ee17e5e0f9f6ce0c6eb3825ca9b5a0866fc1db2e17fab8aca28b8d9f"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-linux_armv7l.whl", hash = 
"sha256:bd5caef3a484e226d05a3f72b2d69af500dca972cf434bf6b08b150880166f0b"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash 
= "sha256:48a2d63d1010e5b218e8e758ecb2a8d63c0c6016434e9f973df1c3558917020a"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash 
= "sha256:baa64a6aa009bffe86309e236c81b02cd4a88c1ebd66f2d92e84e9b97a9ae857"},
-    {file = 
"grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:4ab318c40b5e3c097a159035fc3e4ecfbe9b3d2c9de189e55468b2c27639a6ab"},
-    {file = 
"grpcio_tools-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:50eba3e31f9ac1149463ad9182a37349850904f142cffbd957cd7f54ec320b8e"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:de6fbc071ecc4fe6e354a7939202191c1f1abffe37fbce9b08e7e9a5b93eba3d"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:db9e87f6ea4b0ce99b2651203480585fd9e8dd0dd122a19e46836e93e3a1b749"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-win32.whl", hash = 
"sha256:6a595a872fb720dde924c4e8200f41d5418dd6baab8cc1a3c1e540f8f4596351"},
-    {file = "grpcio_tools-1.67.1-cp312-cp312-win_amd64.whl", hash = 
"sha256:92eebb9b31031604ae97ea7657ae2e43149b0394af7117ad7e15894b6cc136dc"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-linux_armv7l.whl", hash = 
"sha256:9a3b9510cc87b6458b05ad49a6dee38df6af37f9ee6aa027aa086537798c3d4a"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-macosx_10_13_universal2.whl", 
hash = 
"sha256:9e4c9b9fa9b905f15d414cb7bd007ba7499f8907bdd21231ab287a86b27da81a"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash 
= "sha256:e11a98b41af4bc88b7a738232b8fa0306ad82c79fa5d7090bb607f183a57856f"},
-    {file = 
"grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:de0fcfe61c26679d64b1710746f2891f359593f76894fcf492c37148d5694f00"},
-    {file = 
"grpcio_tools-1.67.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:7ae3b3e2ee5aad59dece65a613624c46a84c9582fc3642686537c6dfae8e47dc"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_i686.whl", hash = 
"sha256:9a630f83505b6471a3094a7a372a1240de18d0cd3e64f4fbf46b361bac2be65b"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = 
"sha256:d85a1fcbacd3e08dc2b3d1d46b749351a9a50899fa35cf2ff040e1faf7d405ad"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-win32.whl", hash = 
"sha256:778470f025f25a1fca5a48c93c0a18af395b46b12dd8df7fca63736b85181f41"},
-    {file = "grpcio_tools-1.67.1-cp313-cp313-win_amd64.whl", hash = 
"sha256:6961da86e9856b4ddee0bf51ef6636b4bf9c29c0715aa71f3c8f027c45d42654"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-linux_armv7l.whl", hash = 
"sha256:c088dfbbe289bb171ca9c98fabbf7ecc8c1c51af2ba384ef32a4fdcb784b17e9"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-macosx_10_9_universal2.whl", hash = 
"sha256:11ce546daf8f8c04ee8d4a1673b4754cda4a0a9d505d820efd636e37f46b50c5"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = 
"sha256:83fecb2f6119ef0eea68a091964898418c1969375d399956ff8d1741beb7b081"},
-    {file = 
"grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:d39c1aa6b26e2602d815b9cfa37faba48b2889680ae6baa002560cf0f0c69fac"},
-    {file = 
"grpcio_tools-1.67.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:e975dc9fb61a77d88e739eb17b3361f369d03cc754217f02dd83ec7cfac32e38"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:6c6e5c5b15f2eedc2a81268d588d14a79a52020383bf87b3c7595df7b571504a"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:a974e0ce01806adba718e6eb8c385defe6805b18969b6914da7db55fb055ae45"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-win32.whl", hash = 
"sha256:35e9b0a82be9f425aa67ee1dc69ba02cf135aeee3f22c0455c5d1b01769bbdb4"},
-    {file = "grpcio_tools-1.67.1-cp38-cp38-win_amd64.whl", hash = 
"sha256:0436c97f29e654d2eccd7419907ee019caf7eea6bdc6ae91d98011f6c5f44f17"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-linux_armv7l.whl", hash = 
"sha256:718fbb6d68a3d000cb3cf381642660eade0e8c1b0bf7472b84b3367f5b56171d"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-macosx_10_9_universal2.whl", hash = 
"sha256:062887d2e9cb8bc261c21a2b8da714092893ce62b4e072775eaa9b24dcbf3b31"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = 
"sha256:59dbf14a1ce928bf03a58fa157034374411159ab5d32ad83cf146d9400eed618"},
-    {file = 
"grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:ac552fc9c76d50408d7141e1fd1eae69d85fbf7ae71da4d8877eaa07127fbe74"},
-    {file = 
"grpcio_tools-1.67.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:5c6583773400e441dc62d08b5a32357babef1a9f9f73c3ac328a75af550815a9"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:862108f90f2f6408908e5ea4584c5104f7caf419c6d73aa3ff36bf8284cca224"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:587c6326425f37dca2291f46b93e446c07ee781cea27725865b806b7a049ec56"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-win32.whl", hash = 
"sha256:d7d46a4405bd763525215b6e073888386587aef9b4a5ec125bf97ba897ac757d"},
-    {file = "grpcio_tools-1.67.1-cp39-cp39-win_amd64.whl", hash = 
"sha256:e2fc7980e8bab3ee5ab98b6fdc2a8fbaa4785f196d897531346176fda49a605c"},
-    {file = "grpcio_tools-1.67.1.tar.gz", hash = 
"sha256:d9657f5ddc62b52f58904e6054b7d8a8909ed08a1e28b734be3a707087bcf004"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-linux_armv7l.whl", hash = 
"sha256:9509a5c3ed3d54fa7ac20748d501cb86668f764605a0a68f275339ee0f1dc1a6"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash 
= "sha256:59a885091bf29700ba0e14a954d156a18714caaa2006a7f328b18e1ac4b1e721"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash 
= "sha256:d3e678162e1d7a8720dc05fdd537fc8df082a50831791f7bb1c6f90095f8368b"},
+    {file = 
"grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:10d03e3ad4af6284fd27cb14f5a3d52045913c1253e3e24a384ed91bc8adbfcd"},
+    {file = 
"grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:1769d7f529de1cc102f7fb900611e3c0b69bdb244fca1075b24d6e5b49024586"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:88640d95ee41921ac7352fa5fadca52a06d7e21fbe53e6a706a9a494f756be7d"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:e903d07bc65232aa9e7704c829aec263e1e139442608e473d7912417a9908e29"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-win32.whl", hash = 
"sha256:66b70b37184d40806844f51c2757c6b852511d4ea46a3bf2c7e931a47b455bc6"},
+    {file = "grpcio_tools-1.68.0-cp310-cp310-win_amd64.whl", hash = 
"sha256:b47ae076ffb29a68e517bc03552bef0d9c973f8e18adadff180b123e973a26ea"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-linux_armv7l.whl", hash = 
"sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash 
= "sha256:8fefc6d000e169a97336feded23ce614df3fb9926fc48c7a9ff8ea459d93b5b0"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash 
= "sha256:6dd69c9f3ff85eee8d1f71adf7023c638ca8d465633244ac1b7f19bc3668612d"},
+    {file = 
"grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:7dc5195dc02057668cc22da1ff1aea1811f6fa0deb801b3194dec1fe0bab1cf0"},
+    {file = 
"grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:849b12bec2320e49e988df104c92217d533e01febac172a4495caab36d9f0edc"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:766c2cd2e365e0fc0e559af56f2c2d144d95fd7cb8668a34d533e66d6435eb34"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:2ec3a2e0afa4866ccc5ba33c071aebaa619245dfdd840cbb74f2b0591868d085"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-win32.whl", hash = 
"sha256:80b733014eb40d920d836d782e5cdea0dcc90d251a2ffb35ab378ef4f8a42c14"},
+    {file = "grpcio_tools-1.68.0-cp311-cp311-win_amd64.whl", hash = 
"sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-linux_armv7l.whl", hash = 
"sha256:dd9a654af8536b3de8525bff72a245fef62d572eabf96ac946fe850e707cb27d"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash 
= "sha256:0f77957e3a0916a0dd18d57ce6b49d95fc9a5cfed92310f226339c0fda5394f6"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash 
= "sha256:92a09afe64fe26696595de2036e10967876d26b12c894cc9160f00152cacebe7"},
+    {file = 
"grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:28ebdbad2ef16699d07400b65260240851049a75502eff69a59b127d3ab960f1"},
+    {file = 
"grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:5d3150d784d8050b10dcf5eb06e04fb90747a1547fed3a062a608d940fe57066"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:261d98fd635595de42aadee848f9af46da6654d63791c888891e94f66c5d0682"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:061345c0079b9471f32230186ab01acb908ea0e577bc1699a8cf47acef8be4af"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-win32.whl", hash = 
"sha256:533ce6791a5ba21e35d74c6c25caf4776f5692785a170c01ea1153783ad5af31"},
+    {file = "grpcio_tools-1.68.0-cp312-cp312-win_amd64.whl", hash = 
"sha256:56842a0ce74b4b92eb62cd5ee00181b2d3acc58ba0c4fd20d15a5db51f891ba6"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-linux_armv7l.whl", hash = 
"sha256:1117a81592542f0c36575082daa6413c57ca39188b18a4c50ec7332616f4b97e"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", 
hash = 
"sha256:51e5a090849b30c99a2396d42140b8a3e558eff6cdfa12603f9582e2cd07724e"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash 
= "sha256:4fe611d89a1836df8936f066d39c7eb03d4241806449ec45d4b8e1c843ae8011"},
+    {file = 
"grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:c10f3faa0cc4d89eb546f53b623837af23e86dc495d3b89510bcc0e0a6c0b8b2"},
+    {file = 
"grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:46b537480b8fd2195d988120a28467601a2a3de2e504043b89fb90318e1eb754"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = 
"sha256:17d0c9004ea82b4213955a585401e80c30d4b37a1d4ace32ccdea8db4d3b7d43"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = 
"sha256:2919faae04fe47bad57fc9b578aeaab527da260e851f321a253b6b11862254a8"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-win32.whl", hash = 
"sha256:ee86157ef899f58ba2fe1055cce0d33bd703e99aa6d5a0895581ac3969f06bfa"},
+    {file = "grpcio_tools-1.68.0-cp313-cp313-win_amd64.whl", hash = 
"sha256:d0470ffc6a93c86cdda48edd428d22e2fef17d854788d60d0d5f291038873157"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-linux_armv7l.whl", hash = 
"sha256:795f2cd76f68a12b0b5541b98187ba367dd69b49d359cf98b781ead742961370"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = 
"sha256:57e29e78c33fb1b1d557fbe7650d722d1f2b0a9f53ea73beb8ea47e627b6000b"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = 
"sha256:700f171cd3293ee8d50cd43171562ff07b14fa8e49ee471cd91c6924c7da8644"},
+    {file = 
"grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:196cd8a3a5963a4c9e424314df9eb573b305e6f958fe6508d26580ce01e7aa56"},
+    {file = 
"grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:cad40c3164ee9cef62524dea509449ea581b17ea493178beef051bf79b5103ca"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:ab93fab49fa1e699e577ff5fbb99aba660164d710d4c33cfe0aa9d06f585539f"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:511224a99726eb84db9ddb84dc8a75377c3eae797d835f99e80128ec618376d5"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-win32.whl", hash = 
"sha256:b4ca81770cd729a9ea536d871aacedbde2b732bb9bb83c9d993d63f58502153d"},
+    {file = "grpcio_tools-1.68.0-cp38-cp38-win_amd64.whl", hash = 
"sha256:6950725bf7a496f81d3ec3324334ffc9dbec743b510dd0e897f51f8627eeb6ac"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-linux_armv7l.whl", hash = 
"sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = 
"sha256:5afd2f3f7257b52228a7808a2b4a765893d4d802d7a2377d9284853e67d045c6"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = 
"sha256:453ee3193d59c974c678d91f08786f43c25ef753651b0825dc3d008c31baf68d"},
+    {file = 
"grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", 
hash = 
"sha256:b094b22919b786ad73c20372ef5e546330e7cd2c6dc12293b7ed586975f35d38"},
+    {file = 
"grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:26335eea976dfc1ff5d90b19c309a9425bd53868112a0507ad20f297f2c21d3e"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:c77ecc5164bb413a613bdac9091dcc29d26834a2ac42fcd1afdfcda9e3003e68"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:e31be6dc61496a59c1079b0a669f93dfcc2cdc4b1dbdc4374247cd09cee1329b"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-win32.whl", hash = 
"sha256:3aa40958355920ae2846c6fb5cadac4f2c8e33234a2982fef8101da0990e3968"},
+    {file = "grpcio_tools-1.68.0-cp39-cp39-win_amd64.whl", hash = 
"sha256:19bafb80948eda979b1b3a63c1567162d06249f43068a0e46a028a448e6f72d4"},
+    {file = "grpcio_tools-1.68.0.tar.gz", hash = 
"sha256:737804ec2225dd4cc27e633b4ca0e963b0795161bf678285fab6586e917fd867"},
 ]
 
 [package.dependencies]
-grpcio = ">=1.67.1"
+grpcio = ">=1.68.0"
 protobuf = ">=5.26.1,<6.0dev"
 setuptools = "*"
 
@@ -1980,19 +1980,19 @@ files = [
 
 [[package]]
 name = "mysqlclient"
-version = "2.2.5"
+version = "2.2.6"
 description = "Python interface to MySQL"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "mysqlclient-2.2.5-cp310-cp310-win_amd64.whl", hash = 
"sha256:b78438314199504c64f69e1e3521f2c9b419f19fcd85158b44c997b64409a6af"},
-    {file = "mysqlclient-2.2.5-cp311-cp311-win_amd64.whl", hash = 
"sha256:b54511648c1455b43ac28f8b4c1f732c5b0c343e87f7a3bd6fc9f9fe0f91934e"},
-    {file = "mysqlclient-2.2.5-cp312-cp312-win_amd64.whl", hash = 
"sha256:e871ede4261d0d42b8ed20a2459db411c7deafedd8e77b7e4ba760be4a6a752b"},
-    {file = "mysqlclient-2.2.5-cp313-cp313-win_amd64.whl", hash = 
"sha256:8012c633aab8c91ea8172ac479807135b171501b9cad1a7cd9b58c4dc8dcdab5"},
-    {file = "mysqlclient-2.2.5-cp39-cp39-win_amd64.whl", hash = 
"sha256:3f9625bea2b9bcde0ace76b32708762d44597491092c819fd1bff5b4e27f709b"},
-    {file = "mysqlclient-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = 
"sha256:1d2e2ca0fe8405d8d6464edd01bf059951279e4bc27284d39341bd4737b2bc64"},
-    {file = "mysqlclient-2.2.5-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:aee14f1872114865679fcb09aac3772de4595fa7dcf2f83a4c7afee15e508854"},
-    {file = "mysqlclient-2.2.5.tar.gz", hash = 
"sha256:add8643c32f738014d252d2bdebb478623b04802e8396d5903905db36474d3ff"},
+    {file = "mysqlclient-2.2.6-cp310-cp310-win_amd64.whl", hash = 
"sha256:e94a92858203d97fd584bdb6d7ee8c56f2590db8d77fd44215c0dcf5e739bc37"},
+    {file = "mysqlclient-2.2.6-cp311-cp311-win_amd64.whl", hash = 
"sha256:43c5b30be0675080b9c815f457d73397f0442173e7be83d089b126835e2617ae"},
+    {file = "mysqlclient-2.2.6-cp312-cp312-win_amd64.whl", hash = 
"sha256:e940b41d85dfd7b190fa47d52f525f878cfa203d4653bf6a35b271b3c3be125b"},
+    {file = "mysqlclient-2.2.6-cp313-cp313-win_amd64.whl", hash = 
"sha256:794857bce4f9a1903a99786dd29ad7887f45a870b3d11585b8c51c4a753c4174"},
+    {file = "mysqlclient-2.2.6-cp39-cp39-win_amd64.whl", hash = 
"sha256:b0a5cddf1d3488b254605041070086cac743401d876a659a72d706a0d89c8ebb"},
+    {file = "mysqlclient-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = 
"sha256:f3efb849d6f7ef4b9788a0eda2e896b975e0ebf1d6bf3dcabea63fd698e5b0b5"},
+    {file = "mysqlclient-2.2.6-pp39-pypy39_pp73-win_amd64.whl", hash = 
"sha256:3da70a07753ba6be881f7d75e795e254f6a0c12795778034acc69769b0649d37"},
+    {file = "mysqlclient-2.2.6.tar.gz", hash = 
"sha256:c0b46d9b78b461dbb62482089ca8040fa916595b1b30f831ebbd1b0a82b43d53"},
 ]
 
 [[package]]
@@ -2016,13 +2016,13 @@ pyarrow = ["pyarrow (>=1.0.0)"]
 
 [[package]]
 name = "packaging"
-version = "24.1"
+version = "24.2"
 description = "Core utilities for Python packages"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "packaging-24.1-py3-none-any.whl", hash = 
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
-    {file = "packaging-24.1.tar.gz", hash = 
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+    {file = "packaging-24.2-py3-none-any.whl", hash = 
"sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+    {file = "packaging-24.2.tar.gz", hash = 
"sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
 ]
 
 [[package]]
@@ -2598,54 +2598,54 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "1.10.18"
+version = "1.10.19"
 description = "Data validation and settings management using python type hints"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = 
"sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
-    {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = 
"sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
-    {file = 
"pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"},
-    {file = 
"pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"},
-    {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"},
-    {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"},
-    {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = 
"sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"},
-    {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = 
"sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"},
-    {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = 
"sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"},
-    {file = 
"pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"},
-    {file = 
"pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"},
-    {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"},
-    {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"},
-    {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = 
"sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"},
-    {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = 
"sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"},
-    {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = 
"sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"},
-    {file = 
"pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"},
-    {file = 
"pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"},
-    {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"},
-    {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"},
-    {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = 
"sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"},
-    {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = 
"sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"},
-    {file = 
"pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"},
-    {file = 
"pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"},
-    {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = 
"sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"},
-    {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = 
"sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"},
-    {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = 
"sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"},
-    {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = 
"sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"},
-    {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = 
"sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"},
-    {file = 
"pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"},
-    {file = 
"pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"},
-    {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"},
-    {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"},
-    {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = 
"sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"},
-    {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = 
"sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"},
-    {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = 
"sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"},
-    {file = 
"pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"},
-    {file = 
"pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"},
-    {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"},
-    {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"},
-    {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = 
"sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"},
-    {file = "pydantic-1.10.18-py3-none-any.whl", hash = 
"sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"},
-    {file = "pydantic-1.10.18.tar.gz", hash = 
"sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"},
+    {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = 
"sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"},
+    {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = 
"sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"},
+    {file = 
"pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"},
+    {file = 
"pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"},
+    {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = 
"sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"},
+    {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = 
"sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"},
+    {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = 
"sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"},
+    {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = 
"sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"},
+    {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = 
"sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"},
+    {file = 
"pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"},
+    {file = 
"pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"},
+    {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = 
"sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"},
+    {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = 
"sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"},
+    {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = 
"sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"},
+    {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = 
"sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"},
+    {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = 
"sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"},
+    {file = 
"pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"},
+    {file = 
"pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"},
+    {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = 
"sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"},
+    {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = 
"sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"},
+    {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = 
"sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"},
+    {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = 
"sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"},
+    {file = 
"pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"},
+    {file = 
"pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"},
+    {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = 
"sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"},
+    {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = 
"sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"},
+    {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = 
"sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"},
+    {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = 
"sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"},
+    {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = 
"sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"},
+    {file = 
"pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"},
+    {file = 
"pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"},
+    {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = 
"sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"},
+    {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = 
"sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"},
+    {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = 
"sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"},
+    {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = 
"sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"},
+    {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = 
"sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"},
+    {file = 
"pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"},
+    {file = 
"pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
 hash = 
"sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"},
+    {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = 
"sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"},
+    {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = 
"sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"},
+    {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = 
"sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"},
+    {file = "pydantic-1.10.19-py3-none-any.whl", hash = 
"sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"},
+    {file = "pydantic-1.10.19.tar.gz", hash = 
"sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"},
 ]
 
 [package.dependencies]
@@ -3137,13 +3137,13 @@ files = [
 
 [[package]]
 name = "sqlparse"
-version = "0.5.1"
+version = "0.5.2"
 description = "A non-validating SQL parser."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "sqlparse-0.5.1-py3-none-any.whl", hash = 
"sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
-    {file = "sqlparse-0.5.1.tar.gz", hash = 
"sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
+    {file = "sqlparse-0.5.2-py3-none-any.whl", hash = 
"sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"},
+    {file = "sqlparse-0.5.2.tar.gz", hash = 
"sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"},
 ]
 
 [package.extras]
@@ -3223,13 +3223,13 @@ tornado = ["tornado (>=4.0,<6.0)", "tornado 
(>=4.0,<7.0)"]
 
 [[package]]
 name = "tomli"
-version = "2.0.2"
+version = "2.1.0"
 description = "A lil' TOML parser"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "tomli-2.0.2-py3-none-any.whl", hash = 
"sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
-    {file = "tomli-2.0.2.tar.gz", hash = 
"sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
+    {file = "tomli-2.1.0-py3-none-any.whl", hash = 
"sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
+    {file = "tomli-2.1.0.tar.gz", hash = 
"sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
 ]
 
 [[package]]
@@ -3968,4 +3968,4 @@ sync = ["kafka-python", "requests"]
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8, <3.12"
-content-hash = 
"957ba196be529a7924617a968a810a5934461cb43936ed738779123e963fcb63"
+content-hash = 
"81b915ab08654027b32031f29cc7d558df4e19e32468f0463b85f6fa53122701"
diff --git a/pyproject.toml b/pyproject.toml
index dab159f..db34eef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -137,6 +137,7 @@ httpx = "^0.23.3"
 confluent-kafka = "^2.0.2"
 neo4j = "^5.9.0"
 pulsar-client = "3.3.0"
+grpcio = "^1.49.1"
 
 [tool.poetry.group.lint.dependencies]
 pylint = '2.13.9'
diff --git a/skywalking/__init__.py b/skywalking/__init__.py
index ec1dc09..b8005a5 100644
--- a/skywalking/__init__.py
+++ b/skywalking/__init__.py
@@ -55,6 +55,7 @@ class Component(Enum):
     AIORedis = 7017
     Websockets = 7018
     HTTPX = 7019
+    Grpc = 23
 
 
 class Layer(Enum):
@@ -89,6 +90,6 @@ LogItem = namedtuple('LogItem', 'key val')
 
 class Log(object):
 
-    def __init__(self, timestamp: time = time.time(), items: List[LogItem] = 
None): # noqa
+    def __init__(self, timestamp: time = time.time(), items: List[LogItem] = 
None):  # noqa
         self.timestamp = timestamp
         self.items = items or []
diff --git a/skywalking/config.py b/skywalking/config.py
index 62c8956..5a2b1ca 100644
--- a/skywalking/config.py
+++ b/skywalking/config.py
@@ -37,6 +37,7 @@ from typing import List, Pattern
 
 RE_IGNORE_PATH: Pattern = re.compile('^$')
 RE_HTTP_IGNORE_METHOD: Pattern = RE_IGNORE_PATH
+RE_GRPC_IGNORED_METHODS: Pattern = RE_IGNORE_PATH
 
 options = None  # here to include 'options' in globals
 options = globals().copy()
@@ -212,6 +213,8 @@ plugin_fastapi_collect_http_params: bool = 
os.getenv('SW_PLUGIN_FASTAPI_COLLECT_
 plugin_bottle_collect_http_params: bool = 
os.getenv('SW_PLUGIN_BOTTLE_COLLECT_HTTP_PARAMS', '').lower() == 'true'
 # The maximum length of `celery` functions parameters, longer than this will 
be truncated, 0 turns off
 plugin_celery_parameters_length: int = 
int(os.getenv('SW_PLUGIN_CELERY_PARAMETERS_LENGTH', '512'))
+# Comma-delimited list of user-defined grpc methods to ignore, like 
/package.Service/Method1,/package.Service/Method2
+plugin_grpc_ignored_methods: str = os.getenv('SW_PLUGIN_GRPC_IGNORED_METHODS', 
'').upper()
 
 # BEGIN: Sampling Configurations
 # The number of samples to take in every 3 seconds, 0 turns off
@@ -284,6 +287,7 @@ def finalize_regex() -> None:
     reesc = re.compile(r'([.*+?^=!:${}()|\[\]\\])')
     suffix = r'^.+(?:' + '|'.join(reesc.sub(r'\\\1', s.strip()) for s in 
agent_ignore_suffix.split(',')) + ')$'
     method = r'^' + '|'.join(s.strip() for s in 
plugin_http_ignore_method.split(',')) + '$'
+    grpc_method = r'^' + '|'.join(s.strip() for s in 
plugin_grpc_ignored_methods.split(',')) + '$'
     path = '^(?:' + \
            '|'.join(  # replaces ","
                '/(?:[^/]*/)*'.join(  # replaces "/**/"
@@ -297,15 +301,20 @@ def finalize_regex() -> None:
                ) for p0 in agent_trace_ignore_path.split(',')
            ) + ')$'
 
-    global RE_IGNORE_PATH, RE_HTTP_IGNORE_METHOD
+    global RE_IGNORE_PATH, RE_HTTP_IGNORE_METHOD, RE_GRPC_IGNORED_METHODS
     RE_IGNORE_PATH = re.compile(f'{suffix}|{path}')
     RE_HTTP_IGNORE_METHOD = re.compile(method, re.IGNORECASE)
+    RE_GRPC_IGNORED_METHODS = re.compile(grpc_method, re.IGNORECASE)
 
 
 def ignore_http_method_check(method: str):
     return RE_HTTP_IGNORE_METHOD.match(method)
 
 
+def ignore_grpc_method_check(method: str):
+    return RE_GRPC_IGNORED_METHODS.match(method)
+
+
 def finalize() -> None:
     """
     invokes finalizers
diff --git a/skywalking/meter/pvm/data_source.py 
b/skywalking/meter/pvm/data_source.py
index 3f97c78..4a54122 100644
--- a/skywalking/meter/pvm/data_source.py
+++ b/skywalking/meter/pvm/data_source.py
@@ -23,4 +23,4 @@ class DataSource:
         for name in dir(self):
             if name.endswith('generator'):
                 generator = getattr(self, name)()
-                Gauge.Builder('instance_pvm_' + name[:-10], generator).build()
+                Gauge.Builder(f'instance_pvm_{name[:-10]}', generator).build()
diff --git a/skywalking/plugins/sw_grpc.py b/skywalking/plugins/sw_grpc.py
new file mode 100644
index 0000000..12b03cb
--- /dev/null
+++ b/skywalking/plugins/sw_grpc.py
@@ -0,0 +1,477 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from asyncio import iscoroutine
+from concurrent.futures import Executor
+from typing import Any, Awaitable, Callable, List, NamedTuple, Optional, 
Sequence, Tuple, Union
+
+from skywalking import Component, Layer, config
+from skywalking.trace.carrier import Carrier
+from skywalking.trace.context import NoopContext, get_context
+from skywalking.trace.span import NoopSpan
+from skywalking.trace.tags import TagGrpcMethod, TagGrpcStatusCode, TagGrpcUrl
+
+link_vector = ['https://grpc.io/docs/languages/python']
+support_matrix = {'grpcio': {'>=3.8': ['1.*']}}
+note = """"""
+
+
+def _get_factory_and_method(rpc_handler: Any) -> Tuple[Callable[..., Any], 
Callable[..., Any]]:
+    import grpc
+
+    if rpc_handler.unary_unary:
+        return grpc.unary_unary_rpc_method_handler, rpc_handler.unary_unary
+    elif rpc_handler.unary_stream:
+        return grpc.unary_stream_rpc_method_handler, rpc_handler.unary_stream
+    elif rpc_handler.stream_unary:
+        return grpc.stream_unary_rpc_method_handler, rpc_handler.stream_unary
+    elif rpc_handler.stream_stream:
+        return grpc.stream_stream_rpc_method_handler, rpc_handler.stream_stream
+    else:
+        raise RuntimeError('RPC handler implementation does not exist')
+
+
+def _restore_carrier(handler_call_details: Any) -> Carrier:
+    metadata_map = dict(handler_call_details.invocation_metadata or ())
+
+    carrier = Carrier()
+    for item in carrier:
+        val = metadata_map.get(item.key)
+        if val is not None:
+            item.val = val
+    return carrier
+
+
+def get_method_name(details: Any) -> str:
+    method_name = details.method
+    if type(method_name) is bytes:
+        return method_name.decode()
+    if type(method_name) is str:
+        return method_name
+    return str(method_name)
+
+
+def install_sync() -> None:
+    import grpc
+    from grpc import _channel
+
+    def install_server() -> None:
+        _grpc_server = grpc.server
+
+        def _make_invoke_intercept_method(
+            is_response_streaming: bool,
+            next_handler_method: Callable[..., Any],
+            handler_call_details: grpc.HandlerCallDetails,
+        ) -> Callable[[Any, grpc.ServicerContext], Any]:
+
+            def invoke_intercept_method(request_or_iterator: Any, context: 
grpc.ServicerContext) -> Any:
+
+                carrier = _restore_carrier(handler_call_details)
+                method_name = get_method_name(handler_call_details)
+                with (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_entry_span(op=method_name, 
carrier=carrier)
+                ) as span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.component = Component.Grpc
+                    span.peer = context.peer()
+                    try:
+                        return next_handler_method(request_or_iterator, 
context)
+                    except grpc.RpcError as e:
+                        if hasattr(e, 'code'):
+                            span.tag(TagGrpcStatusCode(e.code()))
+                        else:
+                            
span.tag(TagGrpcStatusCode(grpc.StatusCode.UNKNOWN))
+                        span.raised()
+                        raise e
+
+            def invoke_intercept_method_response_streaming(
+                request_or_iterator: Any, context: grpc.ServicerContext
+            ) -> Any:
+                carrier = _restore_carrier(handler_call_details)
+                method_name = get_method_name(handler_call_details)
+                with (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_entry_span(op=method_name, 
carrier=carrier)
+                ) as span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.component = Component.Grpc
+                    span.peer = context.peer()
+                    try:
+                        yield from next_handler_method(request_or_iterator, 
context)
+                    except grpc.RpcError as e:
+                        if hasattr(e, 'code'):
+                            span.tag(TagGrpcStatusCode(e.code()))
+                        else:
+                            
span.tag(TagGrpcStatusCode(grpc.StatusCode.UNKNOWN))
+                        span.raised()
+                        raise e
+
+            return invoke_intercept_method_response_streaming if 
is_response_streaming else invoke_intercept_method
+
+        class ServerInterceptor(grpc.ServerInterceptor):
+
+            def intercept_service(
+                self,
+                continuation: Callable[[grpc.HandlerCallDetails], 
grpc.RpcMethodHandler],
+                handler_call_details: grpc.HandlerCallDetails,
+            ) -> grpc.RpcMethodHandler:
+                next_handler = continuation(handler_call_details)
+
+                handler_factory, next_handler_method = 
_get_factory_and_method(next_handler)
+
+                return handler_factory(
+                    _make_invoke_intercept_method(
+                        bool(next_handler.response_streaming), 
next_handler_method, handler_call_details
+                    ),
+                    request_deserializer=next_handler.request_deserializer,
+                    response_serializer=next_handler.response_serializer,
+                )
+
+        def _sw_grpc_server(
+            thread_pool: Optional[Executor] = None,
+            handlers: Optional[Sequence[grpc.GenericRpcHandler]] = None,
+            interceptors: Optional[Sequence[Any]] = None,
+            options: Optional[Any] = None,
+            maximum_concurrent_rpcs: Optional[int] = None,
+            compression: Optional[grpc.Compression] = None,
+            xds: bool = False,
+        ):
+            _sw_interceptors = [ServerInterceptor()]
+            if interceptors is not None:
+                _sw_interceptors.extend(interceptors)
+            return _grpc_server(
+                thread_pool,
+                handlers,
+                _sw_interceptors,
+                options,
+                maximum_concurrent_rpcs,
+                compression,
+                xds,
+            )
+
+        grpc.server = _sw_grpc_server
+
+    def install_client() -> None:
+        _grpc_channel = _channel.Channel
+
+        class _ClientCallDetails(NamedTuple):
+            method: str
+            timeout: Optional[float]
+            metadata: Optional[Sequence[Tuple[str, Union[str, bytes]]]]
+            credentials: Optional[grpc.CallCredentials]
+            wait_for_ready: Optional[bool]
+            compression: Any
+
+        class _SWClientCallDetails(_ClientCallDetails, grpc.ClientCallDetails):
+            pass
+
+        class _ClientInterceptor(
+            grpc.UnaryUnaryClientInterceptor,
+            grpc.UnaryStreamClientInterceptor,
+            grpc.StreamUnaryClientInterceptor,
+            grpc.StreamStreamClientInterceptor,
+        ):
+            def __init__(self, target: str):
+                self.target = target
+
+            def _intercept(
+                self,
+                continuation: Callable[..., Any],
+                client_call_details: grpc.ClientCallDetails,
+                request: Any,
+            ):
+                method_name = get_method_name(client_call_details)
+                with (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_exit_span(op=method_name, 
peer=self.target, component=Component.Grpc)
+                ) as span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.tag(TagGrpcUrl(self.target))
+                    carrier = span.inject()
+                    metadata = list(client_call_details.metadata or [])
+                    for item in carrier:
+                        metadata.append((item.key, item.val))
+                    new_client_call_details = _SWClientCallDetails(
+                        client_call_details.method,
+                        client_call_details.timeout,
+                        metadata,
+                        client_call_details.credentials,
+                        client_call_details.wait_for_ready,
+                        client_call_details.compression,
+                    )
+                    return continuation(new_client_call_details, request)
+
+            def intercept_unary_unary(self, continuation, client_call_details, 
request):
+                return self._intercept(continuation, client_call_details, 
request)
+
+            def intercept_unary_stream(self, continuation, 
client_call_details, request):
+                return self._intercept(continuation, client_call_details, 
request)
+
+            def intercept_stream_unary(self, continuation, 
client_call_details, request_iterator):
+                return self._intercept(continuation, client_call_details, 
request_iterator)
+
+            def intercept_stream_stream(self, continuation, 
client_call_details, request_iterator):
+                return self._intercept(continuation, client_call_details, 
request_iterator)
+
+        def _sw_grpc_channel_factory(target: str, *args: Any, **kwargs: Any):
+            c = _grpc_channel(target, *args, **kwargs)
+            if target == config.agent_collector_backend_services:
+                return c
+            return grpc.intercept_channel(c, _ClientInterceptor(target))
+
+        _channel.Channel = _sw_grpc_channel_factory
+
+    install_client()
+    install_server()
+
+
+def install_async() -> None:
+    import grpc
+    from grpc.aio import _channel as _aio_channel
+
+    def install_async_server() -> None:
+        _grpc_aio_server = grpc.aio.server
+
+        def _make_invoke_intercept_method(
+            is_response_streaming: bool,
+            next_handler_method: Callable[..., Any],
+            handler_call_details: grpc.HandlerCallDetails,
+        ) -> Callable[[Any, grpc.aio.ServicerContext[Any, Any]], 
Awaitable[Any]]:
+
+            async def invoke_intercept_method(
+                request_or_iterator: Any, context: 
grpc.aio.ServicerContext[Any, Any]
+            ) -> Any:
+
+                carrier = _restore_carrier(handler_call_details)
+                method_name = get_method_name(handler_call_details)
+                with (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_entry_span(op=method_name, 
carrier=carrier)
+                ) as span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.component = Component.Grpc
+                    span.peer = context.peer()
+                    try:
+                        return await next_handler_method(request_or_iterator, 
context)
+                    except grpc.RpcError as e:
+                        if hasattr(e, 'code'):
+                            span.tag(TagGrpcStatusCode(e.code()))
+                        else:
+                            
span.tag(TagGrpcStatusCode(grpc.StatusCode.UNKNOWN))
+                        span.raised()
+                        raise e
+
+            async def invoke_intercept_method_response_streaming(
+                request_or_iterator: Any, context: 
grpc.aio.ServicerContext[Any, Any]
+            ) -> Any:
+                carrier = _restore_carrier(handler_call_details)
+                method_name = get_method_name(handler_call_details)
+                with (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_entry_span(op=method_name, 
carrier=carrier)
+                ) as span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.component = Component.Grpc
+                    span.peer = context.peer()
+                    try:
+                        coroutine_or_asyncgen = 
next_handler_method(request_or_iterator, context)
+                        async for r in (
+                            await coroutine_or_asyncgen if 
iscoroutine(coroutine_or_asyncgen) else coroutine_or_asyncgen
+                        ):
+                            yield r
+                    except grpc.RpcError as e:
+                        if hasattr(e, 'code'):
+                            span.tag(TagGrpcStatusCode(e.code()))
+                        else:
+                            
span.tag(TagGrpcStatusCode(grpc.StatusCode.UNKNOWN))
+                        span.raised()
+                        raise e
+
+            return invoke_intercept_method_response_streaming if 
is_response_streaming else invoke_intercept_method
+
+        class ServerInterceptor(grpc.aio.ServerInterceptor):
+
+            async def intercept_service(
+                self,
+                continuation: Callable[[grpc.HandlerCallDetails], 
Awaitable[grpc.RpcMethodHandler]],
+                handler_call_details: grpc.HandlerCallDetails,
+            ) -> grpc.RpcMethodHandler:
+                next_handler = await continuation(handler_call_details)
+
+                handler_factory, next_handler_method = 
_get_factory_and_method(next_handler)
+                return handler_factory(
+                    _make_invoke_intercept_method(
+                        bool(next_handler.response_streaming), 
next_handler_method, handler_call_details
+                    ),
+                    request_deserializer=next_handler.request_deserializer,
+                    response_serializer=next_handler.response_serializer,
+                )
+
+        def _sw_grpc_aio_server(
+            migration_thread_pool: Optional[Executor] = None,
+            handlers: Optional[Sequence[grpc.GenericRpcHandler]] = None,
+            interceptors: Optional[Sequence[Any]] = None,
+            options: Optional[grpc.aio.ChannelArgumentType] = None,
+            maximum_concurrent_rpcs: Optional[int] = None,
+            compression: Optional[grpc.Compression] = None,
+        ):
+            _sw_interceptors = [ServerInterceptor()]
+            if interceptors is not None:
+                _sw_interceptors.extend(interceptors)
+            return _grpc_aio_server(
+                migration_thread_pool,
+                handlers,
+                _sw_interceptors,
+                options,
+                maximum_concurrent_rpcs,
+                compression,
+            )
+
+        grpc.aio.server = _sw_grpc_aio_server
+
+    def install_async_client() -> None:
+        _aio_grpc_channel = _aio_channel.Channel
+
+        class _AioClientCallDetails(NamedTuple):
+            method: str
+            timeout: Optional[float]
+            metadata: Optional[grpc.aio.Metadata]
+            credentials: Optional[grpc.CallCredentials]
+            wait_for_ready: Optional[bool]
+
+        class _SWAioClientCallDetails(_AioClientCallDetails, 
grpc.aio.ClientCallDetails):
+            pass
+
+        class _AioClientInterceptor:
+            def __init__(self, target: str):
+                self.target = target
+
+            async def _intercept(
+                self,
+                continuation: Callable[..., Any],
+                client_call_details: grpc.aio.ClientCallDetails,
+                request: Any,
+            ):
+                method_name = get_method_name(client_call_details)
+                span = (
+                    NoopSpan(NoopContext())
+                    if config.ignore_grpc_method_check(method_name)
+                    else get_context().new_exit_span(op=method_name, 
peer=self.target, component=Component.Grpc)
+                )
+                with span:
+                    span.layer = Layer.RPCFramework
+                    span.tag(TagGrpcMethod(method_name))
+                    span.tag(TagGrpcUrl(self.target))
+                    carrier = span.inject()
+                    metadata = client_call_details.metadata or 
grpc.aio.Metadata()
+                    for item in carrier:
+                        metadata.add(item.key, item.val)
+                    new_client_call_details = _SWAioClientCallDetails(
+                        client_call_details.method,
+                        client_call_details.timeout,
+                        metadata,
+                        client_call_details.credentials,
+                        client_call_details.wait_for_ready,
+                    )
+                    return await continuation(new_client_call_details, request)
+
+        class _AioClientUnaryUnaryInterceptor(_AioClientInterceptor, 
grpc.aio.UnaryUnaryClientInterceptor):
+
+            async def intercept_unary_unary(
+                self,
+                continuation: Callable[
+                    [grpc.aio.ClientCallDetails, 
grpc.aio._typing.RequestType], grpc.aio._call.UnaryUnaryCall
+                ],
+                client_call_details: grpc.aio.ClientCallDetails,
+                request: grpc.aio._typing.RequestType,
+            ) -> grpc.aio._call.UnaryUnaryCall:
+                return await self._intercept(continuation, 
client_call_details, request)
+
+        class _AioClientUnaryStreamInterceptor(_AioClientInterceptor, 
grpc.aio.UnaryStreamClientInterceptor):
+
+            async def intercept_unary_stream(
+                self,
+                continuation: Callable[
+                    [grpc.aio.ClientCallDetails, 
grpc.aio._typing.RequestType], grpc.aio._call.UnaryStreamCall
+                ],
+                client_call_details: grpc.aio.ClientCallDetails,
+                request: grpc.aio._typing.RequestType,
+            ) -> grpc.aio._call.UnaryStreamCall:
+                return await self._intercept(continuation, 
client_call_details, request)
+
+        class _AioClientStreamUnaryInterceptor(_AioClientInterceptor, 
grpc.aio.StreamUnaryClientInterceptor):
+
+            async def intercept_stream_unary(
+                self,
+                continuation: Callable[
+                    [grpc.aio.ClientCallDetails, 
grpc.aio._typing.RequestType], grpc.aio._call.StreamUnaryCall
+                ],
+                client_call_details: grpc.aio.ClientCallDetails,
+                request_iterator: grpc.aio._typing.RequestIterableType,
+            ) -> grpc.aio._call.StreamUnaryCall:
+                return await self._intercept(continuation, 
client_call_details, request_iterator)
+
+        class _AioClientStreamStreamInterceptor(_AioClientInterceptor, 
grpc.aio.StreamStreamClientInterceptor):
+
+            async def intercept_stream_stream(
+                self,
+                continuation: Callable[
+                    [grpc.aio.ClientCallDetails, 
grpc.aio._typing.RequestType], grpc.aio._call.StreamStreamCall
+                ],
+                client_call_details: grpc.aio.ClientCallDetails,
+                request_iterator: grpc.aio._typing.RequestIterableType,
+            ) -> grpc.aio._call.StreamStreamCall:
+                return await self._intercept(continuation, 
client_call_details, request_iterator)
+
+        def _sw_grpc_aio_channel_factory(
+            target: str,
+            options: grpc.aio.ChannelArgumentType,
+            credentials: Optional[grpc.ChannelCredentials],
+            compression: Optional[grpc.Compression],
+            interceptors: Optional[Sequence[grpc.aio.ClientInterceptor]],
+        ):
+            if target == config.agent_collector_backend_services:
+                return _aio_grpc_channel(target, options, credentials, 
compression, interceptors)
+            _sw_interceptors: List[grpc.aio.ClientInterceptor] = [
+                _AioClientUnaryUnaryInterceptor(target),
+                _AioClientUnaryStreamInterceptor(target),
+                _AioClientStreamUnaryInterceptor(target),
+                _AioClientStreamStreamInterceptor(target),
+            ]
+            _sw_interceptors.extend(interceptors or [])
+            return _aio_grpc_channel(target, options, credentials, 
compression, _sw_interceptors)
+
+        _aio_channel.Channel = _sw_grpc_aio_channel_factory
+
+    install_async_client()
+    install_async_server()
+
+
+def install():
+    install_sync()
+    install_async()
diff --git a/skywalking/trace/context.py b/skywalking/trace/context.py
index db52935..11ec3da 100644
--- a/skywalking/trace/context.py
+++ b/skywalking/trace/context.py
@@ -49,7 +49,7 @@ try:  # attempt to use async-local instead of thread-local 
context and spans
         return spans
 
     def _spans_dup():
-        spans = __spans.get()[:]
+        spans = __spans.get([])[:]
         __spans.set(spans)
 
         return spans
diff --git a/skywalking/trace/tags.py b/skywalking/trace/tags.py
index a1288c4..11b0519 100644
--- a/skywalking/trace/tags.py
+++ b/skywalking/trace/tags.py
@@ -15,6 +15,7 @@
 # limitations under the License.
 #
 
+
 class Tag:
     key: str = ''
     overridable: bool = True
@@ -93,3 +94,15 @@ class TagMqQueue(Tag):
 
 class TagCeleryParameters(Tag):
     key = 'celery.parameters'
+
+
+class TagGrpcMethod(Tag):
+    key = 'grpc.method'
+
+
+class TagGrpcUrl(Tag):
+    key = 'grpc.url'
+
+
+class TagGrpcStatusCode(Tag):
+    key = 'grpc.status_code'
diff --git a/skywalking/meter/pvm/data_source.py 
b/tests/plugin/web/sw_grpc/__init__.py
similarity index 73%
copy from skywalking/meter/pvm/data_source.py
copy to tests/plugin/web/sw_grpc/__init__.py
index 3f97c78..b1312a0 100644
--- a/skywalking/meter/pvm/data_source.py
+++ b/tests/plugin/web/sw_grpc/__init__.py
@@ -14,13 +14,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-from skywalking.meter.gauge import Gauge
-
-
-class DataSource:
-    def register(self):
-        for name in dir(self):
-            if name.endswith('generator'):
-                generator = getattr(self, name)()
-                Gauge.Builder('instance_pvm_' + name[:-10], generator).build()
diff --git a/tests/plugin/web/sw_grpc/docker-compose.yml 
b/tests/plugin/web/sw_grpc/docker-compose.yml
new file mode 100644
index 0000000..def423b
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/docker-compose.yml
@@ -0,0 +1,114 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+version: '2.1'
+
+services:
+  collector:
+    extends:
+      service: collector
+      file: ../../docker-compose.base.yml
+
+  provider:
+    extends:
+      service: agent
+      file: ../../docker-compose.base.yml
+    ports:
+      - 50051:50051
+    volumes:
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python -m 
grpc_tools.protoc -I/app --python_out=/tmp --grpc_python_out=/tmp example.proto 
&& PYTHONPATH=/tmp sw-python run python3 /app/services/provider.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/50051"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+    environment:
+      SW_AGENT_NAME: provider
+      SW_AGENT_LOGGING_LEVEL: DEBUG
+
+  consumer:
+    extends:
+      service: agent
+      file: ../../docker-compose.base.yml
+    ports:
+      - 50052:50052
+    volumes:
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python -m 
grpc_tools.protoc -I/app --python_out=/tmp --grpc_python_out=/tmp example.proto 
&& PYTHONPATH=/tmp sw-python run python3 /app/services/consumer.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+      provider:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/50051"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+    environment:
+      SW_AGENT_NAME: consumer
+      SW_AGENT_LOGGING_LEVEL: DEBUG
+
+  aio_provider:
+    extends:
+      service: agent
+      file: ../../docker-compose.base.yml
+    ports:
+      - 50061:50061
+    volumes:
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python -m 
grpc_tools.protoc -I/app --python_out=/tmp --grpc_python_out=/tmp example.proto 
&& PYTHONPATH=/tmp sw-python run python3 /app/services/aio_provider.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/50061"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+    environment:
+      SW_AGENT_NAME: aio_provider
+      SW_AGENT_LOGGING_LEVEL: DEBUG
+
+  aio_consumer:
+    extends:
+      service: agent
+      file: ../../docker-compose.base.yml
+    ports:
+      - 50062:50062
+    volumes:
+      - .:/app
+    command: ['bash', '-c', 'pip install -r /app/requirements.txt && python -m 
grpc_tools.protoc -I/app --python_out=/tmp --grpc_python_out=/tmp example.proto 
&& PYTHONPATH=/tmp sw-python run python3 /app/services/aio_consumer.py']
+    depends_on:
+      collector:
+        condition: service_healthy
+      provider:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/50062"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+    environment:
+      SW_AGENT_NAME: aio_consumer
+      SW_AGENT_LOGGING_LEVEL: DEBUG
+networks:
+  beyond:
diff --git a/tests/plugin/web/sw_grpc/example.proto 
b/tests/plugin/web/sw_grpc/example.proto
new file mode 100644
index 0000000..204235f
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/example.proto
@@ -0,0 +1,41 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+syntax = "proto3";
+
+package example;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+  rpc SayHello1 (HelloRequest) returns (HelloReply) {}
+  rpc SayHelloUS (HelloRequest) returns (stream HelloReply) {}
+  rpc SayHelloSU (stream HelloRequest) returns (HelloReply) {}
+  rpc SayHelloSS (stream HelloRequest) returns (stream HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings.
+message HelloReply {
+  string message = 1;
+}
diff --git a/tests/plugin/web/sw_grpc/expected.data.yml 
b/tests/plugin/web/sw_grpc/expected.data.yml
new file mode 100644
index 0000000..7eae4f8
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/expected.data.yml
@@ -0,0 +1,384 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+segmentItems:
+  - serviceName: provider
+    segmentSize: 4
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHello
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHello }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHello,
+                  networkAddress: "provider:50051",
+                  refType: CrossProcess,
+                  parentSpanId: 1,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloUS
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloUS }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloUS,
+                  networkAddress: "provider:50051",
+                  refType: CrossProcess,
+                  parentSpanId: 2,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloSU
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSU }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloSU,
+                  networkAddress: "provider:50051",
+                  refType: CrossProcess,
+                  parentSpanId: 3,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloSS
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSS }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloSS,
+                  networkAddress: "provider:50051",
+                  refType: CrossProcess,
+                  parentSpanId: 4,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: consumer,
+                  traceId: not null,
+                }
+  - serviceName: consumer
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHello
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: provider:50051
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHello }
+              - { key: grpc.url, value: "provider:50051" }
+          - operationName: /example.Greeter/SayHelloUS
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: provider:50051
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloUS }
+              - { key: grpc.url, value: "provider:50051" }
+          - operationName: /example.Greeter/SayHelloSU
+            parentSpanId: 0
+            spanId: 3
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: provider:50051
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSU }
+              - { key: grpc.url, value: "provider:50051" }
+          - operationName: /example.Greeter/SayHelloSS
+            parentSpanId: 0
+            spanId: 4
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: provider:50051
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSS }
+              - { key: grpc.url, value: "provider:50051" }
+          - operationName: /
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7000
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: http.method, value: GET }
+              - { key: http.url, value: "http://127.0.0.1:50052/"; }
+              - { key: http.status_code, value: "200" }
+  - serviceName: aio_provider
+    segmentSize: 4
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHello
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHello }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHello,
+                  networkAddress: "aio_provider:50061",
+                  refType: CrossProcess,
+                  parentSpanId: 1,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: aio_consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloUS
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloUS }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloUS,
+                  networkAddress: "aio_provider:50061",
+                  refType: CrossProcess,
+                  parentSpanId: 2,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: aio_consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloSU
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSU }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloSU,
+                  networkAddress: "aio_provider:50061",
+                  refType: CrossProcess,
+                  parentSpanId: 3,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: aio_consumer,
+                  traceId: not null,
+                }
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHelloSS
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSS }
+            refs:
+              - {
+                  parentEndpoint: /example.Greeter/SayHelloSS,
+                  networkAddress: "aio_provider:50061",
+                  refType: CrossProcess,
+                  parentSpanId: 4,
+                  parentTraceSegmentId: not null,
+                  parentServiceInstance: not null,
+                  parentService: aio_consumer,
+                  traceId: not null,
+                }
+  - serviceName: aio_consumer
+    segmentSize: 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /example.Greeter/SayHello
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: aio_provider:50061
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHello }
+              - { key: grpc.url, value: "aio_provider:50061" }
+          - operationName: /example.Greeter/SayHelloUS
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: aio_provider:50061
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloUS }
+              - { key: grpc.url, value: "aio_provider:50061" }
+          - operationName: /example.Greeter/SayHelloSU
+            parentSpanId: 0
+            spanId: 3
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: aio_provider:50061
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSU }
+              - { key: grpc.url, value: "aio_provider:50061" }
+          - operationName: /example.Greeter/SayHelloSS
+            parentSpanId: 0
+            spanId: 4
+            spanLayer: RPCFramework
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 23
+            isError: false
+            spanType: Exit
+            peer: aio_provider:50061
+            skipAnalysis: false
+            tags:
+              - { key: grpc.method, value: /example.Greeter/SayHelloSS }
+              - { key: grpc.url, value: "aio_provider:50061" }
+          - operationName: /
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 7000
+            isError: false
+            spanType: Entry
+            peer: not null
+            skipAnalysis: false
+            tags:
+              - { key: http.method, value: GET }
+              - { key: http.url, value: "http://127.0.0.1:50062/"; }
+              - { key: http.status_code, value: "200" }
diff --git a/skywalking/meter/pvm/data_source.py 
b/tests/plugin/web/sw_grpc/services/__init__.py
similarity index 73%
copy from skywalking/meter/pvm/data_source.py
copy to tests/plugin/web/sw_grpc/services/__init__.py
index 3f97c78..b1312a0 100644
--- a/skywalking/meter/pvm/data_source.py
+++ b/tests/plugin/web/sw_grpc/services/__init__.py
@@ -14,13 +14,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-from skywalking.meter.gauge import Gauge
-
-
-class DataSource:
-    def register(self):
-        for name in dir(self):
-            if name.endswith('generator'):
-                generator = getattr(self, name)()
-                Gauge.Builder('instance_pvm_' + name[:-10], generator).build()
diff --git a/tests/plugin/web/sw_grpc/services/aio_consumer.py 
b/tests/plugin/web/sw_grpc/services/aio_consumer.py
new file mode 100644
index 0000000..0b19fb4
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/services/aio_consumer.py
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import asyncio
+import socketserver
+from http.server import BaseHTTPRequestHandler
+
+import example_pb2
+import example_pb2_grpc
+import grpc
+
+
+async def generate_messages():
+    yield example_pb2.HelloRequest(name='World')
+
+
+class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
+    def do_GET(self):  # noqa
+        async def task():
+            async with grpc.aio.insecure_channel('aio_provider:50061') as 
channel:
+                stub = example_pb2_grpc.GreeterStub(channel)
+                request = example_pb2.HelloRequest(name='World')
+                await stub.SayHello(request)
+                async for _ in stub.SayHelloUS(request):
+                    pass
+                await stub.SayHelloSU(generate_messages())
+                async for _ in stub.SayHelloSS(generate_messages()):
+                    pass
+
+        asyncio.run(task())
+        self.send_response(200)
+        self.end_headers()
+        self.wfile.write(b'Hello, world')
+
+
+if __name__ == '__main__':
+    PORT = 50062
+    with socketserver.TCPServer(('', PORT), SimpleHTTPRequestHandler) as httpd:
+        print('serving at port', PORT)
+        httpd.serve_forever()
diff --git a/tests/plugin/web/sw_grpc/services/aio_provider.py 
b/tests/plugin/web/sw_grpc/services/aio_provider.py
new file mode 100644
index 0000000..5771e06
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/services/aio_provider.py
@@ -0,0 +1,66 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import asyncio
+from concurrent import futures
+from typing import Any, AsyncGenerator, AsyncIterable, Tuple
+
+import example_pb2
+import example_pb2_grpc
+import grpc
+
+
+async def async_enumerate(aiterable: AsyncIterable[Any], start: int = 0) -> 
AsyncGenerator[Tuple[int, Any], Any]:
+    index = start
+    async for item in aiterable:
+        yield index, item
+        index += 1
+
+
+class GreeterServicer(example_pb2_grpc.GreeterServicer):
+    async def SayHello(self, request, context):  # noqa
+        return example_pb2.HelloReply()
+
+    async def SayHelloUS(self, request, context):  # noqa
+        for i in range(3):
+            response = example_pb2.HelloReply()
+            response.message = f'Hello, {request.name} {i}!'
+            yield response
+
+    async def SayHelloSU(self, request_iterator, context):  # noqa
+        response = example_pb2.HelloReply()
+        async for request in request_iterator:
+            response.message = f'Hello, {request.name}!'
+        return response
+
+    async def SayHelloSS(self, request_iterator, context):  # noqa
+        async for i, request in async_enumerate(request_iterator):
+            response = example_pb2.HelloReply()
+            response.message = f'Hello, {request.name} {i}!'
+            yield response
+
+
+async def serve():
+    server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=10))
+    example_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
+    server.add_insecure_port('[::]:50061')
+    await server.start()
+    await server.wait_for_termination()
+
+
+if __name__ == '__main__':
+    asyncio.run(serve())
diff --git a/tests/plugin/web/sw_grpc/services/consumer.py 
b/tests/plugin/web/sw_grpc/services/consumer.py
new file mode 100644
index 0000000..765b2bb
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/services/consumer.py
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import socketserver
+from http.server import BaseHTTPRequestHandler
+
+import example_pb2
+import example_pb2_grpc
+import grpc
+
+
+def generate_messages():
+    yield example_pb2.HelloRequest(name='World')
+
+
+class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
+    def do_GET(self):  # noqa
+        with grpc.insecure_channel('provider:50051') as channel:
+            stub = example_pb2_grpc.GreeterStub(channel)
+            request = example_pb2.HelloRequest(name='World')
+            stub.SayHello(request)
+            for _ in stub.SayHelloUS(request):
+                pass
+            stub.SayHelloSU(generate_messages())
+            for _ in stub.SayHelloSS(generate_messages()):
+                pass
+            self.send_response(200)
+            self.end_headers()
+            self.wfile.write(b'Hello, world')
+
+
+if __name__ == '__main__':
+    PORT = 50052
+    with socketserver.TCPServer(('', PORT), SimpleHTTPRequestHandler) as httpd:
+        print('serving at port', PORT)
+        httpd.serve_forever()
diff --git a/tests/plugin/web/sw_grpc/services/provider.py 
b/tests/plugin/web/sw_grpc/services/provider.py
new file mode 100644
index 0000000..0269868
--- /dev/null
+++ b/tests/plugin/web/sw_grpc/services/provider.py
@@ -0,0 +1,57 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from concurrent import futures
+
+import example_pb2
+import example_pb2_grpc
+import grpc
+
+
+class GreeterServicer(example_pb2_grpc.GreeterServicer):
+    def SayHello(self, request, context):  # noqa
+        return example_pb2.HelloReply()
+
+    def SayHelloUS(self, request, context):  # noqa
+        for i in range(3):
+            response = example_pb2.HelloReply()
+            response.message = f'Hello, {request.name} {i}!'
+            yield response
+
+    def SayHelloSU(self, request_iterator, context):  # noqa
+        response = example_pb2.HelloReply()
+        for request in request_iterator:
+            response.message = f'Hello, {request.name}!'
+        return response
+
+    def SayHelloSS(self, request_iterator, context):  # noqa
+        for i, request in enumerate(request_iterator):
+            response = example_pb2.HelloReply()
+            response.message = f'Hello, {request.name} {i}!'
+            yield response
+
+
+def serve():
+    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+    example_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
+    server.add_insecure_port('[::]:50051')
+    server.start()
+    server.wait_for_termination()
+
+
+if __name__ == '__main__':
+    serve()
diff --git a/skywalking/meter/pvm/data_source.py 
b/tests/plugin/web/sw_grpc/test_grpc.py
similarity index 53%
copy from skywalking/meter/pvm/data_source.py
copy to tests/plugin/web/sw_grpc/test_grpc.py
index 3f97c78..12d26ae 100644
--- a/skywalking/meter/pvm/data_source.py
+++ b/tests/plugin/web/sw_grpc/test_grpc.py
@@ -14,13 +14,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from typing import Any, Callable
 
-from skywalking.meter.gauge import Gauge
+import pytest
+import requests
 
+from skywalking.plugins.sw_grpc import support_matrix
+from tests.orchestrator import get_test_vector
+from tests.plugin.base import TestPluginBase
 
-class DataSource:
-    def register(self):
-        for name in dir(self):
-            if name.endswith('generator'):
-                generator = getattr(self, name)()
-                Gauge.Builder('instance_pvm_' + name[:-10], generator).build()
+
[email protected]
+def prepare() -> Callable[..., None]:
+
+    def f(*_: Any) -> None:
+        requests.get('http://127.0.0.1:50052/', timeout=5)
+        requests.get('http://127.0.0.1:50062/', timeout=5)
+
+    return f
+
+
+class TestPlugin(TestPluginBase):
+    @pytest.mark.parametrize('version', get_test_vector(lib_name='grpcio', 
support_matrix=support_matrix))
+    def test_plugin(self, docker_compose, version):
+        import time
+
+        time.sleep(100)
+        self.validate()
diff --git a/tools/config_doc_gen.py b/tools/config_doc_gen.py
index 01e527f..9004b70 100644
--- a/tools/config_doc_gen.py
+++ b/tools/config_doc_gen.py
@@ -87,7 +87,7 @@ def create_entry(comment: str, config_index: int) -> str:
     """
 
     def env_var_name(config_entry):
-        return 'SW_' + config_entry.upper()
+        return f'SW_{config_entry.upper()}'
 
     configuration = list(OPTIONS.keys())[config_index]
     type_ = OPTIONS[configuration][1]

Reply via email to