From e7d1440a003b445925a39566fab05a63fe6d598e Mon Sep 17 00:00:00 2001
From: Yair Siman Tov <63305203+yairsimantov20@users.noreply.github.com>
Date: Sun, 18 Feb 2024 13:07:09 +0200
Subject: [PATCH] Send logs to Port integration (#371)
---
CHANGELOG.md | 19 ++
integrations/jira/jira/client.py | 1 +
poetry.lock | 311 +++++++++---------
port_ocean/bootstrap.py | 2 +-
port_ocean/cli/commands/defaults/clean.py | 12 +-
port_ocean/cli/commands/defaults/dock.py | 12 +-
port_ocean/clients/port/authentication.py | 3 +-
.../clients/port/mixins/integrations.py | 27 +-
port_ocean/config/base.py | 29 ++
port_ocean/config/dynamic.py | 8 +-
port_ocean/config/settings.py | 19 +-
port_ocean/consumers/base_consumer.py | 7 -
port_ocean/consumers/kafka_consumer.py | 16 +-
port_ocean/context/event.py | 2 +-
port_ocean/context/ocean.py | 55 ++--
port_ocean/context/utils.py | 24 --
port_ocean/core/event_listener/base.py | 26 +-
port_ocean/core/event_listener/http.py | 5 +-
port_ocean/core/event_listener/kafka.py | 17 +-
port_ocean/core/event_listener/once.py | 4 +-
port_ocean/core/event_listener/polling.py | 12 +-
port_ocean/exceptions/context.py | 4 +
port_ocean/exceptions/utils.py | 9 +
port_ocean/log/__init__.py | 0
port_ocean/log/handlers.py | 88 +++++
port_ocean/log/logger_setup.py | 67 ++++
port_ocean/log/sensetive.py | 51 +++
port_ocean/logger_setup.py | 38 ---
port_ocean/middlewares.py | 2 +-
port_ocean/ocean.py | 24 +-
port_ocean/run.py | 14 +-
port_ocean/utils/__init__.py | 3 +
port_ocean/utils/async_http.py | 29 ++
port_ocean/utils/misc.py | 56 ++++
port_ocean/{utils.py => utils/repeat.py} | 86 +----
port_ocean/utils/signal.py | 54 +++
port_ocean/version.py | 2 +-
pyproject.toml | 2 +-
38 files changed, 735 insertions(+), 405 deletions(-)
delete mode 100644 port_ocean/consumers/base_consumer.py
delete mode 100644 port_ocean/context/utils.py
create mode 100644 port_ocean/exceptions/utils.py
create mode 100644 port_ocean/log/__init__.py
create mode 100644 port_ocean/log/handlers.py
create mode 100644 port_ocean/log/logger_setup.py
create mode 100644 port_ocean/log/sensetive.py
delete mode 100644 port_ocean/logger_setup.py
create mode 100644 port_ocean/utils/__init__.py
create mode 100644 port_ocean/utils/async_http.py
create mode 100644 port_ocean/utils/misc.py
rename port_ocean/{utils.py => utils/repeat.py} (51%)
create mode 100644 port_ocean/utils/signal.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 509052242d..3381c0f486 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,25 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
+## 0.5.0 (2024-02-18)
+
+
+### Features
+
+- Added a method for ocean integration to redact sensitive information from the logs and automatically apply it to sensitive configurations and known sensitive patterns. (#1)
+- Added an HTTP handler for Ocean logs to facilitate sending the logs to the Port. (#2)
+
+### Improvements
+
+- Seperated the `port_ocean.utils` file into multiple files within the `utils` folder to improve code organization. (#1)
+- Changed the Ocean context to be a global variable instead of using Localstack, preventing the framework from re-initiating the context for each thread. (#2)
+
+### Bug Fixes
+
+- Fixed an issue where the event listener was causing the application to continue running even after receiving a termination signal. (#1)
+- Fixed a bug that caused some termination signal handlers to not work by consolidating the signal listeners in a single class, as signals can only have one listener. (#2)
+
+
## 0.4.17 (2024-01-23)
diff --git a/integrations/jira/jira/client.py b/integrations/jira/jira/client.py
index 5650fefb2f..b10f14af90 100644
--- a/integrations/jira/jira/client.py
+++ b/integrations/jira/jira/client.py
@@ -4,6 +4,7 @@
from httpx import Timeout, BasicAuth
from jira.overrides import JiraResourceConfig
from loguru import logger
+
from port_ocean.context.event import event
from port_ocean.context.ocean import ocean
from port_ocean.utils import http_async_client
diff --git a/poetry.lock b/poetry.lock
index 3884565ae3..fb5ee2bdbd 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -70,33 +70,33 @@ chardet = ">=3.0.2"
[[package]]
name = "black"
-version = "23.12.0"
+version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"},
- {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"},
- {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"},
- {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"},
- {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"},
- {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"},
- {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"},
- {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"},
- {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"},
- {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"},
- {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"},
- {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"},
- {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"},
- {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"},
- {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"},
- {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"},
- {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"},
- {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"},
- {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"},
- {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"},
- {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"},
- {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"},
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
@@ -114,13 +114,13 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
-version = "2023.11.17"
+version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
- {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
- {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
@@ -332,17 +332,18 @@ rich = "*"
[[package]]
name = "dill"
-version = "0.3.7"
+version = "0.3.8"
description = "serialize all of Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"},
- {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"},
+ {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"},
+ {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
+profile = ["gprof2dot (>=2022.7.29)"]
[[package]]
name = "fastapi"
@@ -591,71 +592,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
-version = "2.1.3"
+version = "2.1.5"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
files = [
- {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
- {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
- {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
- {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
- {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
- {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
- {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
- {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
+ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
@@ -761,28 +762,28 @@ files = [
[[package]]
name = "platformdirs"
-version = "4.1.0"
+version = "4.2.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.8"
files = [
- {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
- {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
+ {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
+ {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
]
[package.extras]
-docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
+docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
[[package]]
name = "pluggy"
-version = "1.3.0"
+version = "1.4.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
- {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
+ {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
]
[package.extras]
@@ -791,47 +792,47 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
-version = "1.10.13"
+version = "1.10.14"
description = "Data validation and settings management using python type hints"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"},
- {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"},
- {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"},
- {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"},
- {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"},
- {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"},
- {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"},
- {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"},
- {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"},
- {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"},
- {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"},
- {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"},
- {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"},
- {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"},
- {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"},
- {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"},
- {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"},
- {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"},
- {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"},
- {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"},
- {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"},
- {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"},
- {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"},
- {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"},
- {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"},
- {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"},
- {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"},
- {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"},
- {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"},
- {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"},
- {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"},
- {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"},
- {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"},
- {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"},
- {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"},
- {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"},
+ {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"},
+ {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"},
+ {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"},
+ {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"},
+ {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"},
+ {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"},
+ {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"},
+ {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"},
+ {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"},
+ {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"},
+ {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"},
+ {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"},
+ {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"},
+ {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"},
+ {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"},
+ {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"},
+ {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"},
+ {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"},
+ {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"},
+ {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"},
+ {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"},
+ {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"},
+ {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"},
+ {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"},
+ {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"},
+ {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"},
+ {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"},
+ {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"},
+ {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"},
+ {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"},
+ {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"},
+ {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"},
+ {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"},
+ {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"},
+ {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"},
+ {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"},
]
[package.dependencies]
@@ -918,13 +919,13 @@ testutils = ["gitpython (>3)"]
[[package]]
name = "pytest"
-version = "7.4.3"
+version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
- {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
@@ -952,13 +953,13 @@ six = ">=1.5"
[[package]]
name = "python-slugify"
-version = "8.0.1"
+version = "8.0.4"
description = "A Python slugify application that also handles Unicode"
optional = true
python-versions = ">=3.7"
files = [
- {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"},
- {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"},
+ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"},
+ {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"},
]
[package.dependencies]
@@ -979,7 +980,6 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
- {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -987,15 +987,8 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
- {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
- {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
- {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
- {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
- {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
- {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
- {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -1012,7 +1005,6 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
- {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -1020,7 +1012,6 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
- {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -1184,13 +1175,13 @@ dev = ["furo", "packaging", "sphinx (>=5)", "twisted"]
[[package]]
name = "types-python-dateutil"
-version = "2.8.19.14"
+version = "2.8.19.20240106"
description = "Typing stubs for python-dateutil"
optional = true
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"},
- {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"},
+ {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"},
+ {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"},
]
[[package]]
@@ -1372,13 +1363,13 @@ files = [
[[package]]
name = "yamllint"
-version = "1.33.0"
+version = "1.35.0"
description = "A linter for YAML files."
optional = false
python-versions = ">=3.8"
files = [
- {file = "yamllint-1.33.0-py3-none-any.whl", hash = "sha256:28a19f5d68d28d8fec538a1db21bb2d84c7dc2e2ea36266da8d4d1c5a683814d"},
- {file = "yamllint-1.33.0.tar.gz", hash = "sha256:2dceab9ef2d99518a2fcf4ffc964d44250ac4459be1ba3ca315118e4a1a81f7d"},
+ {file = "yamllint-1.35.0-py3-none-any.whl", hash = "sha256:601b0adaaac6d9bacb16a2e612e7ee8d23caf941ceebf9bfe2cff0f196266004"},
+ {file = "yamllint-1.35.0.tar.gz", hash = "sha256:9bc99c3e9fe89b4c6ee26e17aa817cf2d14390de6577cb6e2e6ed5f72120c835"},
]
[package.dependencies]
diff --git a/port_ocean/bootstrap.py b/port_ocean/bootstrap.py
index 117a35efcb..939dd3aa32 100644
--- a/port_ocean/bootstrap.py
+++ b/port_ocean/bootstrap.py
@@ -7,7 +7,7 @@
from port_ocean.core.integrations.base import BaseIntegration
from port_ocean.ocean import Ocean
-from port_ocean.utils import load_module
+from port_ocean.utils.misc import load_module
def _get_base_integration_class_from_module(
diff --git a/port_ocean/cli/commands/defaults/clean.py b/port_ocean/cli/commands/defaults/clean.py
index d1d8cacb53..888e2a2ffd 100644
--- a/port_ocean/cli/commands/defaults/clean.py
+++ b/port_ocean/cli/commands/defaults/clean.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
-import click
-
from inspect import getmembers
-from port_ocean.utils import load_module
-from .group import defaults
+import click
+
+from port_ocean.bootstrap import create_default_app
from port_ocean.cli.commands.main import print_logo, console
-from port_ocean.ocean import Ocean
from port_ocean.core.defaults import clean_defaults
-from port_ocean.bootstrap import create_default_app
+from port_ocean.ocean import Ocean
+from port_ocean.utils.misc import load_module
+from .group import defaults
@defaults.command()
diff --git a/port_ocean/cli/commands/defaults/dock.py b/port_ocean/cli/commands/defaults/dock.py
index 32c881bc3a..71675aefef 100644
--- a/port_ocean/cli/commands/defaults/dock.py
+++ b/port_ocean/cli/commands/defaults/dock.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
-import click
-
from inspect import getmembers
-from port_ocean.utils import load_module
-from .group import defaults
+import click
+
+from port_ocean.bootstrap import create_default_app
from port_ocean.cli.commands.main import print_logo, console
-from port_ocean.ocean import Ocean
from port_ocean.core.defaults.initialize import initialize_defaults
-from port_ocean.bootstrap import create_default_app
+from port_ocean.ocean import Ocean
+from port_ocean.utils.misc import load_module
+from .group import defaults
@defaults.command()
diff --git a/port_ocean/clients/port/authentication.py b/port_ocean/clients/port/authentication.py
index d1f8ef47ee..1ddd506aeb 100644
--- a/port_ocean/clients/port/authentication.py
+++ b/port_ocean/clients/port/authentication.py
@@ -6,7 +6,7 @@
from port_ocean.clients.port.types import UserAgentType
from port_ocean.clients.port.utils import handle_status_code
-from port_ocean.utils import get_time
+from port_ocean.utils.misc import get_time
class TokenResponse(BaseModel):
@@ -79,5 +79,4 @@ async def token(self) -> str:
self.last_token_object = await self._get_token(
self.client_id, self.client_secret
)
-
return self.last_token_object.full_token
diff --git a/port_ocean/clients/port/mixins/integrations.py b/port_ocean/clients/port/mixins/integrations.py
index ac399019a0..3f357d0975 100644
--- a/port_ocean/clients/port/mixins/integrations.py
+++ b/port_ocean/clients/port/mixins/integrations.py
@@ -1,4 +1,4 @@
-from typing import Any, TYPE_CHECKING, Optional
+from typing import Any, TYPE_CHECKING, Optional, TypedDict
import httpx
from loguru import logger
@@ -11,6 +11,10 @@
from port_ocean.core.handlers.port_app_config.models import PortAppConfig
+class LogAttributes(TypedDict):
+ ingestUrl: str
+
+
class IntegrationClientMixin:
def __init__(
self,
@@ -23,6 +27,7 @@ def __init__(
self.integration_version = integration_version
self.auth = auth
self.client = client
+ self._log_attributes: LogAttributes | None = None
async def _get_current_integration(self) -> httpx.Response:
logger.info(f"Fetching integration with id: {self.integration_identifier}")
@@ -39,6 +44,12 @@ async def get_current_integration(
handle_status_code(response, should_raise, should_log)
return response.json()["integration"]
+ async def get_log_attributes(self) -> LogAttributes:
+ if self._log_attributes is None:
+ response = await self.get_current_integration()
+ self._log_attributes = response["logAttributes"]
+ return self._log_attributes
+
async def create_integration(
self,
_type: str,
@@ -112,3 +123,17 @@ async def initialize_integration(
logger.info(
f"Integration with id: {self.integration_identifier} successfully registered"
)
+
+ async def ingest_integration_logs(self, logs: list[dict[str, Any]]) -> None:
+ logger.debug("Ingesting logs")
+ log_attributes = await self.get_log_attributes()
+ headers = await self.auth.headers()
+ response = await self.client.post(
+ log_attributes["ingestUrl"],
+ headers=headers,
+ json={
+ "logs": logs,
+ },
+ )
+ handle_status_code(response)
+ logger.debug("Logs successfully ingested")
diff --git a/port_ocean/config/base.py b/port_ocean/config/base.py
index ace0db1a66..de6b089951 100644
--- a/port_ocean/config/base.py
+++ b/port_ocean/config/base.py
@@ -131,6 +131,9 @@ def load_providers(
class BaseOceanSettings(BaseSettings):
base_path: str
+ def get_sensitive_fields_data(self) -> set[str]:
+ return _get_sensitive_information(self)
+
class Config:
yaml_file = "./config.yaml"
env_prefix = "OCEAN__"
@@ -153,3 +156,29 @@ def customise_sources( # type: ignore
s, env_settings(s), init_settings.init_kwargs["base_path"]
),
)
+
+
+class BaseOceanModel(BaseModel):
+ def get_sensitive_fields_data(self) -> set[str]:
+ return _get_sensitive_information(self)
+
+
+def _get_sensitive_information(
+ model: BaseOceanModel | BaseSettings,
+) -> set[str]:
+ sensitive_fields = [
+ field_name
+ for field_name, field in model.__fields__.items()
+ if field.field_info.extra.get("sensitive", False)
+ ]
+ sensitive_set = {str(getattr(model, field_name)) for field_name in sensitive_fields}
+
+ recursive_sensitive_data = [
+ getattr(model, field_name).get_sensitive_fields_data()
+ for field_name, field in model.__fields__.items()
+ if isinstance(getattr(model, field_name), BaseOceanModel)
+ ]
+ for sensitive_data in recursive_sensitive_data:
+ sensitive_set.update(sensitive_data)
+
+ return sensitive_set
diff --git a/port_ocean/config/dynamic.py b/port_ocean/config/dynamic.py
index 495f55dc2a..b8a10229c6 100644
--- a/port_ocean/config/dynamic.py
+++ b/port_ocean/config/dynamic.py
@@ -3,7 +3,9 @@
from humps import decamelize
from pydantic import BaseModel, AnyUrl, create_model, Extra, parse_obj_as, validator
-from pydantic.fields import ModelField
+from pydantic.fields import ModelField, Field
+
+from port_ocean.config.base import BaseOceanModel
class Configuration(BaseModel, extra=Extra.allow):
@@ -11,6 +13,7 @@ class Configuration(BaseModel, extra=Extra.allow):
type: str
required: bool = False
default: Optional[Any]
+ sensitive: bool = False
def dynamic_parse(value: Any, field: ModelField) -> Any:
@@ -51,11 +54,12 @@ def default_config_factory(configurations: Any) -> Type[BaseModel]:
default = parse_obj_as(field_type, config.default)
fields[decamelize(config.name)] = (
field_type,
- default,
+ Field(default, sensitive=config.sensitive),
)
dynamic_model = create_model( # type: ignore
__model_name="Config",
+ __base__=BaseOceanModel,
**fields,
__validators__={"dynamic_parse": validator("*", pre=True)(dynamic_parse)},
)
diff --git a/port_ocean/config/settings.py b/port_ocean/config/settings.py
index 0472511358..7e3f0e0e18 100644
--- a/port_ocean/config/settings.py
+++ b/port_ocean/config/settings.py
@@ -1,15 +1,18 @@
from typing import Any, Literal
-from pydantic import BaseSettings, BaseModel, Extra, AnyHttpUrl, parse_obj_as, validator
+from pydantic import Extra, AnyHttpUrl, parse_obj_as, validator
+from pydantic.fields import Field
+from pydantic.main import BaseModel
-from port_ocean.config.base import BaseOceanSettings
+from port_ocean.config.base import BaseOceanSettings, BaseOceanModel
from port_ocean.core.event_listener import EventListenerSettingsType
LogLevelType = Literal["ERROR", "WARNING", "INFO", "DEBUG", "CRITICAL"]
-class ApplicationSettings(BaseSettings):
+class ApplicationSettings(BaseOceanModel):
log_level: LogLevelType = "INFO"
+ enable_http_logging: bool = True
port: int = 8000
class Config:
@@ -22,16 +25,16 @@ def customise_sources(cls, init_settings, env_settings, *_, **__): # type: igno
return env_settings, init_settings
-class PortSettings(BaseModel, extra=Extra.allow):
- client_id: str
- client_secret: str
+class PortSettings(BaseOceanModel, extra=Extra.allow):
+ client_id: str = Field(..., sensitive=True)
+ client_secret: str = Field(..., sensitive=True)
base_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://api.getport.io")
-class IntegrationSettings(BaseModel, extra=Extra.allow):
+class IntegrationSettings(BaseOceanModel, extra=Extra.allow):
identifier: str
type: str
- config: dict[str, Any]
+ config: dict[str, Any] | BaseModel
@validator("identifier", "type")
def validate_lower(cls, v: str) -> str:
diff --git a/port_ocean/consumers/base_consumer.py b/port_ocean/consumers/base_consumer.py
deleted file mode 100644
index 56d87096d7..0000000000
--- a/port_ocean/consumers/base_consumer.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from abc import ABC, abstractmethod
-
-
-class BaseConsumer(ABC):
- @abstractmethod
- def start(self) -> None:
- pass
diff --git a/port_ocean/consumers/kafka_consumer.py b/port_ocean/consumers/kafka_consumer.py
index d3862f3608..1599e41157 100644
--- a/port_ocean/consumers/kafka_consumer.py
+++ b/port_ocean/consumers/kafka_consumer.py
@@ -1,12 +1,10 @@
-import signal
+import threading
from typing import Any, Callable
from confluent_kafka import Consumer, KafkaException, Message # type: ignore
from loguru import logger
from pydantic import BaseModel
-from port_ocean.consumers.base_consumer import BaseConsumer
-
class KafkaConsumerConfig(BaseModel):
brokers: str
@@ -19,7 +17,7 @@ class KafkaConsumerConfig(BaseModel):
consumer_poll_timeout: int
-class KafkaConsumer(BaseConsumer):
+class KafkaConsumer:
def __init__(
self,
msg_process: Callable[[Message], None],
@@ -30,9 +28,6 @@ def __init__(
self.org_id = org_id
self.config = config
- signal.signal(signal.SIGINT, self.exit_gracefully)
- signal.signal(signal.SIGTERM, self.exit_gracefully)
-
self.msg_process = msg_process
if config.kafka_security_enabled:
kafka_config = {
@@ -53,7 +48,8 @@ def __init__(
self.consumer = Consumer(kafka_config)
- def start(self) -> None:
+ def start(self, event: threading.Event) -> None:
+ self.running = True
try:
logger.info("Start consumer...")
@@ -64,8 +60,7 @@ def start(self) -> None:
),
)
logger.info("Subscribed to topics")
- self.running = True
- while self.running:
+ while self.running and not event.is_set():
try:
msg = self.consumer.poll(timeout=self.config.consumer_poll_timeout)
if msg is None:
@@ -90,6 +85,7 @@ def start(self) -> None:
except Exception as message_error:
logger.error(str(message_error))
finally:
+ logger.info("Closing consumer...")
self.exit_gracefully()
def exit_gracefully(self, *_: Any) -> None:
diff --git a/port_ocean/context/event.py b/port_ocean/context/event.py
index b95ae10d09..0e83408a4a 100644
--- a/port_ocean/context/event.py
+++ b/port_ocean/context/event.py
@@ -22,7 +22,7 @@
EventContextNotFoundError,
ResourceContextNotFoundError,
)
-from port_ocean.utils import get_time
+from port_ocean.utils.misc import get_time
if TYPE_CHECKING:
from port_ocean.core.handlers.port_app_config.models import (
diff --git a/port_ocean/context/ocean.py b/port_ocean/context/ocean.py
index fd08497854..0830729788 100644
--- a/port_ocean/context/ocean.py
+++ b/port_ocean/context/ocean.py
@@ -1,8 +1,8 @@
-from dataclasses import dataclass
-from typing import Callable, TYPE_CHECKING, Any, Literal
+from typing import Callable, TYPE_CHECKING, Any, Literal, Union
from fastapi import APIRouter
-from werkzeug.local import LocalProxy, LocalStack
+from pydantic.main import BaseModel
+from werkzeug.local import LocalProxy
from port_ocean.clients.port.types import UserAgentType
from port_ocean.core.models import Entity
@@ -12,7 +12,10 @@
RawEntityDiff,
EntityDiff,
)
-from port_ocean.exceptions.context import PortOceanContextNotFoundError
+from port_ocean.exceptions.context import (
+ PortOceanContextNotFoundError,
+ PortOceanContextAlreadyInitializedError,
+)
if TYPE_CHECKING:
from port_ocean.config.settings import IntegrationConfiguration
@@ -21,9 +24,21 @@
from port_ocean.clients.port.client import PortClient
-@dataclass
class PortOceanContext:
- app: "Ocean"
+ def __init__(self, app: Union["Ocean", None]) -> None:
+ self._app = app
+
+ @property
+ def app(self) -> "Ocean":
+ if self._app is None:
+ raise PortOceanContextNotFoundError(
+ "You must first initialize PortOcean in order to use it"
+ )
+ return self._app
+
+ @property
+ def initialized(self) -> bool:
+ return self._app is not None
@property
def config(self) -> "IntegrationConfiguration":
@@ -39,6 +54,8 @@ def integration(self) -> "BaseIntegration":
@property
def integration_config(self) -> dict[str, Any]:
+ if isinstance(self.app.config.integration.config, BaseModel):
+ return self.app.config.integration.config.dict()
return self.app.config.integration.config
@property
@@ -118,27 +135,17 @@ async def sync_raw_all(self) -> None:
await self.integration.sync_raw_all(trigger_type="manual")
-_port_ocean_context_stack: LocalStack[PortOceanContext] = LocalStack()
+_port_ocean: PortOceanContext = PortOceanContext(None)
def initialize_port_ocean_context(ocean_app: "Ocean") -> None:
- """
- This Function initializes the PortOcean context and pushes it into the LocalStack().
- """
- _port_ocean_context_stack.push(PortOceanContext(app=ocean_app))
-
-
-def _get_port_ocean_context() -> PortOceanContext:
- """
- Get the PortOcean context from the current thread.
- """
- port_ocean_context = _port_ocean_context_stack.top
- if port_ocean_context is None:
- raise PortOceanContextNotFoundError(
- "You must first initialize PortOcean in order to use it"
- )
+ global _port_ocean
- return port_ocean_context
+ if _port_ocean.initialized:
+ raise PortOceanContextAlreadyInitializedError(
+ "PortOcean context is already initialized"
+ )
+ _port_ocean = PortOceanContext(app=ocean_app)
-ocean: PortOceanContext = LocalProxy(lambda: _get_port_ocean_context()) # type: ignore
+ocean: PortOceanContext = LocalProxy(lambda: _port_ocean) # type: ignore
diff --git a/port_ocean/context/utils.py b/port_ocean/context/utils.py
deleted file mode 100644
index 21f9cfd122..0000000000
--- a/port_ocean/context/utils.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import Callable
-
-from port_ocean.context.ocean import (
- initialize_port_ocean_context,
- ocean,
-)
-
-
-def wrap_method_with_context(
- func: Callable[..., None],
-) -> Callable[..., None]:
- """
- A method that wraps a method and initializing the PortOceanContext and invoking the given function.
-
- :param func: The function to be wrapped.
- """
- # assign the current ocean app to a variable
- ocean_app = ocean.app
-
- def wrapper(*args, **kwargs) -> None: # type: ignore
- initialize_port_ocean_context(ocean_app=ocean_app)
- func(*args, **kwargs)
-
- return wrapper
diff --git a/port_ocean/core/event_listener/base.py b/port_ocean/core/event_listener/base.py
index aa6394be0c..898bde0291 100644
--- a/port_ocean/core/event_listener/base.py
+++ b/port_ocean/core/event_listener/base.py
@@ -1,7 +1,11 @@
from abc import abstractmethod
+from asyncio import Task
from typing import TypedDict, Callable, Any, Awaitable
-from pydantic import BaseModel, Extra
+from pydantic import Extra
+
+from port_ocean.config.base import BaseOceanModel
+from port_ocean.utils.signal import signal_handler
class EventListenerEvents(TypedDict):
@@ -18,13 +22,29 @@ def __init__(
events: EventListenerEvents,
):
self.events = events
+ self._tasks_to_close: list[Task[Any]] = []
- @abstractmethod
async def start(self) -> None:
+ signal_handler.register(self._stop)
+ await self._start()
+
+ @abstractmethod
+ async def _start(self) -> None:
+ pass
+
+ def stop(self) -> None:
+ self._stop()
+ for task in self._tasks_to_close:
+ task.cancel()
+
+ def _stop(self) -> None:
+ """
+ Can be used for event listeners that need cleanup before exiting.
+ """
pass
-class EventListenerSettings(BaseModel, extra=Extra.allow):
+class EventListenerSettings(BaseOceanModel, extra=Extra.allow):
type: str
def to_request(self) -> dict[str, Any]:
diff --git a/port_ocean/core/event_listener/http.py b/port_ocean/core/event_listener/http.py
index ca35457af2..69083daa00 100644
--- a/port_ocean/core/event_listener/http.py
+++ b/port_ocean/core/event_listener/http.py
@@ -3,6 +3,7 @@
from fastapi import APIRouter
from loguru import logger
from pydantic import AnyHttpUrl
+from pydantic.fields import Field
from port_ocean.context.ocean import ocean
from port_ocean.core.event_listener.base import (
@@ -24,7 +25,7 @@ class HttpEventListenerSettings(EventListenerSettings):
"""
type: Literal["WEBHOOK"]
- app_host: AnyHttpUrl
+ app_host: AnyHttpUrl = Field(..., sensitive=True)
def to_request(self) -> dict[str, Any]:
return {
@@ -53,7 +54,7 @@ def __init__(
super().__init__(events)
self.event_listener_config = event_listener_config
- async def start(self) -> None:
+ async def _start(self) -> None:
"""
Starts the HTTP event listener.
It sets up an APIRouter to handle the `/resync` endpoint and registers the "on_resync" event handler.
diff --git a/port_ocean/core/event_listener/kafka.py b/port_ocean/core/event_listener/kafka.py
index df3ad88f08..4ab52cf87b 100644
--- a/port_ocean/core/event_listener/kafka.py
+++ b/port_ocean/core/event_listener/kafka.py
@@ -11,7 +11,6 @@
from port_ocean.context.ocean import (
ocean,
)
-from port_ocean.context.utils import wrap_method_with_context
from port_ocean.core.event_listener.base import (
BaseEventListener,
EventListenerEvents,
@@ -71,6 +70,8 @@ def __init__(
self.org_id = org_id
self.integration_identifier = integration_identifier
self.integration_type = integration_type
+ self._consumer_kill_event = threading.Event()
+ self._running_task = None
async def _get_kafka_config(self) -> KafkaConsumerConfig:
"""
@@ -120,7 +121,9 @@ async def try_wrapper() -> None:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
- loop.run_until_complete(try_wrapper())
+ running_task = loop.create_task(try_wrapper())
+ self._tasks_to_close.append(running_task)
+ loop.run_until_complete(running_task)
def _handle_message(self, raw_msg: Message) -> None:
"""
@@ -141,12 +144,12 @@ def _handle_message(self, raw_msg: Message) -> None:
logger.info(f"spawning thread {thread_name} to start resync")
threading.Thread(
name=thread_name,
- target=wrap_method_with_context(self._resync_in_new_event_loop),
+ target=self._resync_in_new_event_loop,
args=(message,),
).start()
logger.info(f"thread {thread_name} started")
- async def start(self) -> None:
+ async def _start(self) -> None:
"""
The main method that starts the Kafka consumer.
It creates a KafkaConsumer instance with the given configuration and starts it in a separate thread.
@@ -159,5 +162,9 @@ async def start(self) -> None:
logger.info("Starting Kafka consumer")
threading.Thread(
name="ocean_kafka_consumer",
- target=wrap_method_with_context(func=consumer.start),
+ target=consumer.start,
+ args=(self._consumer_kill_event,),
).start()
+
+ def _stop(self) -> None:
+ self._consumer_kill_event.set()
diff --git a/port_ocean/core/event_listener/once.py b/port_ocean/core/event_listener/once.py
index 9d437c7585..15154a4c35 100644
--- a/port_ocean/core/event_listener/once.py
+++ b/port_ocean/core/event_listener/once.py
@@ -8,7 +8,7 @@
EventListenerEvents,
EventListenerSettings,
)
-from port_ocean.utils import repeat_every
+from port_ocean.utils.repeat import repeat_every
class OnceEventListenerSettings(EventListenerSettings):
@@ -42,7 +42,7 @@ def __init__(
super().__init__(events)
self.event_listener_config = event_listener_config
- async def start(self) -> None:
+ async def _start(self) -> None:
"""
Starts the resync process, and exits the application once finished.
"""
diff --git a/port_ocean/core/event_listener/polling.py b/port_ocean/core/event_listener/polling.py
index f747c24615..d69a540806 100644
--- a/port_ocean/core/event_listener/polling.py
+++ b/port_ocean/core/event_listener/polling.py
@@ -1,3 +1,4 @@
+from asyncio import Task, get_event_loop
from typing import Literal, Any
from loguru import logger
@@ -8,7 +9,7 @@
EventListenerEvents,
EventListenerSettings,
)
-from port_ocean.utils import repeat_every
+from port_ocean.utils.repeat import repeat_every
class PollingEventListenerSettings(EventListenerSettings):
@@ -49,7 +50,7 @@ def __init__(
self.event_listener_config = event_listener_config
self._last_updated_at = None
- async def start(self) -> None:
+ async def _start(self) -> None:
"""
Starts the polling event listener.
It registers the "on_resync" event to be called every `interval` seconds specified in the `event_listener_config`.
@@ -75,7 +76,12 @@ async def resync() -> None:
if should_resync:
logger.info("Detected change in integration, resyncing")
self._last_updated_at = last_updated_at
- await self.events["on_resync"]({})
+ running_task: Task[Any] = get_event_loop().create_task(
+ self.events["on_resync"]({}) # type: ignore
+ )
+ self._tasks_to_close.append(running_task)
+
+ await running_task
# Execute resync repeatedly task
await resync()
diff --git a/port_ocean/exceptions/context.py b/port_ocean/exceptions/context.py
index 77283e28a0..9e23f3fd2e 100644
--- a/port_ocean/exceptions/context.py
+++ b/port_ocean/exceptions/context.py
@@ -15,3 +15,7 @@ class EventContextNotFoundError(NoContextError):
class PortOceanContextNotFoundError(NoContextError):
pass
+
+
+class PortOceanContextAlreadyInitializedError(NoContextError):
+ pass
diff --git a/port_ocean/exceptions/utils.py b/port_ocean/exceptions/utils.py
new file mode 100644
index 0000000000..55f5617ce4
--- /dev/null
+++ b/port_ocean/exceptions/utils.py
@@ -0,0 +1,9 @@
+from port_ocean.exceptions.core import OceanAbortException
+
+
+class SignalHandlerNotInitialized(OceanAbortException):
+ pass
+
+
+class SignalHandlerAlreadyInitialized(OceanAbortException):
+ pass
diff --git a/port_ocean/log/__init__.py b/port_ocean/log/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/port_ocean/log/handlers.py b/port_ocean/log/handlers.py
new file mode 100644
index 0000000000..2c13243e1a
--- /dev/null
+++ b/port_ocean/log/handlers.py
@@ -0,0 +1,88 @@
+import asyncio
+import logging
+import sys
+import threading
+import time
+from datetime import datetime
+from logging.handlers import MemoryHandler
+from typing import Any
+
+from loguru import logger
+
+from port_ocean import Ocean
+from port_ocean.context.ocean import ocean
+
+
+def _serialize_record(record: logging.LogRecord) -> dict[str, Any]:
+ return {
+ "message": record.msg,
+ "level": record.levelname,
+ "timestamp": datetime.utcfromtimestamp(record.created).strftime(
+ "%Y-%m-%dT%H:%M:%S.%fZ"
+ ),
+ "extra": record.__dict__["extra"],
+ }
+
+
+class HTTPMemoryHandler(MemoryHandler):
+ def __init__(
+ self,
+ capacity: int = 100,
+ flush_level: int = logging.FATAL,
+ flush_interval: int = 5,
+ flush_size: int = 1024,
+ ):
+ super().__init__(capacity, flushLevel=flush_level, target=None)
+ self.flush_interval = flush_interval
+ self.flush_size = flush_size
+ self.last_flush_time = time.time()
+ self._serialized_buffer: list[dict[str, Any]] = []
+
+ @property
+ def ocean(self) -> Ocean | None:
+ # We want to wait for the context to be initialized before we can send logs
+ if ocean.initialized:
+ return ocean.app
+ return None
+
+ def emit(self, record: logging.LogRecord) -> None:
+ self._serialized_buffer.append(_serialize_record(record))
+ super().emit(record)
+
+ def shouldFlush(self, record: logging.LogRecord) -> bool:
+ """
+ Extending shouldFlush to include size and time validation as part of the decision whether to flush
+ """
+ if bool(self.buffer) and (
+ super(HTTPMemoryHandler, self).shouldFlush(record)
+ or sys.getsizeof(self.buffer) >= self.flush_size
+ or time.time() - self.last_flush_time >= self.flush_interval
+ ):
+ return True
+ return False
+
+ def flush(self) -> None:
+ if self.ocean is None or not self.buffer:
+ return
+
+ def _wrap_event_loop(_ocean: Ocean, logs_to_send: list[dict[str, Any]]) -> None:
+ loop = asyncio.new_event_loop()
+ loop.run_until_complete(self.send_logs(_ocean, logs_to_send))
+ loop.close()
+
+ self.acquire()
+ logs = list(self._serialized_buffer)
+ if logs:
+ self.buffer.clear()
+ self._serialized_buffer.clear()
+ self.last_flush_time = time.time()
+ threading.Thread(target=_wrap_event_loop, args=(self.ocean, logs)).start()
+ self.release()
+
+ async def send_logs(
+ self, _ocean: Ocean, logs_to_send: list[dict[str, Any]]
+ ) -> None:
+ try:
+ await _ocean.port_client.ingest_integration_logs(logs_to_send)
+ except Exception as e:
+ logger.debug(f"Failed to send logs to Port with error: {e}")
diff --git a/port_ocean/log/logger_setup.py b/port_ocean/log/logger_setup.py
new file mode 100644
index 0000000000..0b171fd96f
--- /dev/null
+++ b/port_ocean/log/logger_setup.py
@@ -0,0 +1,67 @@
+import sys
+from logging import LogRecord
+from logging.handlers import QueueHandler, QueueListener
+from queue import Queue
+
+import loguru
+from loguru import logger
+
+from port_ocean.config.settings import LogLevelType
+from port_ocean.log.handlers import HTTPMemoryHandler
+from port_ocean.log.sensetive import sensitive_log_filter
+
+
+def setup_logger(level: LogLevelType, enable_http_handler: bool) -> None:
+ logger.remove()
+ _stdout_loguru_handler(level)
+ if enable_http_handler:
+ _http_loguru_handler(level)
+
+
+def _stdout_loguru_handler(level: LogLevelType) -> None:
+ logger_format = (
+ "{time:YYYY-MM-DD HH:mm:ss.SSS} | "
+ "{level: <8} | "
+ "{message}"
+ )
+ if level == "DEBUG":
+ logger_format += " | {extra}"
+
+ logger.add(
+ sys.stdout,
+ level=level.upper(),
+ format=logger_format,
+ enqueue=True, # process logs in background
+ diagnose=False, # hide variable values in log backtrace
+ filter=sensitive_log_filter.create_filter(),
+ )
+
+
+def _http_loguru_handler(level: LogLevelType) -> None:
+ queue: Queue[LogRecord] = Queue()
+
+ handler = QueueHandler(queue)
+
+ logger.add(
+ handler,
+ level=level.upper(),
+ format="{message}",
+ diagnose=False, # hide variable values in log backtrace
+ enqueue=True, # process logs in background
+ filter=sensitive_log_filter.create_filter(full_hide=True),
+ )
+
+ queue_listener = QueueListener(queue, HTTPMemoryHandler())
+ queue_listener.start()
+
+
+def exception_deserializer(record: "loguru.Record") -> None:
+ """
+ Workaround for when trying to log exception objects with loguru.
+ Loguru doesn't able to deserialize `Exception` subclasses.
+ https://github.com/Delgan/loguru/issues/504#issuecomment-917365972
+ """
+ exception: loguru.RecordException | None = record["exception"]
+ if exception is not None:
+ fixed = Exception(str(exception.value))
+ record["exception"] = exception._replace(value=fixed)
diff --git a/port_ocean/log/sensetive.py b/port_ocean/log/sensetive.py
new file mode 100644
index 0000000000..c3c56cb8e3
--- /dev/null
+++ b/port_ocean/log/sensetive.py
@@ -0,0 +1,51 @@
+import re
+from typing import Callable, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from loguru import Record
+
+# https://github.com/h33tlit/secret-regex-list
+secret_patterns = {
+ "Password in URL": r"[a-zA-Z]{3,10}:\/\/[^\/\\s:@]{3,20}:[^\/\\s:@]{3,20}@.{1,100}[\"'\\s]",
+ "Generic API Key": r"[a|A][p|P][i|I][_]?[k|K][e|E][y|Y].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
+ "Generic Secret": r"[s|S][e|E][c|C][r|R][e|E][t|T].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
+ "Google API Key": r"AIza[0-9A-Za-z\\-_]{35}",
+ "Firebase URL": r".*firebaseio\.com",
+ "RSA private key": r"-----BEGIN RSA PRIVATE KEY-----",
+ "SSH (DSA) private key": r"-----BEGIN DSA PRIVATE KEY-----",
+ "SSH (EC) private key": r"-----BEGIN EC PRIVATE KEY-----",
+ "PGP private key block": r"-----BEGIN PGP PRIVATE KEY BLOCK-----",
+ "Amazon AWS Access Key ID": r"AKIA[0-9A-Z]{16}",
+ "Amazon MWS Auth Token": r"amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
+ "AWS API Key": r"AKIA[0-9A-Z]{16}",
+ "GitHub": r"[g|G][i|I][t|T][h|H][u|U][b|B].*['|\"][0-9a-zA-Z]{35,40}['|\"]",
+ "Google Cloud Platform API Key": r"AIza[0-9A-Za-z\\-_]{35}",
+ "Google Cloud Platform OAuth": r"[0-9]+-[0-9A-Za-z_]{32}\\.apps\\.googleusercontent\\.com",
+ "Google (GCP) Service-account": r'"type": "service_account"',
+ "Google OAuth Access Token": r"ya29\\.[0-9A-Za-z\\-_]+",
+ "Connection String": r"[a-zA-Z]+:\/\/[^/\s]+:[^/\s]+@[^/\s]+\/[^/\s]+",
+}
+
+
+class SensitiveLogFilter:
+ compiled_patterns = [re.compile(pattern) for pattern in secret_patterns.values()]
+
+ def hide_sensitive_strings(self, *tokens: str) -> None:
+ self.compiled_patterns.extend([re.compile(token) for token in tokens])
+
+ def create_filter(self, full_hide: bool = False) -> Callable[["Record"], bool]:
+ def _filter(record: "Record") -> bool:
+ for pattern in self.compiled_patterns:
+ replace: Callable[[re.Match[str]], str] | str = (
+ "[REDACTED]"
+ if full_hide
+ else lambda match: match.group()[:6] + "[REDACTED]"
+ )
+ record["message"] = pattern.sub(replace, record["message"])
+
+ return True
+
+ return _filter
+
+
+sensitive_log_filter = SensitiveLogFilter()
diff --git a/port_ocean/logger_setup.py b/port_ocean/logger_setup.py
deleted file mode 100644
index 7b581e9089..0000000000
--- a/port_ocean/logger_setup.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import sys
-
-import loguru
-from loguru import logger
-
-from port_ocean.config.settings import LogLevelType
-
-
-def setup_logger(level: LogLevelType) -> None:
- logger_format = (
- "{time:YYYY-MM-DD HH:mm:ss.SSS} | "
- "{level: <8} | "
- "{message}"
- )
- if level == "DEBUG":
- logger_format += " | {extra}"
-
- logger.remove()
- logger.add(
- sys.stdout,
- level=level.upper(),
- format=logger_format,
- enqueue=True, # process logs in background
- diagnose=False, # hide variable values in log backtrace
- )
- logger.configure(patcher=exception_deserializer)
-
-
-def exception_deserializer(record: "loguru.Record") -> None:
- """
- Workaround for when trying to log exception objects with loguru.
- loguru doesn't able to deserialize `Exception` subclasses.
- https://github.com/Delgan/loguru/issues/504#issuecomment-917365972
- """
- exception: loguru.RecordException | None = record["exception"]
- if exception is not None:
- fixed = Exception(str(exception.value))
- record["exception"] = exception._replace(value=fixed)
diff --git a/port_ocean/middlewares.py b/port_ocean/middlewares.py
index 275ad33fd7..cf7c46a078 100644
--- a/port_ocean/middlewares.py
+++ b/port_ocean/middlewares.py
@@ -6,7 +6,7 @@
from port_ocean.exceptions.api import BaseAPIException, InternalServerException
from .context.event import event_context, EventType
from .context.ocean import ocean
-from .utils import get_time, generate_uuid
+from .utils.misc import get_time, generate_uuid
async def _handle_silently(
diff --git a/port_ocean/ocean.py b/port_ocean/ocean.py
index 3640b43f30..24dd4efedd 100644
--- a/port_ocean/ocean.py
+++ b/port_ocean/ocean.py
@@ -18,8 +18,10 @@
initialize_port_ocean_context,
)
from port_ocean.core.integrations.base import BaseIntegration
+from port_ocean.log.sensetive import sensitive_log_filter
from port_ocean.middlewares import request_handler
-from port_ocean.utils import repeat_every
+from port_ocean.utils.repeat import repeat_every
+from port_ocean.utils.signal import init_signal_handler
from port_ocean.version import __integration_version__
@@ -39,10 +41,18 @@ def __init__(
self.config = IntegrationConfiguration(
base_path="./", **(config_override or {})
)
+
if config_factory:
- self.config.integration.config = config_factory(
- **self.config.integration.config
- ).dict()
+ raw_config = (
+ self.config.integration.config
+ if isinstance(self.config.integration.config, dict)
+ else self.config.integration.config.dict()
+ )
+ self.config.integration.config = config_factory(**raw_config)
+ # add the integration sensitive configuration to the sensitive patterns to mask out
+ sensitive_log_filter.hide_sensitive_strings(
+ *self.config.get_sensitive_fields_data()
+ )
self.integration_router = integration_router or APIRouter()
self.port_client = PortClient(
@@ -61,7 +71,6 @@ async def _setup_scheduled_resync(
self,
) -> None:
def execute_resync_all() -> None:
- initialize_port_ocean_context(ocean_app=self)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -86,11 +95,12 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
@self.fast_api_app.on_event("startup")
async def startup() -> None:
+ init_signal_handler()
try:
await self.integration.start()
await self._setup_scheduled_resync()
- except Exception as e:
- logger.error(f"Failed to start integration with error: {e}")
+ except Exception:
+ logger.exception("Failed to start integration")
sys.exit("Server stopped")
await self.fast_api_app(scope, receive, send)
diff --git a/port_ocean/run.py b/port_ocean/run.py
index 24084a1348..bb3c7c218c 100644
--- a/port_ocean/run.py
+++ b/port_ocean/run.py
@@ -1,16 +1,16 @@
from inspect import getmembers
-from typing import Type, Dict, Any
-from pydantic import BaseModel
+from typing import Dict, Any, Type
import uvicorn
+from pydantic import BaseModel
from port_ocean.bootstrap import create_default_app
from port_ocean.config.dynamic import default_config_factory
from port_ocean.config.settings import ApplicationSettings, LogLevelType
from port_ocean.core.defaults.initialize import initialize_defaults
-from port_ocean.logger_setup import setup_logger
+from port_ocean.log.logger_setup import setup_logger
from port_ocean.ocean import Ocean
-from port_ocean.utils import get_spec_file, load_module
+from port_ocean.utils.misc import get_spec_file, load_module
def _get_default_config_factory() -> None | Type[BaseModel]:
@@ -31,7 +31,11 @@ def run(
) -> None:
application_settings = ApplicationSettings(log_level=log_level, port=port)
- setup_logger(application_settings.log_level)
+ setup_logger(
+ application_settings.log_level,
+ enable_http_handler=application_settings.enable_http_logging,
+ )
+
config_factory = _get_default_config_factory()
default_app = create_default_app(path, config_factory, config_override)
diff --git a/port_ocean/utils/__init__.py b/port_ocean/utils/__init__.py
new file mode 100644
index 0000000000..25c8de8f43
--- /dev/null
+++ b/port_ocean/utils/__init__.py
@@ -0,0 +1,3 @@
+from port_ocean.utils.async_http import http_async_client
+
+__all__ = ["http_async_client"]
diff --git a/port_ocean/utils/async_http.py b/port_ocean/utils/async_http.py
new file mode 100644
index 0000000000..a96c497a41
--- /dev/null
+++ b/port_ocean/utils/async_http.py
@@ -0,0 +1,29 @@
+import httpx
+from werkzeug.local import LocalStack, LocalProxy
+
+from port_ocean.context.ocean import ocean
+from port_ocean.helpers.async_client import OceanAsyncClient
+from port_ocean.helpers.retry import RetryTransport
+
+_http_client: LocalStack[httpx.AsyncClient] = LocalStack()
+
+
+def _get_http_client_context() -> httpx.AsyncClient:
+ client = _http_client.top
+ if client is None:
+ client = OceanAsyncClient(RetryTransport, timeout=ocean.config.client_timeout)
+ _http_client.push(client)
+
+ return client
+
+
+"""
+Utilize this client for all outbound integration requests to the third-party application. It functions as a wrapper
+around the httpx.AsyncClient, incorporating retry logic at the transport layer for handling retries on 5xx errors and
+connection errors.
+
+The client is instantiated lazily, only coming into existence upon its initial access. It should not be closed when in
+use, as it operates as a singleton shared across all events in the thread. It also takes care of recreating the client
+in scenarios such as the creation of a new event loop, such as when initiating a new thread.
+"""
+http_async_client: httpx.AsyncClient = LocalProxy(lambda: _get_http_client_context()) # type: ignore
diff --git a/port_ocean/utils/misc.py b/port_ocean/utils/misc.py
new file mode 100644
index 0000000000..c0999a1b76
--- /dev/null
+++ b/port_ocean/utils/misc.py
@@ -0,0 +1,56 @@
+import inspect
+from importlib.util import spec_from_file_location, module_from_spec
+from pathlib import Path
+from time import time
+from types import ModuleType
+from typing import Callable, Any
+from uuid import uuid4
+
+import tomli
+import yaml
+
+
+def get_time(seconds_precision: bool = True) -> float:
+ """Return current time as Unix/Epoch timestamp, in seconds.
+ :param seconds_precision: if True, return with seconds precision as integer (default).
+ If False, return with milliseconds precision as floating point number of seconds.
+ """
+ return time() if not seconds_precision else int(time())
+
+
+def generate_uuid() -> str:
+ """Return a UUID4 as string"""
+ return str(uuid4())
+
+
+def get_function_location(func: Callable[..., Any]) -> str:
+ file_path = inspect.getsourcefile(func)
+ line_number = inspect.getsourcelines(func)[1]
+ return f"{file_path}:{line_number}"
+
+
+def get_integration_version() -> str:
+ try:
+ with open("./pyproject.toml", "rb") as toml_file:
+ pyproject_data = tomli.load(toml_file)
+ return pyproject_data["tool"]["poetry"]["version"]
+ except (FileNotFoundError, KeyError):
+ return ""
+
+
+def get_spec_file(path: Path = Path(".")) -> dict[str, Any] | None:
+ try:
+ return yaml.safe_load((path / ".port/spec.yaml").read_text())
+ except FileNotFoundError:
+ return None
+
+
+def load_module(file_path: str) -> ModuleType:
+ spec = spec_from_file_location("module.name", file_path)
+ if spec is None or spec.loader is None:
+ raise Exception(f"Failed to load integration from path: {file_path}")
+
+ module = module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ return module
diff --git a/port_ocean/utils.py b/port_ocean/utils/repeat.py
similarity index 51%
rename from port_ocean/utils.py
rename to port_ocean/utils/repeat.py
index 5c64de70cf..587237e45b 100644
--- a/port_ocean/utils.py
+++ b/port_ocean/utils/repeat.py
@@ -1,95 +1,11 @@
import asyncio
-import inspect
from asyncio import ensure_future
from functools import wraps
-from importlib.util import module_from_spec, spec_from_file_location
-from pathlib import Path
-from time import time
from traceback import format_exception
-from types import ModuleType
-from typing import Callable, Any, Coroutine
-from uuid import uuid4
+from typing import Callable, Coroutine, Any
-import httpx
-import tomli
-import yaml
from loguru import logger
from starlette.concurrency import run_in_threadpool
-from werkzeug.local import LocalStack, LocalProxy
-
-from port_ocean.helpers.async_client import OceanAsyncClient
-from port_ocean.context.ocean import ocean
-from port_ocean.helpers.retry import RetryTransport
-
-_http_client: LocalStack[httpx.AsyncClient] = LocalStack()
-
-
-def _get_http_client_context() -> httpx.AsyncClient:
- client = _http_client.top
- if client is None:
- client = OceanAsyncClient(RetryTransport, timeout=ocean.config.client_timeout)
- _http_client.push(client)
-
- return client
-
-
-"""
-Utilize this client for all outbound integration requests to the third-party application. It functions as a wrapper
-around the httpx.AsyncClient, incorporating retry logic at the transport layer for handling retries on 5xx errors and
-connection errors.
-
-The client is instantiated lazily, only coming into existence upon its initial access. It should not be closed when in
-use, as it operates as a singleton shared across all events in the thread. It also takes care of recreating the client
-in scenarios such as the creation of a new event loop, such as when initiating a new thread.
-"""
-http_async_client: httpx.AsyncClient = LocalProxy(lambda: _get_http_client_context()) # type: ignore
-
-
-def get_time(seconds_precision: bool = True) -> float:
- """Return current time as Unix/Epoch timestamp, in seconds.
- :param seconds_precision: if True, return with seconds precision as integer (default).
- If False, return with milliseconds precision as floating point number of seconds.
- """
- return time() if not seconds_precision else int(time())
-
-
-def generate_uuid() -> str:
- """Return a UUID4 as string"""
- return str(uuid4())
-
-
-def get_function_location(func: Callable[..., Any]) -> str:
- file_path = inspect.getsourcefile(func)
- line_number = inspect.getsourcelines(func)[1]
- return f"{file_path}:{line_number}"
-
-
-def get_integration_version() -> str:
- try:
- with open("./pyproject.toml", "rb") as toml_file:
- pyproject_data = tomli.load(toml_file)
- return pyproject_data["tool"]["poetry"]["version"]
- except (FileNotFoundError, KeyError):
- return ""
-
-
-def get_spec_file(path: Path = Path(".")) -> dict[str, Any] | None:
- try:
- return yaml.safe_load((path / ".port/spec.yaml").read_text())
- except FileNotFoundError:
- return None
-
-
-def load_module(file_path: str) -> ModuleType:
- spec = spec_from_file_location("module.name", file_path)
- if spec is None or spec.loader is None:
- raise Exception(f"Failed to load integration from path: {file_path}")
-
- module = module_from_spec(spec)
- spec.loader.exec_module(module)
-
- return module
-
NoArgsNoReturnFuncT = Callable[[], None]
NoArgsNoReturnAsyncFuncT = Callable[[], Coroutine[Any, Any, None]]
diff --git a/port_ocean/utils/signal.py b/port_ocean/utils/signal.py
new file mode 100644
index 0000000000..ffab884903
--- /dev/null
+++ b/port_ocean/utils/signal.py
@@ -0,0 +1,54 @@
+import signal
+from typing import Callable, Any
+
+from werkzeug.local import LocalProxy, LocalStack
+
+from port_ocean.exceptions.utils import (
+ SignalHandlerNotInitialized,
+ SignalHandlerAlreadyInitialized,
+)
+from port_ocean.utils.misc import generate_uuid
+
+
+class SignalHandler:
+ def __init__(self) -> None:
+ self._handlers: dict[str, Callable[[], Any]] = {}
+ signal.signal(signal.SIGINT, lambda _, __: self._exit())
+ signal.signal(signal.SIGTERM, lambda _, __: self._exit())
+
+ def _exit(self) -> None:
+ """
+ Handles the exit signal.
+ """
+ while self._handlers:
+ _, handler = self._handlers.popitem()
+ handler()
+
+ def register(self, callback: Callable[[], Any]) -> str:
+ _id = generate_uuid()
+ self._handlers[_id] = callback
+
+ return _id
+
+ def unregister(self, _id: str) -> None:
+ del self._handlers[_id]
+
+
+_signal_handler: LocalStack[SignalHandler] = LocalStack()
+
+
+def _get_signal_handler() -> SignalHandler:
+ global _signal_handler
+ if _signal_handler.top is None:
+ raise SignalHandlerNotInitialized("Signal handler is not initialized")
+ return _signal_handler.top
+
+
+signal_handler: SignalHandler = LocalProxy(_get_signal_handler) # type: ignore
+
+
+def init_signal_handler() -> None:
+ global _signal_handler
+ if _signal_handler.top is not None:
+ raise SignalHandlerAlreadyInitialized("Signal handler is already initialized")
+ _signal_handler.push(SignalHandler())
diff --git a/port_ocean/version.py b/port_ocean/version.py
index 45ce1ad16d..182d840b53 100644
--- a/port_ocean/version.py
+++ b/port_ocean/version.py
@@ -1,6 +1,6 @@
from importlib.metadata import version
-from .utils import get_integration_version
+from .utils.misc import get_integration_version
__version__ = version("port-ocean")
__integration_version__ = get_integration_version()
diff --git a/pyproject.toml b/pyproject.toml
index bb9631c12c..16099ae9fc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "port-ocean"
-version = "0.4.17"
+version = "0.5.0"
description = "Port Ocean is a CLI tool for managing your Port projects."
readme = "README.md"
homepage = "https://app.getport.io"