parent
							
								
									24bc71eb2e
								
							
						
					
					
						commit
						5a7935ff1f
					
				|  | @ -66,7 +66,6 @@ jobs: | |||
|           - "redis" | ||||
|           - "remoulade" | ||||
|           - "requests" | ||||
|           - "sklearn" | ||||
|           - "sqlalchemy" | ||||
|           - "sqlite3" | ||||
|           - "starlette" | ||||
|  | @ -75,14 +74,6 @@ jobs: | |||
|           - "tortoiseorm" | ||||
|         os: [ubuntu-20.04] | ||||
|         exclude: | ||||
|           - python-version: py39 | ||||
|             package: "sklearn" | ||||
|           - python-version: py310 | ||||
|             package: "sklearn" | ||||
|           - python-version: py311 | ||||
|             package: "sklearn" | ||||
|           - python-version: py312 | ||||
|             package: "sklearn" | ||||
|           - python-version: py312 | ||||
|             package: "boto" | ||||
|           - python-version: py312 | ||||
|  | @ -103,8 +94,6 @@ jobs: | |||
|             package: "remoulade" | ||||
|           - python-version: pypy3 | ||||
|             package: "requests" | ||||
|           - python-version: pypy3 | ||||
|             package: "sklearn" | ||||
|           - python-version: pypy3 | ||||
|             package: "confluent-kafka" | ||||
|           - python-version: pypy3 | ||||
|  |  | |||
|  | @ -93,31 +93,3 @@ jobs: | |||
|           key: v7-build-tox-cache-${{ matrix.package }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }} | ||||
|       - name: run tox | ||||
|         run: tox -e lint-${{ matrix.package }} | ||||
| 
 | ||||
|   lint-3_8: | ||||
|     strategy: | ||||
|       fail-fast: false  # ensures the entire test matrix is run, even if one permutation fails | ||||
|       matrix: | ||||
|         package: | ||||
|           - "instrumentation-sklearn" | ||||
|         os: [ubuntu-20.04] | ||||
|     runs-on: ubuntu-20.04 | ||||
|     steps: | ||||
|       - name: Checkout Contrib Repo @ SHA - ${{ github.sha }} | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Python 3.8 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: 3.8 | ||||
|       - name: Install tox | ||||
|         run: pip install tox | ||||
|       - name: Cache tox environment | ||||
|         # Preserves .tox directory between runs for faster installs | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             .tox | ||||
|             ~/.cache/pip | ||||
|           key: v7-build-tox-cache-${{ matrix.package }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }} | ||||
|       - name: run tox | ||||
|         run: tox -e lint-${{ matrix.package }} | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 
 | ||||
| ### Added | ||||
| 
 | ||||
| - `opentelemetry-instrumentation-sklearn` Deprecated the sklearn instrumentation | ||||
|   ([#2708](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2708)) | ||||
| - `opentelemetry-instrumentation-pyramid` Record exceptions raised when serving a request | ||||
|   ([#2622](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2622)) | ||||
| - `opentelemetry-sdk-extension-aws` Add AwsXrayLambdaPropagator | ||||
|  |  | |||
|  | @ -54,7 +54,6 @@ packages= | |||
| [lintroots] | ||||
| extraroots=examples/*,scripts/ | ||||
| subglob=*.py,tests/,test/,src/*,examples/* | ||||
| ignore=sklearn | ||||
| 
 | ||||
| [testroots] | ||||
| extraroots=examples/*,tests/ | ||||
|  |  | |||
|  | @ -38,7 +38,6 @@ | |||
| | [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 | No | experimental | ||||
| | [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.50 | No | experimental | ||||
| | [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 | Yes | migration | ||||
| | [opentelemetry-instrumentation-sklearn](./opentelemetry-instrumentation-sklearn) | scikit-learn ~= 0.24.0 | No | experimental | ||||
| | [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy | Yes | experimental | ||||
| | [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No | experimental | ||||
| | [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette ~= 0.13.0 | Yes | experimental | ||||
|  |  | |||
|  | @ -1,201 +0,0 @@ | |||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
| 
 | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
| 
 | ||||
|    1. Definitions. | ||||
| 
 | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
| 
 | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
| 
 | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
| 
 | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
| 
 | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
| 
 | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
| 
 | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
| 
 | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
| 
 | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
| 
 | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
| 
 | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
| 
 | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
| 
 | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
| 
 | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
| 
 | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
| 
 | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
| 
 | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
| 
 | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
| 
 | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
| 
 | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
| 
 | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
| 
 | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
| 
 | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
| 
 | ||||
|    END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
| 
 | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
| 
 | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
|  | @ -1,24 +0,0 @@ | |||
| OpenTelemetry Scikit-Learn Instrumentation | ||||
| ========================================== | ||||
| 
 | ||||
| |pypi| | ||||
| 
 | ||||
| .. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-sklearn.svg | ||||
|    :target: https://pypi.org/project/opentelemetry-instrumentation-sklearn/ | ||||
| 
 | ||||
| This library allows tracing HTTP requests made by the | ||||
| `scikit-learn <https://scikit-learn.org/stable/>`_ library. | ||||
| 
 | ||||
| Installation | ||||
| ------------ | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|      pip install opentelemetry-instrumentation-sklearn | ||||
| 
 | ||||
| References | ||||
| ---------- | ||||
| 
 | ||||
| * `OpenTelemetry sklearn Instrumentation <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/sklearn/sklearn.html>`_ | ||||
| * `OpenTelemetry Project <https://opentelemetry.io/>`_ | ||||
| * `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_ | ||||
|  | @ -1,49 +0,0 @@ | |||
| [build-system] | ||||
| requires = ["hatchling"] | ||||
| build-backend = "hatchling.build" | ||||
| 
 | ||||
| [project] | ||||
| name = "opentelemetry-instrumentation-sklearn" | ||||
| dynamic = ["version"] | ||||
| description = "OpenTelemetry sklearn instrumentation" | ||||
| readme = "README.rst" | ||||
| license = "Apache-2.0" | ||||
| requires-python = ">=3.8" | ||||
| authors = [ | ||||
|   { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" }, | ||||
| ] | ||||
| classifiers = [ | ||||
|   "Development Status :: 4 - Beta", | ||||
|   "Intended Audience :: Developers", | ||||
|   "License :: OSI Approved :: Apache Software License", | ||||
|   "Programming Language :: Python", | ||||
|   "Programming Language :: Python :: 3", | ||||
|   "Programming Language :: Python :: 3.8", | ||||
| ] | ||||
| dependencies = [ | ||||
|   "opentelemetry-api ~= 1.12", | ||||
|   "opentelemetry-instrumentation == 0.47b0.dev", | ||||
| ] | ||||
| 
 | ||||
| [project.optional-dependencies] | ||||
| instruments = [ | ||||
|   "scikit-learn ~= 0.24.0", | ||||
| ] | ||||
| 
 | ||||
| [project.entry-points.opentelemetry_instrumentor] | ||||
| sklearn = "opentelemetry.instrumentation.sklearn:SklearnInstrumentor" | ||||
| 
 | ||||
| [project.urls] | ||||
| Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-sklearn" | ||||
| 
 | ||||
| [tool.hatch.version] | ||||
| path = "src/opentelemetry/instrumentation/sklearn/version.py" | ||||
| 
 | ||||
| [tool.hatch.build.targets.sdist] | ||||
| include = [ | ||||
|   "/src", | ||||
|   "/tests", | ||||
| ] | ||||
| 
 | ||||
| [tool.hatch.build.targets.wheel] | ||||
| packages = ["src/opentelemetry"] | ||||
|  | @ -1,792 +0,0 @@ | |||
| # Copyright 2020, OpenTelemetry Authors | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| """ | ||||
| The integration with sklearn supports the scikit-learn compatible libraries, | ||||
| it can be enabled by using ``SklearnInstrumentor``. | ||||
| 
 | ||||
| .. sklearn: https://github.com/scikit-learn/scikit-learn | ||||
| 
 | ||||
| Usage | ||||
| ----- | ||||
| 
 | ||||
| Package instrumentation example: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from opentelemetry.instrumentation.sklearn import SklearnInstrumentor | ||||
| 
 | ||||
|     # instrument the sklearn library | ||||
|     SklearnInstrumentor().instrument() | ||||
| 
 | ||||
|     # instrument sklearn and other libraries | ||||
|     SklearnInstrumentor( | ||||
|         packages=["sklearn", "lightgbm", "xgboost"] | ||||
|     ).instrument() | ||||
| 
 | ||||
| 
 | ||||
| Model instrumentation example: | ||||
| 
 | ||||
| .. code-block:: python | ||||
| 
 | ||||
|     from opentelemetry.instrumentation.sklearn import SklearnInstrumentor | ||||
|     from sklearn.datasets import load_iris | ||||
|     from sklearn.ensemble import RandomForestClassifier | ||||
|     from sklearn.model_selection import train_test_split | ||||
|     from sklearn.pipeline import Pipeline | ||||
| 
 | ||||
|     X, y = load_iris(return_X_y=True) | ||||
|     X_train, X_test, y_train, y_test = train_test_split(X, y) | ||||
| 
 | ||||
|     model = Pipeline( | ||||
|         [ | ||||
|             ("class", RandomForestClassifier(n_estimators=10)), | ||||
|         ] | ||||
|     ) | ||||
| 
 | ||||
|     model.fit(X_train, y_train) | ||||
| 
 | ||||
|     SklearnInstrumentor().instrument_estimator(model) | ||||
| 
 | ||||
| """ | ||||
| import logging | ||||
| import os | ||||
| from functools import wraps | ||||
| from importlib import import_module | ||||
| from inspect import isclass | ||||
| from pkgutil import iter_modules | ||||
| from typing import ( | ||||
|     Callable, | ||||
|     Collection, | ||||
|     Dict, | ||||
|     List, | ||||
|     MutableMapping, | ||||
|     Sequence, | ||||
|     Type, | ||||
|     Union, | ||||
| ) | ||||
| 
 | ||||
| from sklearn.base import BaseEstimator | ||||
| from sklearn.pipeline import FeatureUnion, Pipeline | ||||
| from sklearn.tree import BaseDecisionTree | ||||
| from sklearn.utils.metaestimators import _IffHasAttrDescriptor | ||||
| 
 | ||||
| from opentelemetry.instrumentation.instrumentor import BaseInstrumentor | ||||
| 
 | ||||
| # pylint: disable=no-name-in-module | ||||
| from opentelemetry.instrumentation.sklearn.package import _instruments | ||||
| from opentelemetry.instrumentation.sklearn.version import __version__ | ||||
| from opentelemetry.trace import get_tracer | ||||
| from opentelemetry.util.types import Attributes | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def implement_span_estimator( | ||||
|     func: Callable, | ||||
|     estimator: Union[BaseEstimator, Type[BaseEstimator]], | ||||
|     attributes: Attributes = None, | ||||
| ): | ||||
|     """Wrap the method call with a span. | ||||
| 
 | ||||
|     Args: | ||||
|         func: A callable to be wrapped in a span | ||||
|         estimator: An instance or class of an estimator | ||||
|         attributes: Attributes to apply to the span | ||||
| 
 | ||||
|     Returns: | ||||
|         The passed function wrapped in a span. | ||||
|     """ | ||||
|     if isclass(estimator): | ||||
|         name = estimator.__name__ | ||||
|     else: | ||||
|         name = estimator.__class__.__name__ | ||||
|     logger.debug("Instrumenting: %s.%s", name, func.__name__) | ||||
|     attributes = attributes or {} | ||||
|     name = f"{name}.{func.__name__}" | ||||
|     return implement_span_function(func, name, attributes) | ||||
| 
 | ||||
| 
 | ||||
| def implement_span_function(func: Callable, name: str, attributes: Attributes): | ||||
|     """Wrap the function with a span. | ||||
| 
 | ||||
|     Args: | ||||
|         func: A callable to be wrapped in a span | ||||
|         name: The name of the span | ||||
|         attributes: Attributes to apply to the span | ||||
| 
 | ||||
|     Returns: | ||||
|         The passed function wrapped in a span. | ||||
|     """ | ||||
| 
 | ||||
|     @wraps(func) | ||||
|     def wrapper(*args, **kwargs): | ||||
|         with get_tracer( | ||||
|             __name__, | ||||
|             __version__, | ||||
|             schema_url="https://opentelemetry.io/schemas/1.11.0", | ||||
|         ).start_as_current_span(name=name) as span: | ||||
|             if span.is_recording(): | ||||
|                 for key, val in attributes.items(): | ||||
|                     span.set_attribute(key, val) | ||||
|             return func(*args, **kwargs) | ||||
| 
 | ||||
|     return wrapper | ||||
| 
 | ||||
| 
 | ||||
| def implement_span_delegator( | ||||
|     obj: _IffHasAttrDescriptor, attributes: Attributes = None | ||||
| ): | ||||
|     """Wrap the descriptor's fn with a span. | ||||
| 
 | ||||
|     Args: | ||||
|         obj: An instance of _IffHasAttrDescriptor | ||||
|         attributes: Attributes to apply to the span | ||||
|     """ | ||||
|     # Don't instrument inherited delegators | ||||
|     if hasattr(obj, "_otel_original_fn"): | ||||
|         logger.debug("Already instrumented: %s", obj.fn.__qualname__) | ||||
|         return | ||||
|     logger.debug("Instrumenting: %s", obj.fn.__qualname__) | ||||
|     attributes = attributes or {} | ||||
|     setattr(obj, "_otel_original_fn", getattr(obj, "fn")) | ||||
|     setattr( | ||||
|         obj, | ||||
|         "fn", | ||||
|         implement_span_function(obj.fn, obj.fn.__qualname__, attributes), | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def get_delegator( | ||||
|     estimator: Type[BaseEstimator], method_name: str | ||||
| ) -> Union[_IffHasAttrDescriptor, None]: | ||||
|     """Get the delegator from a class method or None. | ||||
| 
 | ||||
|     Args: | ||||
|         estimator: A class derived from ``sklearn``'s ``BaseEstimator``. | ||||
|         method_name (str): The method name of the estimator on which to | ||||
|           check for delegation. | ||||
| 
 | ||||
|     Returns: | ||||
|         The delegator, if one exists, otherwise None. | ||||
|     """ | ||||
|     class_attr = getattr(estimator, method_name) | ||||
|     if getattr(class_attr, "__closure__", None) is not None: | ||||
|         for cell in class_attr.__closure__: | ||||
|             if isinstance(cell.cell_contents, _IffHasAttrDescriptor): | ||||
|                 return cell.cell_contents | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| def get_base_estimators(packages: List[str]) -> Dict[str, Type[BaseEstimator]]: | ||||
|     """Walk package hierarchies to get BaseEstimator-derived classes. | ||||
| 
 | ||||
|     Args: | ||||
|         packages (list(str)): A list of package names to instrument. | ||||
| 
 | ||||
|     Returns: | ||||
|         A dictionary of qualnames and classes inheriting from | ||||
|         ``BaseEstimator``. | ||||
|     """ | ||||
|     klasses = {} | ||||
|     for package_name in packages: | ||||
|         lib = import_module(package_name) | ||||
|         package_dir = os.path.dirname(lib.__file__) | ||||
|         for _, module_name, _ in iter_modules([package_dir]): | ||||
|             # import the module and iterate through its attributes | ||||
|             try: | ||||
|                 module = import_module(package_name + "." + module_name) | ||||
|             except ImportError: | ||||
|                 logger.warning( | ||||
|                     "Unable to import %s.%s", package_name, module_name | ||||
|                 ) | ||||
|                 continue | ||||
|             for attribute_name in dir(module): | ||||
|                 attrib = getattr(module, attribute_name) | ||||
|                 if isclass(attrib) and issubclass(attrib, BaseEstimator): | ||||
|                     klasses[ | ||||
|                         ".".join([package_name, module_name, attribute_name]) | ||||
|                     ] = attrib | ||||
|     return klasses | ||||
| 
 | ||||
| 
 | ||||
| # Methods on which spans should be applied. | ||||
| DEFAULT_METHODS = [ | ||||
|     "fit", | ||||
|     "transform", | ||||
|     "predict", | ||||
|     "predict_proba", | ||||
|     "_fit", | ||||
|     "_transform", | ||||
|     "_predict", | ||||
|     "_predict_proba", | ||||
| ] | ||||
| 
 | ||||
| # Classes and their attributes which contain a list of tupled estimators | ||||
| # through which we should walk recursively for estimators. | ||||
| DEFAULT_NAMEDTUPLE_ATTRIBS = { | ||||
|     Pipeline: ["steps"], | ||||
|     FeatureUnion: ["transformer_list"], | ||||
| } | ||||
| 
 | ||||
| # Classes and their attributes which contain an estimator or sequence of | ||||
| # estimators through which we should walk recursively for estimators. | ||||
| DEFAULT_ATTRIBS = {} | ||||
| 
 | ||||
| # Classes (including children) explicitly excluded from autoinstrumentation | ||||
| DEFAULT_EXCLUDE_CLASSES = [BaseDecisionTree] | ||||
| 
 | ||||
| # Default packages for autoinstrumentation | ||||
| DEFAULT_PACKAGES = ["sklearn"] | ||||
| 
 | ||||
| 
 | ||||
| class SklearnInstrumentor(BaseInstrumentor): | ||||
|     """Instrument a fitted sklearn model with opentelemetry spans. | ||||
| 
 | ||||
|     Instrument methods of ``BaseEstimator``-derived components in a sklearn | ||||
|     model.  The assumption is that a machine learning model ``Pipeline`` (or | ||||
|     class descendent) is being instrumented with opentelemetry. Within a | ||||
|     ``Pipeline`` is some hierarchy of estimators and transformers. | ||||
| 
 | ||||
|     The ``instrument_estimator`` method walks this hierarchy of estimators, | ||||
|     implementing each of the defined methods with its own span. | ||||
| 
 | ||||
|     Certain estimators in the sklearn ecosystem contain other estimators as | ||||
|     instance attributes. Support for walking this embedded sub-hierarchy is | ||||
|     supported with ``recurse_attribs``. This argument is a dictionary | ||||
|     with classes as keys, and a list of attributes representing embedded | ||||
|     estimators as values. By default, ``recurse_attribs`` is empty. | ||||
| 
 | ||||
|     Similar to Pipelines, there are also estimators which have class attributes | ||||
|     as a list of 2-tuples; for instance, the ``FeatureUnion`` and its attribute | ||||
|     ``transformer_list``. Instrumenting estimators like this is also | ||||
|     supported through the ``recurse_namedtuple_attribs`` argument. This | ||||
|     argument is a dictionary with classes as keys, and a list of attribute | ||||
|     names representing the namedtuple list(s). By default, the | ||||
|     ``recurse_namedtuple_attribs`` dictionary supports | ||||
|     ``Pipeline`` with ``steps``, and ``FeatureUnion`` with | ||||
|     ``transformer_list``. | ||||
| 
 | ||||
|     Note that spans will not be generated for any child transformer whose | ||||
|     parent transformer has ``n_jobs`` parameter set to anything besides | ||||
|     ``None`` or ``1``. | ||||
| 
 | ||||
|     Package instrumentation example: | ||||
| 
 | ||||
|     .. code-block:: python | ||||
| 
 | ||||
|         from opentelemetry.instrumentation.sklearn import SklearnInstrumentor | ||||
| 
 | ||||
|         # instrument the sklearn library | ||||
|         SklearnInstrumentor().instrument() | ||||
| 
 | ||||
|         # instrument several sklearn-compatible libraries | ||||
|         packages = ["sklearn", "lightgbm", "xgboost"] | ||||
|         SklearnInstrumentor(packages=packages).instrument() | ||||
| 
 | ||||
| 
 | ||||
|     Model instrumentation example: | ||||
| 
 | ||||
|     .. code-block:: python | ||||
| 
 | ||||
|         from opentelemetry.instrumentation.sklearn import SklearnInstrumentor | ||||
|         from sklearn.datasets import load_iris | ||||
|         from sklearn.ensemble import RandomForestClassifier | ||||
|         from sklearn.model_selection import train_test_split | ||||
|         from sklearn.pipeline import Pipeline | ||||
| 
 | ||||
|         X, y = load_iris(return_X_y=True) | ||||
|         X_train, X_test, y_train, y_test = train_test_split(X, y) | ||||
| 
 | ||||
|         model = Pipeline( | ||||
|             [ | ||||
|                 ("class", RandomForestClassifier(n_estimators=10)), | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|         model.fit(X_train, y_train) | ||||
| 
 | ||||
|         SklearnInstrumentor().instrument_estimator(model) | ||||
| 
 | ||||
|     Args: | ||||
|         methods (list): A list of method names on which to instrument a span. | ||||
|           This list of methods will be checked on all estimators in the model | ||||
|           hierarchy. Used in package and model instrumentation | ||||
|         recurse_attribs (dict): A dictionary of ``BaseEstimator``-derived | ||||
|           sklearn classes as keys, with values being a list of attributes. Each | ||||
|           attribute represents either an estimator or list of estimators on | ||||
|           which to also implement spans. An example is | ||||
|           ``RandomForestClassifier`` and its attribute ``estimators_``. Used | ||||
|           in model instrumentation only. | ||||
|         recurse_namedtuple_attribs (dict): A dictionary of ``BaseEstimator``- | ||||
|           derived sklearn types as keys, with values being a list of | ||||
|           attribute names. Each attribute represents a list of 2-tuples in | ||||
|           which the first element is the estimator name, and the second | ||||
|           element is the estimator. Defaults include sklearn's ``Pipeline`` | ||||
|           and its attribute ``steps``, and the ``FeatureUnion`` and its | ||||
|           attribute ``transformer_list``. Used in model instrumentation only. | ||||
|         packages: A list of sklearn-compatible packages to | ||||
|           instrument. Used with package instrumentation only. | ||||
|         exclude_classes: A list of classes to exclude from instrumentation. | ||||
|           Child classes are also excluded. Default is sklearn's | ||||
|           ``[BaseDecisionTree]``. | ||||
|     """ | ||||
| 
 | ||||
|     def __new__(cls, *args, **kwargs): | ||||
|         """Override new. | ||||
| 
 | ||||
|         The base class' new method passes args and kwargs. We override because | ||||
|         we init the class with configuration and Python raises TypeError when | ||||
|         additional arguments are passed to the object.__new__() method. | ||||
|         """ | ||||
|         if cls._instance is None: | ||||
|             cls._instance = object.__new__(cls) | ||||
| 
 | ||||
|         return cls._instance | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         methods: List[str] = None, | ||||
|         recurse_attribs: Dict[Type[BaseEstimator], List[str]] = None, | ||||
|         recurse_namedtuple_attribs: Dict[ | ||||
|             Type[BaseEstimator], List[str] | ||||
|         ] = None, | ||||
|         packages: List[str] = None, | ||||
|         exclude_classes: List[Type] = None, | ||||
|     ): | ||||
|         self.methods = methods or DEFAULT_METHODS | ||||
|         self.recurse_attribs = recurse_attribs or DEFAULT_ATTRIBS | ||||
|         self.recurse_namedtuple_attribs = ( | ||||
|             recurse_namedtuple_attribs or DEFAULT_NAMEDTUPLE_ATTRIBS | ||||
|         ) | ||||
|         self.packages = packages or DEFAULT_PACKAGES | ||||
|         if exclude_classes is None: | ||||
|             self.exclude_classes = tuple(DEFAULT_EXCLUDE_CLASSES) | ||||
|         else: | ||||
|             self.exclude_classes = tuple(exclude_classes) | ||||
| 
 | ||||
|     def instrumentation_dependencies(self) -> Collection[str]: | ||||
|         return _instruments | ||||
| 
 | ||||
|     def _instrument(self, **kwargs): | ||||
|         """Instrument the library, and any additional specified on init.""" | ||||
|         klasses = get_base_estimators(packages=self.packages) | ||||
|         attributes = kwargs.get("attributes") | ||||
|         for _, klass in klasses.items(): | ||||
|             if issubclass(klass, self.exclude_classes): | ||||
|                 logger.debug("Not instrumenting (excluded): %s", str(klass)) | ||||
|             else: | ||||
|                 logger.debug("Instrumenting: %s", str(klass)) | ||||
|                 for method_name in self.methods: | ||||
|                     if hasattr(klass, method_name): | ||||
|                         self._instrument_class_method( | ||||
|                             estimator=klass, | ||||
|                             method_name=method_name, | ||||
|                             attributes=attributes, | ||||
|                         ) | ||||
| 
 | ||||
|     def _uninstrument(self, **kwargs): | ||||
|         """Uninstrument the library""" | ||||
|         klasses = get_base_estimators(packages=self.packages) | ||||
|         for _, klass in klasses.items(): | ||||
|             logger.debug("Uninstrumenting: %s", str(klass)) | ||||
|             for method_name in self.methods: | ||||
|                 if hasattr(klass, method_name): | ||||
|                     self._uninstrument_class_method( | ||||
|                         estimator=klass, method_name=method_name | ||||
|                     ) | ||||
| 
 | ||||
|     def instrument_estimator( | ||||
|         self, estimator: BaseEstimator, attributes: Attributes = None | ||||
|     ): | ||||
|         """Instrument a fitted estimator and its hierarchy where configured. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (sklearn.base.BaseEstimator): A fitted ``sklearn`` | ||||
|               estimator, typically a ``Pipeline`` instance. | ||||
|             attributes (dict): Attributes to attach to the spans. | ||||
|         """ | ||||
|         if isinstance(estimator, self.exclude_classes): | ||||
|             logger.debug( | ||||
|                 "Not instrumenting (excluded): %s", | ||||
|                 estimator.__class__.__name__, | ||||
|             ) | ||||
|             return | ||||
| 
 | ||||
|         if isinstance( | ||||
|             estimator, tuple(self.recurse_namedtuple_attribs.keys()) | ||||
|         ): | ||||
|             self._instrument_estimator_namedtuple( | ||||
|                 estimator=estimator, attributes=attributes | ||||
|             ) | ||||
| 
 | ||||
|         if isinstance(estimator, tuple(self.recurse_attribs.keys())): | ||||
|             self._instrument_estimator_attribute( | ||||
|                 estimator=estimator, attributes=attributes | ||||
|             ) | ||||
| 
 | ||||
|         for method_name in self.methods: | ||||
|             if hasattr(estimator, method_name): | ||||
|                 self._instrument_instance_method( | ||||
|                     estimator=estimator, | ||||
|                     method_name=method_name, | ||||
|                     attributes=attributes, | ||||
|                 ) | ||||
| 
 | ||||
|     def uninstrument_estimator(self, estimator: BaseEstimator): | ||||
|         """Uninstrument a fitted estimator and its hierarchy where configured. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (sklearn.base.BaseEstimator): A fitted ``sklearn`` | ||||
|               estimator, typically a ``Pipeline`` instance. | ||||
|         """ | ||||
|         if isinstance(estimator, self.exclude_classes): | ||||
|             logger.debug( | ||||
|                 "Not uninstrumenting (excluded): %s", | ||||
|                 estimator.__class__.__name__, | ||||
|             ) | ||||
|             return | ||||
| 
 | ||||
|         if isinstance( | ||||
|             estimator, tuple(self.recurse_namedtuple_attribs.keys()) | ||||
|         ): | ||||
|             self._uninstrument_estimator_namedtuple(estimator=estimator) | ||||
| 
 | ||||
|         if isinstance(estimator, tuple(self.recurse_attribs.keys())): | ||||
|             self._uninstrument_estimator_attribute(estimator=estimator) | ||||
| 
 | ||||
|         for method_name in self.methods: | ||||
|             if hasattr(estimator, method_name): | ||||
|                 self._uninstrument_instance_method( | ||||
|                     estimator=estimator, method_name=method_name | ||||
|                 ) | ||||
| 
 | ||||
|     def _check_instrumented( | ||||
|         self, | ||||
|         estimator: Union[BaseEstimator, Type[BaseEstimator]], | ||||
|         method_name: str, | ||||
|     ) -> bool: | ||||
|         """Check an estimator-method is instrumented. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A class or instance of an ``sklearn`` | ||||
|               estimator. | ||||
|             method_name (str): The method name of the estimator on which to | ||||
|               check for instrumentation. | ||||
|         """ | ||||
|         orig_method_name = "_otel_original_" + method_name | ||||
|         has_original = hasattr(estimator, orig_method_name) | ||||
|         orig_class, orig_method = getattr( | ||||
|             estimator, orig_method_name, (None, None) | ||||
|         ) | ||||
|         same_class = orig_class == estimator | ||||
|         if has_original and same_class: | ||||
|             class_method = self._unwrap_function( | ||||
|                 getattr(estimator, method_name) | ||||
|             ) | ||||
|             # if they match then the subclass doesn't override | ||||
|             # if they don't then the overridden method needs instrumentation | ||||
|             if class_method.__name__ == orig_method.__name__: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     def _uninstrument_class_method( | ||||
|         self, estimator: Type[BaseEstimator], method_name: str | ||||
|     ): | ||||
|         """Uninstrument a class method. | ||||
| 
 | ||||
|         Replaces the patched method with the original, and deletes the | ||||
|         attribute which stored the original method. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A class or instance of an ``sklearn`` | ||||
|               estimator. | ||||
|             method_name (str): The method name of the estimator on which to | ||||
|               apply a span. | ||||
|         """ | ||||
|         orig_method_name = "_otel_original_" + method_name | ||||
|         if isclass(estimator): | ||||
|             qualname = estimator.__qualname__ | ||||
|         else: | ||||
|             qualname = estimator.__class__.__qualname__ | ||||
|         delegator = get_delegator(estimator, method_name) | ||||
|         if self._check_instrumented(estimator, method_name): | ||||
|             logger.debug( | ||||
|                 "Uninstrumenting: %s.%s", | ||||
|                 qualname, | ||||
|                 method_name, | ||||
|             ) | ||||
|             _, orig_method = getattr(estimator, orig_method_name) | ||||
|             setattr( | ||||
|                 estimator, | ||||
|                 method_name, | ||||
|                 orig_method, | ||||
|             ) | ||||
|             delattr(estimator, orig_method_name) | ||||
|         elif delegator is not None: | ||||
|             if not hasattr(delegator, "_otel_original_fn"): | ||||
|                 logger.debug( | ||||
|                     "Already uninstrumented: %s.%s", | ||||
|                     qualname, | ||||
|                     method_name, | ||||
|                 ) | ||||
|                 return | ||||
|             setattr( | ||||
|                 delegator, | ||||
|                 "fn", | ||||
|                 getattr(delegator, "_otel_original_fn"), | ||||
|             ) | ||||
|             delattr(delegator, "_otel_original_fn") | ||||
|         else: | ||||
|             logger.debug( | ||||
|                 "Already uninstrumented: %s.%s", | ||||
|                 qualname, | ||||
|                 method_name, | ||||
|             ) | ||||
| 
 | ||||
|     def _uninstrument_instance_method( | ||||
|         self, estimator: BaseEstimator, method_name: str | ||||
|     ): | ||||
|         """Uninstrument an instance method. | ||||
| 
 | ||||
|         Replaces the patched method with the original, and deletes the | ||||
|         attribute which stored the original method. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A class or instance of an ``sklearn`` | ||||
|               estimator. | ||||
|             method_name (str): The method name of the estimator on which to | ||||
|               apply a span. | ||||
|         """ | ||||
|         orig_method_name = "_otel_original_" + method_name | ||||
|         if isclass(estimator): | ||||
|             qualname = estimator.__qualname__ | ||||
|         else: | ||||
|             qualname = estimator.__class__.__qualname__ | ||||
|         if self._check_instrumented(estimator, method_name): | ||||
|             logger.debug( | ||||
|                 "Uninstrumenting: %s.%s", | ||||
|                 qualname, | ||||
|                 method_name, | ||||
|             ) | ||||
|             _, orig_method = getattr(estimator, orig_method_name) | ||||
|             setattr( | ||||
|                 estimator, | ||||
|                 method_name, | ||||
|                 orig_method, | ||||
|             ) | ||||
|             delattr(estimator, orig_method_name) | ||||
|         else: | ||||
|             logger.debug( | ||||
|                 "Already uninstrumented: %s.%s", | ||||
|                 qualname, | ||||
|                 method_name, | ||||
|             ) | ||||
| 
 | ||||
|     def _instrument_class_method( | ||||
|         self, | ||||
|         estimator: Type[BaseEstimator], | ||||
|         method_name: str, | ||||
|         attributes: Attributes = None, | ||||
|     ): | ||||
|         """Instrument an estimator method with a span. | ||||
| 
 | ||||
|         When instrumenting we attach a tuple of (Class, method) to the | ||||
|         attribute ``_otel_original_<method_name>`` for each method. This allows | ||||
|         us to replace the patched with the original in uninstrumentation, but | ||||
|         also allows proper instrumentation of child classes without | ||||
|         instrumenting inherited methods twice. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A ``BaseEstimator``-derived | ||||
|               class | ||||
|             method_name (str): The method name of the estimator on which to | ||||
|               apply a span. | ||||
|             attributes (dict): Attributes to attach to the spans. | ||||
|         """ | ||||
|         if self._check_instrumented(estimator, method_name): | ||||
|             logger.debug( | ||||
|                 "Already instrumented: %s.%s", | ||||
|                 estimator.__qualname__, | ||||
|                 method_name, | ||||
|             ) | ||||
|             return | ||||
|         class_attr = getattr(estimator, method_name) | ||||
|         delegator = get_delegator(estimator, method_name) | ||||
|         if isinstance(class_attr, property): | ||||
|             logger.debug( | ||||
|                 "Not instrumenting found property: %s.%s", | ||||
|                 estimator.__qualname__, | ||||
|                 method_name, | ||||
|             ) | ||||
|         elif delegator is not None: | ||||
|             implement_span_delegator(delegator) | ||||
|         else: | ||||
|             setattr( | ||||
|                 estimator, | ||||
|                 "_otel_original_" + method_name, | ||||
|                 (estimator, class_attr), | ||||
|             ) | ||||
|             setattr( | ||||
|                 estimator, | ||||
|                 method_name, | ||||
|                 implement_span_estimator(class_attr, estimator, attributes), | ||||
|             ) | ||||
| 
 | ||||
|     def _unwrap_function(self, function): | ||||
|         """Fetch the function underlying any decorators""" | ||||
|         if hasattr(function, "__wrapped__"): | ||||
|             return self._unwrap_function(function.__wrapped__) | ||||
|         return function | ||||
| 
 | ||||
|     def _instrument_instance_method( | ||||
|         self, | ||||
|         estimator: BaseEstimator, | ||||
|         method_name: str, | ||||
|         attributes: Attributes = None, | ||||
|     ): | ||||
|         """Instrument an estimator instance method with a span. | ||||
| 
 | ||||
|         When instrumenting we attach a tuple of (Class, method) to the | ||||
|         attribute ``_otel_original_<method_name>`` for each method. This allows | ||||
|         us to replace the patched with the original in unstrumentation. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A fitted ``sklearn`` estimator. | ||||
|             method_name (str): The method name of the estimator on which to | ||||
|               apply a span. | ||||
|             attributes (dict): Attributes to attach to the spans. | ||||
|         """ | ||||
|         if self._check_instrumented(estimator, method_name): | ||||
|             logger.debug( | ||||
|                 "Already instrumented: %s.%s", | ||||
|                 estimator.__class__.__qualname__, | ||||
|                 method_name, | ||||
|             ) | ||||
|             return | ||||
| 
 | ||||
|         class_attr = getattr(type(estimator), method_name, None) | ||||
|         if isinstance(class_attr, property): | ||||
|             logger.debug( | ||||
|                 "Not instrumenting found property: %s.%s", | ||||
|                 estimator.__class__.__qualname__, | ||||
|                 method_name, | ||||
|             ) | ||||
|         else: | ||||
|             method = getattr(estimator, method_name) | ||||
|             setattr( | ||||
|                 estimator, "_otel_original_" + method_name, (estimator, method) | ||||
|             ) | ||||
|             setattr( | ||||
|                 estimator, | ||||
|                 method_name, | ||||
|                 implement_span_estimator(method, estimator, attributes), | ||||
|             ) | ||||
| 
 | ||||
|     def _instrument_estimator_attribute( | ||||
|         self, estimator: BaseEstimator, attributes: Attributes = None | ||||
|     ): | ||||
|         """Instrument instance attributes which also contain estimators. | ||||
| 
 | ||||
|         Handle instance attributes which are also estimators, are a list | ||||
|         (Sequence) of estimators, or are mappings (dictionary) in which | ||||
|         the values are estimators. | ||||
| 
 | ||||
|         Examples include ``RandomForestClassifier`` and | ||||
|         ``MultiOutputRegressor`` instances which have attributes | ||||
|         ``estimators_`` attributes. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A fitted ``sklearn`` estimator, with an | ||||
|               attribute which also contains an estimator or collection of | ||||
|               estimators. | ||||
|             attributes (dict): Attributes to attach to the spans. | ||||
|         """ | ||||
|         attribs = self.recurse_attribs.get(estimator.__class__, []) | ||||
|         for attrib in attribs: | ||||
|             attrib_value = getattr(estimator, attrib) | ||||
|             if isinstance(attrib_value, Sequence): | ||||
|                 for value in attrib_value: | ||||
|                     self.instrument_estimator( | ||||
|                         estimator=value, attributes=attributes | ||||
|                     ) | ||||
|             elif isinstance(attrib_value, MutableMapping): | ||||
|                 for value in attrib_value.values(): | ||||
|                     self.instrument_estimator( | ||||
|                         estimator=value, attributes=attributes | ||||
|                     ) | ||||
|             else: | ||||
|                 self.instrument_estimator( | ||||
|                     estimator=attrib_value, attributes=attributes | ||||
|                 ) | ||||
| 
 | ||||
|     def _instrument_estimator_namedtuple( | ||||
|         self, estimator: BaseEstimator, attributes: Attributes = None | ||||
|     ): | ||||
|         """Instrument attributes with (name, estimator) tupled components. | ||||
| 
 | ||||
|         Examples include Pipeline and FeatureUnion instances which | ||||
|         have attributes steps and transformer_list, respectively. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator: A fitted sklearn estimator, with an attribute which also | ||||
|               contains an estimator or collection of estimators. | ||||
|             attributes (dict): Attributes to attach to the spans. | ||||
|         """ | ||||
|         attribs = self.recurse_namedtuple_attribs.get(estimator.__class__, []) | ||||
|         for attrib in attribs: | ||||
|             for _, est in getattr(estimator, attrib): | ||||
|                 self.instrument_estimator(estimator=est, attributes=attributes) | ||||
| 
 | ||||
|     def _uninstrument_estimator_attribute(self, estimator: BaseEstimator): | ||||
|         """Uninstrument instance attributes which also contain estimators. | ||||
| 
 | ||||
|         Handle instance attributes which are also estimators, are a list | ||||
|         (Sequence) of estimators, or are mappings (dictionary) in which | ||||
|         the values are estimators. | ||||
| 
 | ||||
|         Examples include ``RandomForestClassifier`` and | ||||
|         ``MultiOutputRegressor`` instances which have attributes | ||||
|         ``estimators_`` attributes. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator (BaseEstimator): A fitted ``sklearn`` estimator, with an | ||||
|               attribute which also contains an estimator or collection of | ||||
|               estimators. | ||||
|         """ | ||||
|         attribs = self.recurse_attribs.get(estimator.__class__, []) | ||||
|         for attrib in attribs: | ||||
|             attrib_value = getattr(estimator, attrib) | ||||
|             if isinstance(attrib_value, Sequence): | ||||
|                 for value in attrib_value: | ||||
|                     self.uninstrument_estimator(estimator=value) | ||||
|             elif isinstance(attrib_value, MutableMapping): | ||||
|                 for value in attrib_value.values(): | ||||
|                     self.uninstrument_estimator(estimator=value) | ||||
|             else: | ||||
|                 self.uninstrument_estimator(estimator=attrib_value) | ||||
| 
 | ||||
|     def _uninstrument_estimator_namedtuple(self, estimator: BaseEstimator): | ||||
|         """Uninstrument attributes with (name, estimator) tupled components. | ||||
| 
 | ||||
|         Examples include Pipeline and FeatureUnion instances which | ||||
|         have attributes steps and transformer_list, respectively. | ||||
| 
 | ||||
|         Args: | ||||
|             estimator: A fitted sklearn estimator, with an attribute which also | ||||
|               contains an estimator or collection of estimators. | ||||
|         """ | ||||
|         attribs = self.recurse_namedtuple_attribs.get(estimator.__class__, []) | ||||
|         for attrib in attribs: | ||||
|             for _, est in getattr(estimator, attrib): | ||||
|                 self.uninstrument_estimator(estimator=est) | ||||
|  | @ -1,16 +0,0 @@ | |||
| # Copyright 2020, OpenTelemetry Authors | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| 
 | ||||
| _instruments = ("scikit-learn ~= 0.24.0",) | ||||
|  | @ -1,15 +0,0 @@ | |||
| # Copyright 2020, OpenTelemetry Authors | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| __version__ = "0.47b0.dev" | ||||
|  | @ -1,19 +0,0 @@ | |||
| asgiref==3.7.2 | ||||
| Deprecated==1.2.14 | ||||
| importlib-metadata==6.11.0 | ||||
| iniconfig==2.0.0 | ||||
| joblib==1.3.2 | ||||
| numpy==1.24.4 | ||||
| packaging==24.0 | ||||
| pluggy==1.5.0 | ||||
| py-cpuinfo==9.0.0 | ||||
| pytest==7.4.4 | ||||
| scikit-learn==0.24.2 | ||||
| scipy==1.10.1 | ||||
| threadpoolctl==3.3.0 | ||||
| tomli==2.0.1 | ||||
| typing_extensions==4.10.0 | ||||
| wrapt==1.16.0 | ||||
| zipp==3.19.2 | ||||
| -e opentelemetry-instrumentation | ||||
| -e instrumentation/opentelemetry-instrumentation-sklearn | ||||
|  | @ -1,54 +0,0 @@ | |||
| # Copyright 2020, OpenTelemetry Authors | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| import numpy as np | ||||
| from sklearn.datasets import load_iris | ||||
| from sklearn.decomposition import PCA, TruncatedSVD | ||||
| from sklearn.ensemble import RandomForestClassifier | ||||
| from sklearn.model_selection import train_test_split | ||||
| from sklearn.pipeline import FeatureUnion, Pipeline | ||||
| from sklearn.preprocessing import Normalizer, StandardScaler | ||||
| 
 | ||||
| X, y = load_iris(return_X_y=True) | ||||
| X_train, X_test, y_train, y_test = train_test_split(X, y) | ||||
| 
 | ||||
| 
 | ||||
| def pipeline(): | ||||
|     """A dummy model that has a bunch of components that we can test.""" | ||||
|     model = Pipeline( | ||||
|         [ | ||||
|             ("scaler", StandardScaler()), | ||||
|             ("normal", Normalizer()), | ||||
|             ( | ||||
|                 "union", | ||||
|                 FeatureUnion( | ||||
|                     [ | ||||
|                         ("pca", PCA(n_components=1)), | ||||
|                         ("svd", TruncatedSVD(n_components=2)), | ||||
|                     ], | ||||
|                     n_jobs=1,  # parallelized components won't generate spans | ||||
|                 ), | ||||
|             ), | ||||
|             ("class", RandomForestClassifier(n_estimators=10)), | ||||
|         ] | ||||
|     ) | ||||
|     model.fit(X_train, y_train) | ||||
|     return model | ||||
| 
 | ||||
| 
 | ||||
| def random_input(): | ||||
|     """A random record from the feature set.""" | ||||
|     rows = X.shape[0] | ||||
|     random_row = np.random.choice(rows, size=1) | ||||
|     return X[random_row, :] | ||||
|  | @ -1,190 +0,0 @@ | |||
| # Copyright 2020, OpenTelemetry Authors | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| from sklearn.ensemble import RandomForestClassifier | ||||
| 
 | ||||
| # pylint: disable=no-name-in-module | ||||
| from opentelemetry.instrumentation.sklearn import ( | ||||
|     DEFAULT_EXCLUDE_CLASSES, | ||||
|     DEFAULT_METHODS, | ||||
|     SklearnInstrumentor, | ||||
|     get_base_estimators, | ||||
|     get_delegator, | ||||
| ) | ||||
| from opentelemetry.test.test_base import TestBase | ||||
| from opentelemetry.trace import SpanKind | ||||
| 
 | ||||
| from .fixtures import pipeline, random_input | ||||
| 
 | ||||
| 
 | ||||
| def assert_instrumented(base_estimators): | ||||
|     for _, estimator in base_estimators.items(): | ||||
|         for method_name in DEFAULT_METHODS: | ||||
|             original_method_name = "_otel_original_" + method_name | ||||
|             if issubclass(estimator, tuple(DEFAULT_EXCLUDE_CLASSES)): | ||||
|                 assert not hasattr(estimator, original_method_name) | ||||
|                 continue | ||||
|             class_attr = getattr(estimator, method_name, None) | ||||
|             if isinstance(class_attr, property): | ||||
|                 assert not hasattr(estimator, original_method_name) | ||||
|                 continue | ||||
|             delegator = None | ||||
|             if hasattr(estimator, method_name): | ||||
|                 delegator = get_delegator(estimator, method_name) | ||||
|             if delegator is not None: | ||||
|                 assert hasattr(delegator, "_otel_original_fn") | ||||
|             elif hasattr(estimator, method_name): | ||||
|                 assert hasattr(estimator, original_method_name) | ||||
| 
 | ||||
| 
 | ||||
| def assert_uninstrumented(base_estimators): | ||||
|     for _, estimator in base_estimators.items(): | ||||
|         for method_name in DEFAULT_METHODS: | ||||
|             original_method_name = "_otel_original_" + method_name | ||||
|             if issubclass(estimator, tuple(DEFAULT_EXCLUDE_CLASSES)): | ||||
|                 assert not hasattr(estimator, original_method_name) | ||||
|                 continue | ||||
|             class_attr = getattr(estimator, method_name, None) | ||||
|             if isinstance(class_attr, property): | ||||
|                 assert not hasattr(estimator, original_method_name) | ||||
|                 continue | ||||
|             delegator = None | ||||
|             if hasattr(estimator, method_name): | ||||
|                 delegator = get_delegator(estimator, method_name) | ||||
|             if delegator is not None: | ||||
|                 assert not hasattr(delegator, "_otel_original_fn") | ||||
|             elif hasattr(estimator, method_name): | ||||
|                 assert not hasattr(estimator, original_method_name) | ||||
| 
 | ||||
| 
 | ||||
| class TestSklearn(TestBase): | ||||
|     def test_package_instrumentation(self): | ||||
|         ski = SklearnInstrumentor() | ||||
| 
 | ||||
|         base_estimators = get_base_estimators(packages=["sklearn"]) | ||||
| 
 | ||||
|         model = pipeline() | ||||
| 
 | ||||
|         ski.instrument() | ||||
|         assert_instrumented(base_estimators) | ||||
| 
 | ||||
|         x_test = random_input() | ||||
| 
 | ||||
|         model.predict(x_test) | ||||
| 
 | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 8) | ||||
|         self.memory_exporter.clear() | ||||
| 
 | ||||
|         ski.uninstrument() | ||||
|         assert_uninstrumented(base_estimators) | ||||
| 
 | ||||
|         model = pipeline() | ||||
|         x_test = random_input() | ||||
| 
 | ||||
|         model.predict(x_test) | ||||
| 
 | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 0) | ||||
| 
 | ||||
|     def test_span_properties(self): | ||||
|         """Test that we get all of the spans we expect.""" | ||||
|         model = pipeline() | ||||
|         ski = SklearnInstrumentor() | ||||
|         ski.instrument_estimator(estimator=model) | ||||
| 
 | ||||
|         x_test = random_input() | ||||
| 
 | ||||
|         model.predict(x_test) | ||||
| 
 | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 8) | ||||
|         span = spans[0] | ||||
|         self.assertEqual(span.name, "StandardScaler.transform") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[-1].context.span_id) | ||||
|         span = spans[1] | ||||
|         self.assertEqual(span.name, "Normalizer.transform") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[-1].context.span_id) | ||||
|         span = spans[2] | ||||
|         self.assertEqual(span.name, "PCA.transform") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[4].context.span_id) | ||||
|         span = spans[3] | ||||
|         self.assertEqual(span.name, "TruncatedSVD.transform") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[4].context.span_id) | ||||
|         span = spans[4] | ||||
|         self.assertEqual(span.name, "FeatureUnion.transform") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[-1].context.span_id) | ||||
|         span = spans[5] | ||||
|         self.assertEqual(span.name, "RandomForestClassifier.predict_proba") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[6].context.span_id) | ||||
|         span = spans[6] | ||||
|         self.assertEqual(span.name, "RandomForestClassifier.predict") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
|         self.assertEqual(span.parent.span_id, spans[-1].context.span_id) | ||||
|         span = spans[7] | ||||
|         self.assertEqual(span.name, "Pipeline.predict") | ||||
|         self.assertEqual(span.kind, SpanKind.INTERNAL) | ||||
| 
 | ||||
|         self.memory_exporter.clear() | ||||
| 
 | ||||
|         # uninstrument | ||||
|         ski.uninstrument_estimator(estimator=model) | ||||
|         x_test = random_input() | ||||
|         model.predict(x_test) | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 0) | ||||
| 
 | ||||
|     def test_attrib_config(self): | ||||
|         """Test that the attribute config makes spans on the decision trees.""" | ||||
|         model = pipeline() | ||||
|         attrib_config = {RandomForestClassifier: ["estimators_"]} | ||||
|         ski = SklearnInstrumentor( | ||||
|             recurse_attribs=attrib_config, | ||||
|             exclude_classes=[],  # decision trees excluded by default | ||||
|         ) | ||||
|         ski.instrument_estimator(estimator=model) | ||||
| 
 | ||||
|         x_test = random_input() | ||||
|         model.predict(x_test) | ||||
| 
 | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 8 + model.steps[-1][-1].n_estimators) | ||||
| 
 | ||||
|         self.memory_exporter.clear() | ||||
| 
 | ||||
|         ski.uninstrument_estimator(estimator=model) | ||||
|         x_test = random_input() | ||||
|         model.predict(x_test) | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         self.assertEqual(len(spans), 0) | ||||
| 
 | ||||
|     def test_span_attributes(self): | ||||
|         model = pipeline() | ||||
|         attributes = {"model_name": "random_forest_model"} | ||||
|         ski = SklearnInstrumentor() | ||||
|         ski.instrument_estimator(estimator=model, attributes=attributes) | ||||
| 
 | ||||
|         x_test = random_input() | ||||
| 
 | ||||
|         model.predict(x_test) | ||||
| 
 | ||||
|         spans = self.memory_exporter.get_finished_spans() | ||||
|         for span in spans: | ||||
|             assert span.attributes["model_name"] == "random_forest_model" | ||||
|  | @ -66,7 +66,6 @@ dependencies = [ | |||
|     "opentelemetry-instrumentation-redis==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-remoulade==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-requests==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-sklearn==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-sqlalchemy==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-sqlite3==0.47b0.dev", | ||||
|     "opentelemetry-instrumentation-starlette==0.47b0.dev", | ||||
|  |  | |||
|  | @ -152,10 +152,6 @@ libraries = [ | |||
|         "library": "requests ~= 2.0", | ||||
|         "instrumentation": "opentelemetry-instrumentation-requests==0.47b0.dev", | ||||
|     }, | ||||
|     { | ||||
|         "library": "scikit-learn ~= 0.24.0", | ||||
|         "instrumentation": "opentelemetry-instrumentation-sklearn==0.47b0.dev", | ||||
|     }, | ||||
|     { | ||||
|         "library": "sqlalchemy", | ||||
|         "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.47b0.dev", | ||||
|  |  | |||
							
								
								
									
										16
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										16
									
								
								tox.ini
								
								
								
								
							|  | @ -273,10 +273,6 @@ envlist = | |||
|     pypy3-test-instrumentation-celery | ||||
|     lint-instrumentation-celery | ||||
| 
 | ||||
|     ; opentelemetry-instrumentation-sklearn | ||||
|     py3{8}-test-instrumentation-sklearn | ||||
|     lint-instrumentation-sklearn | ||||
| 
 | ||||
|     ; opentelemetry-instrumentation-system-metrics | ||||
|     py3{8,9,10,11,12}-test-instrumentation-system-metrics | ||||
|     pypy3-test-instrumentation-system-metrics | ||||
|  | @ -701,12 +697,6 @@ commands_pre = | |||
|   prometheus: pip install opentelemetry-test-utils@{env:CORE_REPO}\#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils | ||||
|   prometheus: pip install -r {toxinidir}/exporter/opentelemetry-exporter-prometheus-remote-write/test-requirements.txt | ||||
| 
 | ||||
|   sklearn: pip install opentelemetry-api@{env:CORE_REPO}\#egg=opentelemetry-api&subdirectory=opentelemetry-api | ||||
|   sklearn: pip install opentelemetry-semantic-conventions@{env:CORE_REPO}\#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions | ||||
|   sklearn: pip install opentelemetry-sdk@{env:CORE_REPO}\#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk | ||||
|   sklearn: pip install opentelemetry-test-utils@{env:CORE_REPO}\#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils | ||||
|   sklearn: pip install -r {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn/test-requirements.txt | ||||
| 
 | ||||
|   sqlalchemy: pip install opentelemetry-api@{env:CORE_REPO}\#egg=opentelemetry-api&subdirectory=opentelemetry-api | ||||
|   sqlalchemy: pip install opentelemetry-semantic-conventions@{env:CORE_REPO}\#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions | ||||
|   sqlalchemy: pip install opentelemetry-sdk@{env:CORE_REPO}\#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk | ||||
|  | @ -1021,12 +1011,6 @@ commands = | |||
|   lint-instrumentation-requests: flake8 --config {toxinidir}/.flake8 {toxinidir}/instrumentation/opentelemetry-instrumentation-requests | ||||
|   lint-instrumentation-requests: sh -c "cd instrumentation && pylint --rcfile ../.pylintrc opentelemetry-instrumentation-requests" | ||||
| 
 | ||||
|   test-instrumentation-sklearn: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn/tests {posargs} | ||||
|   lint-instrumentation-sklearn: black --diff --check --config {toxinidir}/pyproject.toml {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn | ||||
|   lint-instrumentation-sklearn: isort --diff --check-only --settings-path {toxinidir}/.isort.cfg {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn | ||||
|   lint-instrumentation-sklearn: flake8 --config {toxinidir}/.flake8 {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn | ||||
|   lint-instrumentation-sklearn: sh -c "cd instrumentation && pylint --rcfile ../.pylintrc opentelemetry-instrumentation-sklearn" | ||||
| 
 | ||||
|   test-instrumentation-sqlalchemy: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests {posargs} | ||||
|   lint-instrumentation-sqlalchemy: black --diff --check --config {toxinidir}/pyproject.toml {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy | ||||
|   lint-instrumentation-sqlalchemy: isort --diff --check-only --settings-path {toxinidir}/.isort.cfg {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue