From 78aa7f8093e362112329af25757211f890b3e49f Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Tue, 19 May 2020 18:42:05 +0100 Subject: [PATCH] improv: move away from monorepo due to change in direction (#36) From now on, each Lambda Powertools for a given runtime should have its own repo * chore: move all files to root * docs: fix alignment --- python/.flake8 => .flake8 | 0 .github/workflows/python.yml | 9 +- .github/workflows/python_docs.yml | 11 +- .gitignore | 6 +- python/MANIFEST.in => MANIFEST.in | 0 python/Makefile => Makefile | 0 README.md | 442 +++++++++++++++++- .../__init__.py | 0 .../helper/__init__.py | 0 .../helper/models.py | 0 .../logging/__init__.py | 0 .../logging/exceptions.py | 0 .../logging/logger.py | 0 .../metrics/__init__.py | 0 .../metrics/base.py | 0 .../metrics/exceptions.py | 0 .../metrics/metric.py | 0 .../metrics/metrics.py | 0 .../metrics/schema.json | 0 .../middleware_factory/__init__.py | 0 .../middleware_factory/exceptions.py | 0 .../middleware_factory/factory.py | 0 .../tracing/__init__.py | 0 .../tracing/extensions.py | 0 .../tracing/tracer.py | 0 python/bandit.baseline => bandit.baseline | 0 ...d_linux_wheels.sh => build_linux_wheels.sh | 0 {python/example => example}/.gitignore | 0 {python/example => example}/README.md | 0 {python/example => example}/events/event.json | 0 .../hello_world/__init__.py | 0 .../example => example}/hello_world/app.py | 0 .../hello_world/requirements.txt | 0 {python/example => example}/pytest.ini | 0 .../example => example}/requirements-dev.txt | 0 {python/example => example}/samconfig.toml | 0 {python/example => example}/template.yaml | 0 .../example => example}/tests/test_handler.py | 0 python/poetry.lock => poetry.lock | 147 +----- python/pyproject.toml => pyproject.toml | 1 - python/pytest.ini => pytest.ini | 0 python/.bumpversion.cfg | 10 - python/.gitignore | 298 ------------ python/.pre-commit-config.yaml | 40 -- python/.vscode/settings.json | 4 - python/CHANGELOG.md | 78 ---- python/LICENSE | 15 - python/README.md | 435 ----------------- .../tests => tests}/functional/__init__.py | 0 .../functional/test_aws_lambda_logging.py | 0 .../tests => tests}/functional/test_logger.py | 0 .../functional/test_metrics.py | 0 .../functional/test_middleware_factory.py | 0 .../functional/test_tracing.py | 0 {python/tests => tests}/unit/__init__.py | 0 {python/tests => tests}/unit/test_tracing.py | 0 56 files changed, 445 insertions(+), 1051 deletions(-) rename python/.flake8 => .flake8 (100%) rename python/MANIFEST.in => MANIFEST.in (100%) rename python/Makefile => Makefile (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/helper/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/helper/models.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/logging/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/logging/exceptions.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/logging/logger.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/base.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/exceptions.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/metric.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/metrics.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/metrics/schema.json (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/middleware_factory/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/middleware_factory/exceptions.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/middleware_factory/factory.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/tracing/__init__.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/tracing/extensions.py (100%) rename {python/aws_lambda_powertools => aws_lambda_powertools}/tracing/tracer.py (100%) rename python/bandit.baseline => bandit.baseline (100%) rename python/build_linux_wheels.sh => build_linux_wheels.sh (100%) rename {python/example => example}/.gitignore (100%) rename {python/example => example}/README.md (100%) rename {python/example => example}/events/event.json (100%) rename {python/example => example}/hello_world/__init__.py (100%) rename {python/example => example}/hello_world/app.py (100%) rename {python/example => example}/hello_world/requirements.txt (100%) rename {python/example => example}/pytest.ini (100%) rename {python/example => example}/requirements-dev.txt (100%) rename {python/example => example}/samconfig.toml (100%) rename {python/example => example}/template.yaml (100%) rename {python/example => example}/tests/test_handler.py (100%) rename python/poetry.lock => poetry.lock (93%) rename python/pyproject.toml => pyproject.toml (99%) rename python/pytest.ini => pytest.ini (100%) delete mode 100644 python/.bumpversion.cfg delete mode 100644 python/.gitignore delete mode 100644 python/.pre-commit-config.yaml delete mode 100644 python/.vscode/settings.json delete mode 100644 python/CHANGELOG.md delete mode 100644 python/LICENSE delete mode 100644 python/README.md rename {python/tests => tests}/functional/__init__.py (100%) rename {python/tests => tests}/functional/test_aws_lambda_logging.py (100%) rename {python/tests => tests}/functional/test_logger.py (100%) rename {python/tests => tests}/functional/test_metrics.py (100%) rename {python/tests => tests}/functional/test_middleware_factory.py (100%) rename {python/tests => tests}/functional/test_tracing.py (100%) rename {python/tests => tests}/unit/__init__.py (100%) rename {python/tests => tests}/unit/test_tracing.py (100%) diff --git a/python/.flake8 b/.flake8 similarity index 100% rename from python/.flake8 rename to .flake8 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 54d1e4b1a86..604219724f7 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,25 +1,18 @@ -name: Powertools Python +name: Build on: pull_request: branches: - develop - master - paths: - - "python/**" push: branches: - develop - master - paths: - - "python/**" jobs: build: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./python/ strategy: max-parallel: 4 matrix: diff --git a/.github/workflows/python_docs.yml b/.github/workflows/python_docs.yml index b57bd29a475..57ef3eb4013 100644 --- a/.github/workflows/python_docs.yml +++ b/.github/workflows/python_docs.yml @@ -1,23 +1,16 @@ -name: Powertools Python Docs +name: Docs on: pull_request: branches: - master - paths: - - "python/**" push: branches: - master - paths: - - "python/**" jobs: docs: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./python/ steps: - uses: actions/checkout@v1 - name: Set up Python @@ -32,4 +25,4 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: python/docs/aws_lambda_powertools/ + publish_dir: docs/aws_lambda_powertools/ diff --git a/.gitignore b/.gitignore index 68c23bc3257..d12573e83a0 100644 --- a/.gitignore +++ b/.gitignore @@ -291,5 +291,7 @@ $RECYCLE.BIN/ # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode # Misc -test_report -/.idea/* +test_report +wheelhouse +docs +/.idea/* diff --git a/python/MANIFEST.in b/MANIFEST.in similarity index 100% rename from python/MANIFEST.in rename to MANIFEST.in diff --git a/python/Makefile b/Makefile similarity index 100% rename from python/Makefile rename to Makefile diff --git a/README.md b/README.md index 9f53b6e1178..49e6b3fffd4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,447 @@ # Lambda Powertools -![Python Build](https://github.com/awslabs/aws-lambda-powertools/workflows/Powertools%20Python/badge.svg?branch=master) +![Build](https://github.com/awslabs/aws-lambda-powertools/workflows/Powertools%20Python/badge.svg?branch=master) +![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8&color=blue?style=flat-square&logo=python) ![PyPI version](https://badge.fury.io/py/aws-lambda-powertools.svg) ![PyPi monthly downloads](https://img.shields.io/pypi/dm/aws-lambda-powertools) A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. +* **Status**: Release candidate +* **How long until GA?**: [Current progress](https://github.com/awslabs/aws-lambda-powertools/projects/1) + +## Features + +* **[Tracing](###Tracing)** - Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions +* **[Logging](###Logging)** - Structured logging made easier, and decorator to enrich structured logging with key Lambda context details +* **[Metrics](###Metrics)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) +* **[Bring your own middleware](###Bring-your-own-middleware)** - Decorator factory to create your own middleware to run logic before, and after each Lambda invocation + +## Usage + +See **[example](./example/README.md)** of all features, testing, and a SAM template with all Powertools env vars. All features also provide full docs, and code completion for VSCode and PyCharm. + +### Installation + +With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip install aws-lambda-powertools`` + +### Environment variables + +**Environment variables** used across suite of utilities + +Environment variable | Description | Default | Utility +------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- +POWERTOOLS_SERVICE_NAME | Sets service name used for tracing namespace, metrics dimensions and structured logging | "service_undefined" | all +POWERTOOLS_TRACE_DISABLED | Disables tracing | "false" | [Tracing](###Tracing) +POWERTOOLS_TRACE_MIDDLEWARES | Creates sub-segment for each middleware created by lambda_handler_decorator | "false" | [middleware_factory](###Bring-your-own-middleware) +POWERTOOLS_LOGGER_LOG_EVENT | Logs incoming event | "false" | [Logging](###Logging) +POWERTOOLS_LOGGER_SAMPLE_RATE | Debug log sampling | 0 | [Logging](###Logging) +POWERTOOLS_METRICS_NAMESPACE | Metrics namespace | None | [Metrics](###Metrics) +LOG_LEVEL | Sets logging level | "INFO" | [Logging](###Logging) + +### Tracing + +**Key features** + +* Capture cold start as annotation, and response and exceptions as metadata +* Run functions locally with SAM CLI without code change to disable tracing +* Explicitly disable tracing via env var `POWERTOOLS_TRACE_DISABLED="true"` +* Support tracing async methods + +#### Tracing Lambda handler and a function + +```python +from aws_lambda_powertools.tracing import Tracer +tracer = Tracer() +# tracer = Tracer(service="payment") # can also be explicitly defined + +@tracer.capture_method +def collect_payment(charge_id): + ret = requests.post(PAYMENT_ENDPOINT) # logic + tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") # custom annotation + return ret + +@tracer.capture_lambda_handler +def handler(event, context) + charge_id = event.get('charge_id') + payment = collect_payment(charge_id) + ... +``` + +#### Tracing asynchronous functions + +```python +import asyncio + +from aws_lambda_powertools.tracing import Tracer +tracer = Tracer() +# tracer = Tracer(service="payment") # can also be explicitly defined + +@tracer.capture_method +async def collect_payment(charge_id): + ... + +@tracer.capture_lambda_handler +def handler(event, context) + charge_id = event.get('charge_id') + payment = asyncio.run(collect_payment(charge_id)) # python 3.7+ + ... +``` + +#### Tracing concurrent asynchronous with gather + +:warning: This will no longer be necessary after [this X-Ray recorder issue is resolved](https://github.com/aws/aws-xray-sdk-python/issues/164) as it's an edge case. :warning: + +To safely workaround this issue, use `@tracer.capture_method` on functions not being run with `async.gather`, and instead use `in_subsegment_async` context manager escape hatch to have the same tracing effect. + + +```python +import asyncio + +from aws_lambda_powertools.tracing import Tracer +tracer = Tracer() +# tracer = Tracer(service="payment") # can also be explicitly defined + +async def another_async_task(): + async with tracer.provider.in_subsegment_async("## another_async_task"): + ... + +async def another_async_task_2(): + async with tracer.provider.in_subsegment_async("## another_async_task_2"): + ... + +@tracer.capture_method +async def collect_payment(charge_id): + asyncio.gather(another_async_task(), another_async_task_2()) + ... + +@tracer.capture_lambda_handler +def handler(event, context) + charge_id = event.get('charge_id') + payment = asyncio.run(collect_payment(charge_id)) # python 3.7+ + ... +``` + +#### Using escape hatch mechanisms + +You can use `tracer.provider` attribute to access all methods provided by `xray_recorder`. This is useful when you need a feature available in X-Ray that is not available in the Tracer middleware, for example [thread-safe](https://github.com/aws/aws-xray-sdk-python/#user-content-trace-threadpoolexecutor), or [context managers](https://github.com/aws/aws-xray-sdk-python/#user-content-start-a-custom-segmentsubsegment). + +**Example using aiohttp with an async context manager** + +> NOTE: It expects you have `aiohttp` as a dependency. `aiohttp_trace_config` uses lazy import to create a trace_config object following `aiohttp` protocol. + +```python +import asyncio +import aiohttp + +from aws_lambda_powertools.tracing import Tracer, aiohttp_trace_config +tracer = Tracer() + +# aiohttp_trace_config is x-ray extension for aiohttp trace config known as aws_xray_trace_config + +async def aiohttp_task(): + # Async context manager as opposed to `@tracer.capture_method` + async with tracer.provider.in_subsegment_async("## aiohttp escape hatch"): + async with aiohttp.ClientSession(trace_configs=[aiohttp_trace_config()]) as session: + async with session.get("https://httpbin.org/json") as resp: + resp = await resp.json() + return resp + +@tracer.capture_method +async def async_tasks(): + ret = await aiohttp_task() + ... + + return { + "task": "done", + **ret + } + +@tracer.capture_lambda_handler +def handler(event, context) + ret = asyncio.run(async_tasks()) # python 3.7+ + ... +``` + +#### Using a pre-configured tracer anywhere + +```python +# handler.py +from aws_lambda_powertools.tracing import Tracer +tracer = Tracer(service="payment") + +@tracer.capture_lambda_handler +def handler(event, context) + charge_id = event.get('charge_id') + payment = collect_payment(charge_id) + ... + +# another_file.py +from aws_lambda_powertools.tracing import Tracer +tracer = Tracer(auto_patch=False) # new instance using existing configuration with auto patching overriden +``` + +### Logging + +**Key features** + +* Capture key fields from Lambda context, cold start and structures logging output as JSON +* Log Lambda event when instructed (disabled by default) + - Enable via `POWERTOOLS_LOGGER_LOG_EVENT="true"` or explicitly via decorator param +* Log sampling enables DEBUG log level for a percentage of requests (disabled by default) + - Enable via `POWERTOOLS_LOGGER_SAMPLE_RATE=0.1`, ranges from 0 to 1, where 0.1 is 10% and 1 is 100% +* Append additional keys to structured log at any point in time + +#### Structuring logs with Lambda context info + +```python +from aws_lambda_powertools.logging import Logger + +logger = Logger() +# Logger(service="payment", level="INFO") # also accepts explicit service name, log level + +@logger.inject_lambda_context +def handler(event, context) + logger.info("Collecting payment") + ... + # You can log entire objects too + logger.info({ + "operation": "collect_payment", + "charge_id": event['charge_id'] + }) + ... +``` + +
+Exerpt output in CloudWatch Logs + +```json +{ + "timestamp":"2019-08-22 18:17:33,774", + "level":"INFO", + "location":"collect.handler:1", + "service":"payment", + "lambda_function_name":"test", + "lambda_function_memory_size":"128", + "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", + "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", + "cold_start": "true", + "sampling_rate": 0.1, + "message": "Collecting payment" +} + +{ + "timestamp":"2019-08-22 18:17:33,774", + "level":"INFO", + "location":"collect.handler:15", + "service":"payment", + "lambda_function_name":"test", + "lambda_function_memory_size":"128", + "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", + "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", + "cold_start": "true", + "sampling_rate": 0.1, + "message":{ + "operation":"collect_payment", + "charge_id": "ch_AZFlk2345C0" + } +} +``` +
+ +#### Appending additional keys to current logger + +```python +from aws_lambda_powertools.logging import Logger + +logger = Logger() + +@logger.inject_lambda_context +def handler(event, context) + if "order_id" in event: + logger.structure_logs(append=True, order_id=event["order_id"]) + logger.info("Collecting payment") + ... +``` + +
+Exerpt output in CloudWatch Logs + +```json +{ + "timestamp":"2019-08-22 18:17:33,774", + "level":"INFO", + "location":"collect.handler:1", + "service":"payment", + "lambda_function_name":"test", + "lambda_function_memory_size":"128", + "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", + "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", + "cold_start": "true", + "sampling_rate": 0.1, + "order_id": "order_id_value", + "message": "Collecting payment" +} +``` +
+ +### Metrics + +**Key features** + +* Aggregate up to 100 metrics using a single CloudWatch Embedded Metric Format (EMF) object (large JSON blob) +* Context manager to create an one off metric with a different dimension than metrics already aggregated +* Validate against common metric definitions mistakes (metric unit, values, max dimensions, max metrics, etc) +* Metrics are created asynchronously by CloudWatch service, no custom stacks needed +* EMF Metrics validation before publishing + - At least of one Metric and Dimension + - Maximum of 9 dimensions + - Only one Namespace + - [Any Metric unit supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) + +#### Creating multiple metrics + +If using multiple middlewares, use `log_metrics` as the last decorator, or else it will fail with `SchemaValidationError` if no metrics are recorded. + +```python +from aws_lambda_powertools.metrics import Metrics, MetricUnit + +metrics = Metrics() +metrics.add_namespace(name="ServerlessAirline") +metrics.add_metric(name="ColdStart", unit="Count", value=1) +metrics.add_dimension(name="service", value="booking") + +@metrics.log_metrics +@tracer.capture_lambda_handler +def lambda_handler(evt, ctx): + metrics.add_metric(name="BookingConfirmation", unit="Count", value=1) + some_code() + return True + +def some_code(): + metrics.add_metric(name="some_other_metric", unit=MetricUnit.Seconds, value=1) + ... +``` + +CloudWatch EMF uses the same dimensions across all metrics. If you have metrics that should have different dimensions, use `single_metric` to create a single metric with any dimension you want. Generally, this would be an edge case since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing/) + +> unique metric = (metric_name + dimension_name + dimension_value) + +```python +from aws_lambda_powertools.metrics import MetricUnit, single_metric + +with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1) as metric: + metric.add_dimension(name="function_context", value="$LATEST") +``` + +> **NOTE**: When using Metrics() in multiple places in your code, make sure to use `POWERTOOLS_METRICS_NAMESPACE` env var, or setting namespace param. + +### Bring your own middleware + +**Key features** + +* Utility to easily create your own middleware +* Run logic before, after, and handle exceptions +* Receive lambda handler, event, context +* Optionally create sub-segment for each custom middleware + +This feature allows you to create your own middleware as a decorator with ease by following a simple signature. + +* Accept 3 mandatory args - `handler, event, context` +* Always return the handler with event/context or response if executed + - Supports nested middleware/decorators use case + +#### Middleware with no params + +```python +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator + +@lambda_handler_decorator +def middleware_name(handler, event, context): + return handler(event, context) + +@lambda_handler_decorator +def middleware_before_after(handler, event, context): + logic_before_handler_execution() + response = handler(event, context) + logic_after_handler_execution() + return response + + +# middleware_name will wrap Lambda handler +# and simply return the handler as we're not pre/post-processing anything +# then middleware_before_after will wrap middleware_name +# run some code before/after calling the handler returned by middleware_name +# This way, lambda_handler is only actually called once (top-down) +@middleware_before_after # This will run last +@middleware_name # This will run first +def lambda_handler(event, context): + return True +``` + +#### Middleware with params + +```python +@lambda_handler_decorator +def obfuscate_sensitive_data(handler, event, context, fields=None): + # Obfuscate email before calling Lambda handler + if fields: + for field in fields: + field = event.get(field, "") + event[field] = obfuscate_pii(field) + + return handler(event, context) + +@obfuscate_sensitive_data(fields=["email"]) +def lambda_handler(event, context): + return True +``` + +#### Tracing middleware execution + +This makes use of an existing Tracer instance that you may have initialized anywhere in your code. If no Tracer instance is found, it'll initialize one using default options. + +```python +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator + +@lambda_handler_decorator(trace_execution=True) +def middleware_name(handler, event, context): + return handler(event, context) + +@middleware_name +def lambda_handler(event, context): + return True +``` + +Optionally, you can enrich the final trace with additional annotations and metadata by retrieving a copy of the Tracer used. + +```python +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.tracing import Tracer + +@lambda_handler_decorator(trace_execution=True) +def middleware_name(handler, event, context): + tracer = Tracer() # Takes a copy of an existing tracer instance + tracer.add_anotation... + tracer.metadata... + return handler(event, context) + +@middleware_name +def lambda_handler(event, context): + return True +``` + +### Debug mode + +By default, all log statements from AWS Lambda Powertools package are suppressed. If you'd like to enable them, use `set_package_logger` utility: + +```python +import aws_lambda_powertools +aws_lambda_powertools.logging.logger.set_package_logger() +... +``` + ## Tenets -* **AWS Lambda only** – We optimise for AWS Lambda function environments only. Utilities might work with web frameworks and non-Lambda environments, though they are not officially supported. +* **AWS Lambda only** – We optimise for AWS Lambda function environments and supported runtimes only. Utilities might work with web frameworks and non-Lambda environments, though they are not officially supported. * **Eases the adoption of best practices** – The main priority of the utilities is to facilitate best practices adoption, as defined in the AWS Well-Architected Serverless Lens; all other functionality is optional. * **Keep it lean** – Additional dependencies are carefully considered for security and ease of maintenance, and prevent negatively impacting startup time. * **We strive for backwards compatibility** – New features and changes should keep backwards compatibility. If a breaking change cannot be avoided, the deprecation and migration process should be clearly defined. @@ -15,9 +450,6 @@ A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, _`*` Core utilities are Tracer, Logger and Metrics. Optional utilities may vary across languages._ -## Powertools available - -* [Python](./python/README.md) ## Credits diff --git a/python/aws_lambda_powertools/__init__.py b/aws_lambda_powertools/__init__.py similarity index 100% rename from python/aws_lambda_powertools/__init__.py rename to aws_lambda_powertools/__init__.py diff --git a/python/aws_lambda_powertools/helper/__init__.py b/aws_lambda_powertools/helper/__init__.py similarity index 100% rename from python/aws_lambda_powertools/helper/__init__.py rename to aws_lambda_powertools/helper/__init__.py diff --git a/python/aws_lambda_powertools/helper/models.py b/aws_lambda_powertools/helper/models.py similarity index 100% rename from python/aws_lambda_powertools/helper/models.py rename to aws_lambda_powertools/helper/models.py diff --git a/python/aws_lambda_powertools/logging/__init__.py b/aws_lambda_powertools/logging/__init__.py similarity index 100% rename from python/aws_lambda_powertools/logging/__init__.py rename to aws_lambda_powertools/logging/__init__.py diff --git a/python/aws_lambda_powertools/logging/exceptions.py b/aws_lambda_powertools/logging/exceptions.py similarity index 100% rename from python/aws_lambda_powertools/logging/exceptions.py rename to aws_lambda_powertools/logging/exceptions.py diff --git a/python/aws_lambda_powertools/logging/logger.py b/aws_lambda_powertools/logging/logger.py similarity index 100% rename from python/aws_lambda_powertools/logging/logger.py rename to aws_lambda_powertools/logging/logger.py diff --git a/python/aws_lambda_powertools/metrics/__init__.py b/aws_lambda_powertools/metrics/__init__.py similarity index 100% rename from python/aws_lambda_powertools/metrics/__init__.py rename to aws_lambda_powertools/metrics/__init__.py diff --git a/python/aws_lambda_powertools/metrics/base.py b/aws_lambda_powertools/metrics/base.py similarity index 100% rename from python/aws_lambda_powertools/metrics/base.py rename to aws_lambda_powertools/metrics/base.py diff --git a/python/aws_lambda_powertools/metrics/exceptions.py b/aws_lambda_powertools/metrics/exceptions.py similarity index 100% rename from python/aws_lambda_powertools/metrics/exceptions.py rename to aws_lambda_powertools/metrics/exceptions.py diff --git a/python/aws_lambda_powertools/metrics/metric.py b/aws_lambda_powertools/metrics/metric.py similarity index 100% rename from python/aws_lambda_powertools/metrics/metric.py rename to aws_lambda_powertools/metrics/metric.py diff --git a/python/aws_lambda_powertools/metrics/metrics.py b/aws_lambda_powertools/metrics/metrics.py similarity index 100% rename from python/aws_lambda_powertools/metrics/metrics.py rename to aws_lambda_powertools/metrics/metrics.py diff --git a/python/aws_lambda_powertools/metrics/schema.json b/aws_lambda_powertools/metrics/schema.json similarity index 100% rename from python/aws_lambda_powertools/metrics/schema.json rename to aws_lambda_powertools/metrics/schema.json diff --git a/python/aws_lambda_powertools/middleware_factory/__init__.py b/aws_lambda_powertools/middleware_factory/__init__.py similarity index 100% rename from python/aws_lambda_powertools/middleware_factory/__init__.py rename to aws_lambda_powertools/middleware_factory/__init__.py diff --git a/python/aws_lambda_powertools/middleware_factory/exceptions.py b/aws_lambda_powertools/middleware_factory/exceptions.py similarity index 100% rename from python/aws_lambda_powertools/middleware_factory/exceptions.py rename to aws_lambda_powertools/middleware_factory/exceptions.py diff --git a/python/aws_lambda_powertools/middleware_factory/factory.py b/aws_lambda_powertools/middleware_factory/factory.py similarity index 100% rename from python/aws_lambda_powertools/middleware_factory/factory.py rename to aws_lambda_powertools/middleware_factory/factory.py diff --git a/python/aws_lambda_powertools/tracing/__init__.py b/aws_lambda_powertools/tracing/__init__.py similarity index 100% rename from python/aws_lambda_powertools/tracing/__init__.py rename to aws_lambda_powertools/tracing/__init__.py diff --git a/python/aws_lambda_powertools/tracing/extensions.py b/aws_lambda_powertools/tracing/extensions.py similarity index 100% rename from python/aws_lambda_powertools/tracing/extensions.py rename to aws_lambda_powertools/tracing/extensions.py diff --git a/python/aws_lambda_powertools/tracing/tracer.py b/aws_lambda_powertools/tracing/tracer.py similarity index 100% rename from python/aws_lambda_powertools/tracing/tracer.py rename to aws_lambda_powertools/tracing/tracer.py diff --git a/python/bandit.baseline b/bandit.baseline similarity index 100% rename from python/bandit.baseline rename to bandit.baseline diff --git a/python/build_linux_wheels.sh b/build_linux_wheels.sh similarity index 100% rename from python/build_linux_wheels.sh rename to build_linux_wheels.sh diff --git a/python/example/.gitignore b/example/.gitignore similarity index 100% rename from python/example/.gitignore rename to example/.gitignore diff --git a/python/example/README.md b/example/README.md similarity index 100% rename from python/example/README.md rename to example/README.md diff --git a/python/example/events/event.json b/example/events/event.json similarity index 100% rename from python/example/events/event.json rename to example/events/event.json diff --git a/python/example/hello_world/__init__.py b/example/hello_world/__init__.py similarity index 100% rename from python/example/hello_world/__init__.py rename to example/hello_world/__init__.py diff --git a/python/example/hello_world/app.py b/example/hello_world/app.py similarity index 100% rename from python/example/hello_world/app.py rename to example/hello_world/app.py diff --git a/python/example/hello_world/requirements.txt b/example/hello_world/requirements.txt similarity index 100% rename from python/example/hello_world/requirements.txt rename to example/hello_world/requirements.txt diff --git a/python/example/pytest.ini b/example/pytest.ini similarity index 100% rename from python/example/pytest.ini rename to example/pytest.ini diff --git a/python/example/requirements-dev.txt b/example/requirements-dev.txt similarity index 100% rename from python/example/requirements-dev.txt rename to example/requirements-dev.txt diff --git a/python/example/samconfig.toml b/example/samconfig.toml similarity index 100% rename from python/example/samconfig.toml rename to example/samconfig.toml diff --git a/python/example/template.yaml b/example/template.yaml similarity index 100% rename from python/example/template.yaml rename to example/template.yaml diff --git a/python/example/tests/test_handler.py b/example/tests/test_handler.py similarity index 100% rename from python/example/tests/test_handler.py rename to example/tests/test_handler.py diff --git a/python/poetry.lock b/poetry.lock similarity index 93% rename from python/poetry.lock rename to poetry.lock index 07143b50fa7..b5ec9af256f 100644 --- a/python/poetry.lock +++ b/poetry.lock @@ -199,14 +199,6 @@ optional = false python-versions = "*" version = "2020.4.5.1" -[[package]] -category = "dev" -description = "Validate configuration and produce human readable error messages." -name = "cfgv" -optional = false -python-versions = ">=3.6" -version = "3.0.0" - [[package]] category = "dev" description = "Universal encoding detector for Python 2 and 3" @@ -247,14 +239,6 @@ version = "*" [package.extras] toml = ["toml"] -[[package]] -category = "dev" -description = "Distribution utilities" -name = "distlib" -optional = false -python-versions = "*" -version = "0.3.0" - [[package]] category = "main" description = "Docutils -- Python Documentation Utilities" @@ -282,14 +266,6 @@ version = "2.14.4" [package.extras] devel = ["colorama", "jsonschema", "json-spec", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] -[[package]] -category = "dev" -description = "A platform independent file lock." -name = "filelock" -optional = false -python-versions = "*" -version = "3.0.12" - [[package]] category = "dev" description = "the modular source code checker: pep8 pyflakes and co" @@ -464,17 +440,6 @@ version = "3.1.2" [package.dependencies] gitdb = ">=4.0.1,<5" -[[package]] -category = "dev" -description = "File identification library for Python" -name = "identify" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.15" - -[package.extras] -license = ["editdistance"] - [[package]] category = "dev" description = "Internationalized Domain Names in Applications (IDNA)" @@ -510,27 +475,6 @@ zipp = ">=0.5" docs = ["sphinx", "rst.linker"] testing = ["packaging", "importlib-resources"] -[[package]] -category = "dev" -description = "Read resources from Python packages" -marker = "python_version < \"3.7\"" -name = "importlib-resources" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.5.0" - -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - -[package.dependencies.zipp] -python = "<3.8" -version = ">=0.4" - -[package.extras] -docs = ["sphinx", "rst.linker", "jaraco.packaging"] - [[package]] category = "dev" description = "A Python utility / library to sort Python imports." @@ -646,14 +590,6 @@ optional = false python-versions = ">=3.5" version = "4.7.5" -[[package]] -category = "dev" -description = "Node.js virtual environment builder" -name = "nodeenv" -optional = false -python-versions = "*" -version = "1.3.5" - [[package]] category = "dev" description = "Core utilities for Python packages" @@ -710,30 +646,6 @@ version = ">=0.12" [package.extras] dev = ["pre-commit", "tox"] -[[package]] -category = "dev" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -name = "pre-commit" -optional = false -python-versions = ">=3.6" -version = "2.1.1" - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=15.2" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - -[package.dependencies.importlib-resources] -python = "<3.7" -version = "*" - [[package]] category = "dev" description = "library with cross-python path, ini-parsing, io, code, log facilities" @@ -983,32 +895,6 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] -[[package]] -category = "dev" -description = "Virtual Python Environment builder" -name = "virtualenv" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.20" - -[package.dependencies] -appdirs = ">=1.4.3,<2" -distlib = ">=0.3.0,<1" -filelock = ">=3.0.0,<4" -six = ">=1.9.0,<2" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12,<2" - -[package.dependencies.importlib-resources] -python = "<3.7" -version = ">=1.0,<2" - -[package.extras] -docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] -testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] - [[package]] category = "dev" description = "Measures number of Terminal column cells of wide-character codes" @@ -1053,7 +939,6 @@ multidict = ">=4.0" [[package]] category = "main" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" @@ -1064,7 +949,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "525f4150dc764e0fa82b790ada43514e328c26e0e3e90e26103b038ce0bd896e" +content-hash = "11587b6860e090c953a7e70372c266723b32848accdde9e3546e7ed9d5208a9f" python-versions = "^3.6" [metadata.files] @@ -1134,10 +1019,6 @@ certifi = [ {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, ] -cfgv = [ - {file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"}, - {file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"}, -] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, @@ -1183,9 +1064,6 @@ coverage = [ {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, ] -distlib = [ - {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, -] docutils = [ {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"}, {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"}, @@ -1198,10 +1076,6 @@ fastjsonschema = [ {file = "fastjsonschema-2.14.4-py3-none-any.whl", hash = "sha256:02a39b518077cc73c1a537f27776527dc6c1e5012d530eb8ac0d1062efbabff7"}, {file = "fastjsonschema-2.14.4.tar.gz", hash = "sha256:7292cde54f1c30172f78557509ad4cb152f374087fc844bd113a83e2ac494dd6"}, ] -filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, -] flake8 = [ {file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"}, {file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"}, @@ -1254,10 +1128,6 @@ gitpython = [ {file = "GitPython-3.1.2-py3-none-any.whl", hash = "sha256:da3b2cf819974789da34f95ac218ef99f515a928685db141327c09b73dd69c09"}, {file = "GitPython-3.1.2.tar.gz", hash = "sha256:864a47472548f3ba716ca202e034c1900f197c0fb3a08f641c20c3cafd15ed94"}, ] -identify = [ - {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, - {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, -] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, @@ -1269,10 +1139,6 @@ importlib-metadata = [ {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, ] -importlib-resources = [ - {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, - {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, -] isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, @@ -1359,9 +1225,6 @@ multidict = [ {file = "multidict-4.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969"}, {file = "multidict-4.7.5.tar.gz", hash = "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e"}, ] -nodeenv = [ - {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, -] packaging = [ {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, @@ -1381,10 +1244,6 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -pre-commit = [ - {file = "pre_commit-2.1.1-py2.py3-none-any.whl", hash = "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6"}, - {file = "pre_commit-2.1.1.tar.gz", hash = "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb"}, -] py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, @@ -1521,10 +1380,6 @@ urllib3 = [ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, ] -virtualenv = [ - {file = "virtualenv-20.0.20-py2.py3-none-any.whl", hash = "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74"}, - {file = "virtualenv-20.0.20.tar.gz", hash = "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"}, -] wcwidth = [ {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, diff --git a/python/pyproject.toml b/pyproject.toml similarity index 99% rename from python/pyproject.toml rename to pyproject.toml index 51f92db882d..337f67d5e8e 100644 --- a/python/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ flake8-fixme = "^1.1.1" flake8-isort = "^2.8.0" flake8-variables-names = "^0.0.3" isort = "^4.3.21" -pre-commit = "^2.1.0" pytest-cov = "^2.8.1" pytest-mock = "^2.0.0" pdoc3 = "^0.7.5" diff --git a/python/pytest.ini b/pytest.ini similarity index 100% rename from python/pytest.ini rename to pytest.ini diff --git a/python/.bumpversion.cfg b/python/.bumpversion.cfg deleted file mode 100644 index 61bb3abd6ab..00000000000 --- a/python/.bumpversion.cfg +++ /dev/null @@ -1,10 +0,0 @@ -[bumpversion] -current_version = 0.5.0 -commit = False -tag = True - -[bumpversion:file:pyproject.toml] - -[bumpversion:file:aws_lambda_powertools/__init__.py] -search = __version__ = '{current_version}' -replace = __version__ = '{new_version}' diff --git a/python/.gitignore b/python/.gitignore deleted file mode 100644 index fcab8fae4e0..00000000000 --- a/python/.gitignore +++ /dev/null @@ -1,298 +0,0 @@ - -# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode -# Edit at https://www.gitignore.io/?templates=osx,linux,python,windows,pycharm,visualstudiocode - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### OSX ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### PyCharm ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### PyCharm Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -.idea/sonarlint - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode - -# Misc - -test_report -wheelhouse -docs \ No newline at end of file diff --git a/python/.pre-commit-config.yaml b/python/.pre-commit-config.yaml deleted file mode 100644 index 1b3d03b5236..00000000000 --- a/python/.pre-commit-config.yaml +++ /dev/null @@ -1,40 +0,0 @@ -repos: -- repo: local - hooks: - - id: isort - name: isort - stages: [commit] - language: system - entry: pipenv run isort - types: [python] - - - id: black - name: black - stages: [commit] - language: system - entry: pipenv run black - types: [python] - - - id: flake8 - name: flake8 - stages: [commit] - language: system - entry: pipenv run flake8 - types: [python] - exclude: setup.py - - - id: pytest - name: pytest - stages: [commit] - language: system - entry: pipenv run pytest - types: [python] - - - id: pytest-cov - name: pytest - stages: [push] - language: system - entry: pipenv run pytest --cov --cov-fail-under=70 - types: [python] - pass_filenames: false - \ No newline at end of file diff --git a/python/.vscode/settings.json b/python/.vscode/settings.json deleted file mode 100644 index b8ab046880e..00000000000 --- a/python/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.pythonPath": "/Users/lessa/.pyenv/aws-lambda-powertools-_w3KY4aG-py3.7/bin/python", - "python.formatting.provider": "black" -} \ No newline at end of file diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md deleted file mode 100644 index c3d8c76207e..00000000000 --- a/python/CHANGELOG.md +++ /dev/null @@ -1,78 +0,0 @@ -# HISTORY - -## May 16th - -**0.9.3** - -* **Tracer**: Bugfix - Runtime Error for nested sync due to incorrect loop usage - -## May 14th - -**0.9.2** - -* **Tracer**: Bugfix - aiohttp lazy import so it's not a hard dependency - -## May 12th - -**0.9.0** - -* **Tracer**: Support for async functions in `Tracer` via `capture_method` decorator -* **Tracer**: Support for `aiohttp` via `aiohttp_trace_config` trace config -* **Tracer**: Support for patching specific modules via `patch_modules` param -* **Tracer**: Document escape hatch mechanisms via `tracer.provider` - -## May 1st - -**0.8.1** - -* **Metrics**: Fix metric unit casting logic if one passes plain string (value or key) -* **Metrics: **Fix `MetricUnit` enum values for - - `BytesPerSecond` - - `KilobytesPerSecond` - - `MegabytesPerSecond` - - `GigabytesPerSecond` - - `TerabytesPerSecond` - - `BitsPerSecond` - - `KilobitsPerSecond` - - `MegabitsPerSecond` - - `GigabitsPerSecond` - - `TerabitsPerSecond` - - `CountPerSecond` - -## April 24th - -**0.8.0** - -* **Logger**: Introduces `Logger` class for stuctured logging as a replacement for `logger_setup` -* **Logger**: Introduces `Logger.inject_lambda_context` decorator as a replacement for `logger_inject_lambda_context` -* **Logger**: Raise `DeprecationWarning` exception for both `logger_setup`, `logger_inject_lambda_context` - -## April 20th, 2020 - -**0.7.0** - -* **Middleware factory**: Introduces Middleware Factory to build your own middleware via `lambda_handler_decorator` -* **Metrics**: Fixes metrics dimensions not being included correctly in EMF - -## April 9th, 2020 - -**0.6.3** - -* **Logger**: Fix `log_metrics` decorator logic not calling the decorated function, and exception handling - -## April 8th, 2020 - -**0.6.1** - -* **Metrics**: Introduces Metrics middleware to utilise CloudWatch Embedded Metric Format -* **Metrics**: Adds deprecation warning for `log_metrics` - -## February 20th, 2020 - -**0.5.0** - -* **Logger**: Introduces log sampling for debug - Thanks to [Danilo's contribution](https://github.com/awslabs/aws-lambda-powertools/pull/7) - -## November 15th, 2019 - -* Public beta release diff --git a/python/LICENSE b/python/LICENSE deleted file mode 100644 index fcc7fa6828c..00000000000 --- a/python/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/python/README.md b/python/README.md deleted file mode 100644 index 42907724dba..00000000000 --- a/python/README.md +++ /dev/null @@ -1,435 +0,0 @@ -# Lambda Powertools - -![PackageStatus](https://img.shields.io/static/v1?label=status&message=release-candidate&color=blueviolet?style=flat-square) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8&color=blue?style=flat-square&logo=python) ![PyPI version](https://badge.fury.io/py/aws-lambda-powertools.svg) ![PyPi monthly downloads](https://img.shields.io/pypi/dm/aws-lambda-powertools) ![Build](https://github.com/awslabs/aws-lambda-powertools/workflows/Powertools%20Python/badge.svg?branch=master) - -A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging, and creating custom metrics asynchronously easier - Compatible with Python >=3.6. - -* **Status**: Release candidate -* **How long until GA?**: [Current progress](https://github.com/awslabs/aws-lambda-powertools/projects/1) - -## Features - -**[Tracing](###Tracing)** - -* Capture cold start as annotation, and response and exceptions as metadata -* Run functions locally with SAM CLI without code change to disable tracing -* Explicitly disable tracing via env var `POWERTOOLS_TRACE_DISABLED="true"` -* Support tracing async methods - -**[Logging](###Logging)** - -* Capture key fields from Lambda context, cold start and structures logging output as JSON -* Log Lambda event when instructed (disabled by default) - - Enable via `POWERTOOLS_LOGGER_LOG_EVENT="true"` or explicitly via decorator param -* Log sampling enables DEBUG log level for a percentage of requests (disabled by default) - - Enable via `POWERTOOLS_LOGGER_SAMPLE_RATE=0.1`, ranges from 0 to 1, where 0.1 is 10% and 1 is 100% -* Append additional keys to structured log at any point in time - -**[Metrics](###Metrics)** - -* Aggregate up to 100 metrics using a single CloudWatch Embedded Metric Format object (large JSON blob) -* Context manager to create an one off metric with a different dimension than metrics already aggregated -* Validate against common metric definitions mistakes (metric unit, values, max dimensions, max metrics, etc) - -**[Bring your own middleware](###Bring-your-own-middleware)** - -* Utility to easily create your own middleware -* Run logic before, after, and handle exceptions -* Receive lambda handler, event, context -* Optionally create sub-segment for each custom middleware - -**Environment variables** used across suite of utilities - -Environment variable | Description | Default | Utility -------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- -POWERTOOLS_SERVICE_NAME | Sets service name used for tracing namespace, metrics dimensions and structured logging | "service_undefined" | all -POWERTOOLS_TRACE_DISABLED | Disables tracing | "false" | [Tracing](###Tracing) -POWERTOOLS_TRACE_MIDDLEWARES | Creates sub-segment for each middleware created by lambda_handler_decorator | "false" | [middleware_factory](###Bring-your-own-middleware) -POWERTOOLS_LOGGER_LOG_EVENT | Logs incoming event | "false" | [Logging](###Logging) -POWERTOOLS_LOGGER_SAMPLE_RATE | Debug log sampling | 0 | [Logging](###Logging) -POWERTOOLS_METRICS_NAMESPACE | Metrics namespace | None | [Metrics](###Metrics) -LOG_LEVEL | Sets logging level | "INFO" | [Logging](###Logging) - -## Usage - -See **[example](./example/README.md)** of all features, testing, and a SAM template with all Powertools env vars. All features also provide full docs, and code completion for VSCode and PyCharm. - -### Installation - -With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip install aws-lambda-powertools`` - -### Tracing - -#### Tracing Lambda handler and a function - -```python -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer() -# tracer = Tracer(service="payment") # can also be explicitly defined - -@tracer.capture_method -def collect_payment(charge_id): - ret = requests.post(PAYMENT_ENDPOINT) # logic - tracer.put_annotation("PAYMENT_STATUS", "SUCCESS") # custom annotation - return ret - -@tracer.capture_lambda_handler -def handler(event, context) - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) - ... -``` - -#### Tracing asynchronous functions - -```python -import asyncio - -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer() -# tracer = Tracer(service="payment") # can also be explicitly defined - -@tracer.capture_method -async def collect_payment(charge_id): - ... - -@tracer.capture_lambda_handler -def handler(event, context) - charge_id = event.get('charge_id') - payment = asyncio.run(collect_payment(charge_id)) # python 3.7+ - ... -``` - -#### Tracing concurrent asynchronous with gather - -:warning: This will no longer be necessary after [this X-Ray recorder issue is resolved](https://github.com/aws/aws-xray-sdk-python/issues/164) as it's an edge case. :warning: - -To safely workaround this issue, use `@tracer.capture_method` on functions not being run with `async.gather`, and instead use `in_subsegment_async` context manager escape hatch to have the same tracing effect. - - -```python -import asyncio - -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer() -# tracer = Tracer(service="payment") # can also be explicitly defined - -async def another_async_task(): - async with tracer.provider.in_subsegment_async("## another_async_task"): - ... - -async def another_async_task_2(): - async with tracer.provider.in_subsegment_async("## another_async_task_2"): - ... - -@tracer.capture_method -async def collect_payment(charge_id): - asyncio.gather(another_async_task(), another_async_task_2()) - ... - -@tracer.capture_lambda_handler -def handler(event, context) - charge_id = event.get('charge_id') - payment = asyncio.run(collect_payment(charge_id)) # python 3.7+ - ... -``` - -#### Using escape hatch mechanisms - -You can use `tracer.provider` attribute to access all methods provided by `xray_recorder`. This is useful when you need a feature available in X-Ray that is not available in the Tracer middleware, for example [thread-safe](https://github.com/aws/aws-xray-sdk-python/#user-content-trace-threadpoolexecutor), or [context managers](https://github.com/aws/aws-xray-sdk-python/#user-content-start-a-custom-segmentsubsegment). - -**Example using aiohttp with an async context manager** - -> NOTE: It expects you have `aiohttp` as a dependency. `aiohttp_trace_config` uses lazy import to create a trace_config object following `aiohttp` protocol. - -```python -import asyncio -import aiohttp - -from aws_lambda_powertools.tracing import Tracer, aiohttp_trace_config -tracer = Tracer() - -# aiohttp_trace_config is x-ray extension for aiohttp trace config known as aws_xray_trace_config - -async def aiohttp_task(): - # Async context manager as opposed to `@tracer.capture_method` - async with tracer.provider.in_subsegment_async("## aiohttp escape hatch"): - async with aiohttp.ClientSession(trace_configs=[aiohttp_trace_config()]) as session: - async with session.get("https://httpbin.org/json") as resp: - resp = await resp.json() - return resp - -@tracer.capture_method -async def async_tasks(): - ret = await aiohttp_task() - ... - - return { - "task": "done", - **ret - } - -@tracer.capture_lambda_handler -def handler(event, context) - ret = asyncio.run(async_tasks()) # python 3.7+ - ... -``` - -#### Using a pre-configured tracer anywhere - -```python -# handler.py -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer(service="payment") - -@tracer.capture_lambda_handler -def handler(event, context) - charge_id = event.get('charge_id') - payment = collect_payment(charge_id) - ... - -# another_file.py -from aws_lambda_powertools.tracing import Tracer -tracer = Tracer(auto_patch=False) # new instance using existing configuration with auto patching overriden -``` - -### Logging - -#### Structuring logs with Lambda context info - -```python -from aws_lambda_powertools.logging import Logger - -logger = Logger() -# Logger(service="payment", level="INFO") # also accepts explicit service name, log level - -@logger.inject_lambda_context -def handler(event, context) - logger.info("Collecting payment") - ... - # You can log entire objects too - logger.info({ - "operation": "collect_payment", - "charge_id": event['charge_id'] - }) - ... -``` - -
-Exerpt output in CloudWatch Logs - -```json -{ - "timestamp":"2019-08-22 18:17:33,774", - "level":"INFO", - "location":"collect.handler:1", - "service":"payment", - "lambda_function_name":"test", - "lambda_function_memory_size":"128", - "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", - "cold_start": "true", - "sampling_rate": 0.1, - "message": "Collecting payment" -} - -{ - "timestamp":"2019-08-22 18:17:33,774", - "level":"INFO", - "location":"collect.handler:15", - "service":"payment", - "lambda_function_name":"test", - "lambda_function_memory_size":"128", - "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", - "cold_start": "true", - "sampling_rate": 0.1, - "message":{ - "operation":"collect_payment", - "charge_id": "ch_AZFlk2345C0" - } -} -``` -
- -#### Appending additional keys to current logger - -```python -from aws_lambda_powertools.logging import Logger - -logger = Logger() - -@logger.inject_lambda_context -def handler(event, context) - if "order_id" in event: - logger.structure_logs(append=True, order_id=event["order_id"]) - logger.info("Collecting payment") - ... -``` - -
-Exerpt output in CloudWatch Logs - -```json -{ - "timestamp":"2019-08-22 18:17:33,774", - "level":"INFO", - "location":"collect.handler:1", - "service":"payment", - "lambda_function_name":"test", - "lambda_function_memory_size":"128", - "lambda_function_arn":"arn:aws:lambda:eu-west-1:12345678910:function:test", - "lambda_request_id":"52fdfc07-2182-154f-163f-5f0f9a621d72", - "cold_start": "true", - "sampling_rate": 0.1, - "order_id": "order_id_value", - "message": "Collecting payment" -} -``` -
- -### Metrics - -This feature makes use of CloudWatch Embedded Metric Format (EMF), and metrics are created asynchronously by CloudWatch service. - -Metrics middleware validates against the minimum necessary for a metric to be published: - -* At least of one Metric and Dimension -* Maximum of 9 dimensions -* Only one Namespace -* [Any Metric unit supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) - -#### Creating multiple metrics - -If using multiple middlewares, use `log_metrics` as the last decorator, or else it will fail with `SchemaValidationError` if no metrics are recorded. - -```python -from aws_lambda_powertools.metrics import Metrics, MetricUnit - -metrics = Metrics() -metrics.add_namespace(name="ServerlessAirline") -metrics.add_metric(name="ColdStart", unit="Count", value=1) -metrics.add_dimension(name="service", value="booking") - -@metrics.log_metrics -@tracer.capture_lambda_handler -def lambda_handler(evt, ctx): - metrics.add_metric(name="BookingConfirmation", unit="Count", value=1) - some_code() - return True - -def some_code(): - metrics.add_metric(name="some_other_metric", unit=MetricUnit.Seconds, value=1) - ... -``` - -CloudWatch EMF uses the same dimensions across all metrics. If you have metrics that should have different dimensions, use `single_metric` to create a single metric with any dimension you want. Generally, this would be an edge case since you [pay for unique metric](https://aws.amazon.com/cloudwatch/pricing/) - -> unique metric = (metric_name + dimension_name + dimension_value) - -```python -from aws_lambda_powertools.metrics import MetricUnit, single_metric - -with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1) as metric: - metric.add_dimension(name="function_context", value="$LATEST") -``` - -> **NOTE**: When using Metrics() in multiple places in your code, make sure to use `POWERTOOLS_METRICS_NAMESPACE` env var, or setting namespace param. - -### Bring your own middleware - -This feature allows you to create your own middleware as a decorator with ease by following a simple signature. - -* Accept 3 mandatory args - `handler, event, context` -* Always return the handler with event/context or response if executed - - Supports nested middleware/decorators use case - -#### Middleware with no params - -```python -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator - -@lambda_handler_decorator -def middleware_name(handler, event, context): - return handler(event, context) - -@lambda_handler_decorator -def middleware_before_after(handler, event, context): - logic_before_handler_execution() - response = handler(event, context) - logic_after_handler_execution() - return response - - -# middleware_name will wrap Lambda handler -# and simply return the handler as we're not pre/post-processing anything -# then middleware_before_after will wrap middleware_name -# run some code before/after calling the handler returned by middleware_name -# This way, lambda_handler is only actually called once (top-down) -@middleware_before_after # This will run last -@middleware_name # This will run first -def lambda_handler(event, context): - return True -``` - -#### Middleware with params - -```python -@lambda_handler_decorator -def obfuscate_sensitive_data(handler, event, context, fields=None): - # Obfuscate email before calling Lambda handler - if fields: - for field in fields: - field = event.get(field, "") - event[field] = obfuscate_pii(field) - - return handler(event, context) - -@obfuscate_sensitive_data(fields=["email"]) -def lambda_handler(event, context): - return True -``` - -#### Tracing middleware execution - -This makes use of an existing Tracer instance that you may have initialized anywhere in your code. If no Tracer instance is found, it'll initialize one using default options. - -```python -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator - -@lambda_handler_decorator(trace_execution=True) -def middleware_name(handler, event, context): - return handler(event, context) - -@middleware_name -def lambda_handler(event, context): - return True -``` - -Optionally, you can enrich the final trace with additional annotations and metadata by retrieving a copy of the Tracer used. - -```python -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator -from aws_lambda_powertools.tracing import Tracer - -@lambda_handler_decorator(trace_execution=True) -def middleware_name(handler, event, context): - tracer = Tracer() # Takes a copy of an existing tracer instance - tracer.add_anotation... - tracer.metadata... - return handler(event, context) - -@middleware_name -def lambda_handler(event, context): - return True -``` - -### Debug mode - -By default, all log statements from AWS Lambda Powertools package are suppressed. If you'd like to enable them, use `set_package_logger` utility: - -```python -import aws_lambda_powertools -aws_lambda_powertools.logging.logger.set_package_logger() -... -``` diff --git a/python/tests/functional/__init__.py b/tests/functional/__init__.py similarity index 100% rename from python/tests/functional/__init__.py rename to tests/functional/__init__.py diff --git a/python/tests/functional/test_aws_lambda_logging.py b/tests/functional/test_aws_lambda_logging.py similarity index 100% rename from python/tests/functional/test_aws_lambda_logging.py rename to tests/functional/test_aws_lambda_logging.py diff --git a/python/tests/functional/test_logger.py b/tests/functional/test_logger.py similarity index 100% rename from python/tests/functional/test_logger.py rename to tests/functional/test_logger.py diff --git a/python/tests/functional/test_metrics.py b/tests/functional/test_metrics.py similarity index 100% rename from python/tests/functional/test_metrics.py rename to tests/functional/test_metrics.py diff --git a/python/tests/functional/test_middleware_factory.py b/tests/functional/test_middleware_factory.py similarity index 100% rename from python/tests/functional/test_middleware_factory.py rename to tests/functional/test_middleware_factory.py diff --git a/python/tests/functional/test_tracing.py b/tests/functional/test_tracing.py similarity index 100% rename from python/tests/functional/test_tracing.py rename to tests/functional/test_tracing.py diff --git a/python/tests/unit/__init__.py b/tests/unit/__init__.py similarity index 100% rename from python/tests/unit/__init__.py rename to tests/unit/__init__.py diff --git a/python/tests/unit/test_tracing.py b/tests/unit/test_tracing.py similarity index 100% rename from python/tests/unit/test_tracing.py rename to tests/unit/test_tracing.py