From 9f85476528eacf2f6318050188310a7af5af9fe9 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 10 Apr 2025 17:21:46 +0200 Subject: [PATCH 01/44] Added first examples. --- 2025/anti/data_processing.py | 24 +++++ 2025/anti/exception_control_flow_after.py | 39 +++++++ 2025/anti/exception_control_flow_before.py | 40 ++++++++ 2025/anti/pyproject.toml | 8 ++ 2025/anti/static_methods_after.py | 23 +++++ 2025/anti/static_methods_before copy.py | 47 +++++++++ 2025/anti/uv.lock | 112 +++++++++++++++++++++ 7 files changed, 293 insertions(+) create mode 100644 2025/anti/data_processing.py create mode 100644 2025/anti/exception_control_flow_after.py create mode 100644 2025/anti/exception_control_flow_before.py create mode 100644 2025/anti/pyproject.toml create mode 100644 2025/anti/static_methods_after.py create mode 100644 2025/anti/static_methods_before copy.py create mode 100644 2025/anti/uv.lock diff --git a/2025/anti/data_processing.py b/2025/anti/data_processing.py new file mode 100644 index 0000000..b69f589 --- /dev/null +++ b/2025/anti/data_processing.py @@ -0,0 +1,24 @@ +import pandas as pd + + +def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame: + """Fill missing numeric values with the median of each column.""" + return df.fillna(df.median(numeric_only=True)) + + +def normalize_columns(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame: + """Min-max normalize specified columns in the DataFrame.""" + df = df.copy() + for col in columns: + min_val = df[col].min() + max_val = df[col].max() + if min_val == max_val: + df[col] = 0.0 # avoid division by zero + else: + df[col] = (df[col] - min_val) / (max_val - min_val) + return df + + +def encode_categorical(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame: + """Convert categorical columns into one-hot encoded columns.""" + return pd.get_dummies(df, columns=columns, drop_first=True) diff --git a/2025/anti/exception_control_flow_after.py b/2025/anti/exception_control_flow_after.py new file mode 100644 index 0000000..89c4665 --- /dev/null +++ b/2025/anti/exception_control_flow_after.py @@ -0,0 +1,39 @@ +import random +import time + + +# Simulated API call that randomly succeeds or times out +def fetch_from_primary_api(city: str) -> dict[str, str]: + if simulate_timeout(): + return {"status": "error", "message": "Primary API timed out."} + return {"status": "success", "data": f"Weather in {city} is sunny."} + + +def fetch_from_backup_api(city: str) -> dict[str, str]: + if simulate_timeout(): + return {"status": "error", "message": "Backup API timed out."} + return {"status": "success", "data": f"Backup: Weather in {city} is cloudy."} + + +def simulate_timeout() -> bool: + time.sleep(0.2) # network delay + return random.random() < 0.5 + + +# Good: using return values for expected control flow +def get_weather_forecast(city: str) -> dict[str, str]: + result = fetch_from_primary_api(city) + if result["status"] == "error": + result = fetch_from_backup_api(city) + if result["status"] == "error": + return {"status": "error", "message": "Both APIs timed out."} + return result + + +def main(): + result = get_weather_forecast("New York") + print(result) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/exception_control_flow_before.py b/2025/anti/exception_control_flow_before.py new file mode 100644 index 0000000..cafce78 --- /dev/null +++ b/2025/anti/exception_control_flow_before.py @@ -0,0 +1,40 @@ +import random +import time + + +# Simulated API call that randomly succeeds or times out +def fetch_from_primary_api(city: str) -> dict[str, str]: + if simulate_timeout(): + raise TimeoutError("Primary API timed out.") + return {"status": "success", "data": f"Weather in {city} is sunny."} + + +def fetch_from_backup_api(city: str) -> dict[str, str]: + if simulate_timeout(): + raise TimeoutError("Backup API timed out.") + return {"status": "success", "data": f"Backup: Weather in {city} is cloudy."} + + +def simulate_timeout() -> bool: + time.sleep(0.2) # network delay + return random.random() < 0.5 + + +# Bad: using exceptions for expected control flow +def get_weather_forecast(city: str) -> dict[str, str]: + try: + return fetch_from_primary_api(city) + except TimeoutError: + try: + return fetch_from_backup_api(city) + except TimeoutError: + return {"status": "error", "message": "Both APIs timed out."} + + +def main(): + result = get_weather_forecast("New York") + print(result) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/pyproject.toml b/2025/anti/pyproject.toml new file mode 100644 index 0000000..d1f592a --- /dev/null +++ b/2025/anti/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "anti-patterns" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "numpy>=2.2.4", + "pandas>=2.2.3", +] diff --git a/2025/anti/static_methods_after.py b/2025/anti/static_methods_after.py new file mode 100644 index 0000000..15195d7 --- /dev/null +++ b/2025/anti/static_methods_after.py @@ -0,0 +1,23 @@ +import numpy as np +import pandas as pd +from data_processing import encode_categorical, fill_missing_values, normalize_columns + + +def main() -> None: + df = pd.DataFrame( + { + "age": [25, 30, np.nan, 22], + "income": [50000, 60000, 55000, np.nan], + "gender": ["male", "female", "female", "male"], + } + ) + + df = fill_missing_values(df) + df = normalize_columns(df, ["age", "income"]) + df = encode_categorical(df, ["gender"]) + + print(df) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/static_methods_before copy.py b/2025/anti/static_methods_before copy.py new file mode 100644 index 0000000..d49f8fb --- /dev/null +++ b/2025/anti/static_methods_before copy.py @@ -0,0 +1,47 @@ +import numpy as np +import pandas as pd + + +class DataPreprocessingUtils: + @staticmethod + def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame: + """Fill missing numeric values with the median of each column.""" + return df.fillna(df.median(numeric_only=True)) + + @staticmethod + def normalize_columns(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame: + """Min-max normalize specified columns in the DataFrame.""" + df = df.copy() + for col in columns: + min_val = df[col].min() + max_val = df[col].max() + if min_val == max_val: + df[col] = 0.0 # avoid division by zero + else: + df[col] = (df[col] - min_val) / (max_val - min_val) + return df + + @staticmethod + def encode_categorical(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame: + """Convert categorical columns into one-hot encoded columns.""" + return pd.get_dummies(df, columns=columns, drop_first=True) + + +def main() -> None: + df = pd.DataFrame( + { + "age": [25, 30, np.nan, 22], + "income": [50000, 60000, 55000, np.nan], + "gender": ["male", "female", "female", "male"], + } + ) + + df = DataPreprocessingUtils.fill_missing_values(df) + df = DataPreprocessingUtils.normalize_columns(df, ["age", "income"]) + df = DataPreprocessingUtils.encode_categorical(df, ["gender"]) + + print(df) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/uv.lock b/2025/anti/uv.lock new file mode 100644 index 0000000..c7be281 --- /dev/null +++ b/2025/anti/uv.lock @@ -0,0 +1,112 @@ +version = 1 +revision = 1 +requires-python = ">=3.13" + +[[package]] +name = "anti-patterns" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "numpy" }, + { name = "pandas" }, +] + +[package.metadata] +requires-dist = [ + { name = "numpy", specifier = ">=2.2.4" }, + { name = "pandas", specifier = ">=2.2.3" }, +] + +[[package]] +name = "numpy" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] From bfca9cbadd95da78002f3493973f9334f411d440 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 17 Apr 2025 10:23:36 +0200 Subject: [PATCH 02/44] Added anti-pattern code examples. --- 2025/anti/abstraction.py | 98 ++++ 2025/anti/config.json | 5 + 2025/anti/decorator_inject_after.py | 17 + 2025/anti/decorator_inject_before.py | 30 ++ 2025/anti/hardcoded_after.py | 81 +++ 2025/anti/hardcoded_before.py | 39 ++ 2025/anti/inappropriate_intimacy.py | 66 +++ 2025/anti/overengineering_after.py | 59 +++ 2025/anti/overengineering_before.py | 69 +++ 2025/anti/overriding_new_after.py | 47 ++ 2025/anti/overriding_new_before.py | 30 ++ 2025/anti/pyproject.toml | 3 + ...efore copy.py => static_methods_before.py} | 0 2025/anti/uv.lock | 490 ++++++++++++++++++ 14 files changed, 1034 insertions(+) create mode 100644 2025/anti/abstraction.py create mode 100644 2025/anti/config.json create mode 100644 2025/anti/decorator_inject_after.py create mode 100644 2025/anti/decorator_inject_before.py create mode 100644 2025/anti/hardcoded_after.py create mode 100644 2025/anti/hardcoded_before.py create mode 100644 2025/anti/inappropriate_intimacy.py create mode 100644 2025/anti/overengineering_after.py create mode 100644 2025/anti/overengineering_before.py create mode 100644 2025/anti/overriding_new_after.py create mode 100644 2025/anti/overriding_new_before.py rename 2025/anti/{static_methods_before copy.py => static_methods_before.py} (100%) diff --git a/2025/anti/abstraction.py b/2025/anti/abstraction.py new file mode 100644 index 0000000..00e6736 --- /dev/null +++ b/2025/anti/abstraction.py @@ -0,0 +1,98 @@ +import json +from dataclasses import dataclass +from typing import Callable, Protocol + + +@dataclass +class Report: + title: str + content: str + + def to_csv(self) -> str: + return f"{self.title}, {self.content}\n" + + def to_json(self) -> str: + return json.dumps( + { + "title": self.title, + "content": self.content, + }, + indent=4, + ) + + +@dataclass +class Budget: + title: str + amount: float + + def to_csv(self) -> str: + return f"{self.title}, {self.amount}\n" + + def to_json(self) -> str: + return json.dumps( + { + "title": self.title, + "amount": self.amount, + }, + indent=4, + ) + + +type ExportFn = Callable[[str, Exportable], None] + + +class Exportable(Protocol): + def to_csv(self) -> str: ... + + def to_json(self) -> str: ... + + +def export_to_csv(filename: str, data: Exportable) -> None: + print("Exporting to CSV...") + with open(filename, "w") as f: + f.write(data.to_csv()) + print("Done.") + + +def export_to_json(filename: str, data: Exportable) -> None: + print("Exporting to JSON...") + with open(filename, "w") as f: + json.dump(data.to_json(), f) + print("Done.") + + +EXPORTERS = { + "csv": export_to_csv, + "json": export_to_json, +} + + +def get_exporter(format: str) -> ExportFn: + """Factory function to get the appropriate exporter function based on format string.""" + if format in EXPORTERS: + return EXPORTERS[format] + else: + raise ValueError(f"Unsupported export format: {format}") + + +def main() -> None: + report = Report( + title="Quarterly Earnings", + content="Here are the earnings for the last quarter...", + ) + + export_fn = get_exporter("json") + export_fn("report.json", report) + + budget = Budget( + title="Annual Budget", + amount=1000000.00, + ) + + export_fn = get_exporter("json") + export_fn("budget.json", budget) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/config.json b/2025/anti/config.json new file mode 100644 index 0000000..faff3b0 --- /dev/null +++ b/2025/anti/config.json @@ -0,0 +1,5 @@ +{ + "learning_rate": 0.01, + "batch_size": 32, + "epochs": 10 +} \ No newline at end of file diff --git a/2025/anti/decorator_inject_after.py b/2025/anti/decorator_inject_after.py new file mode 100644 index 0000000..cf4a453 --- /dev/null +++ b/2025/anti/decorator_inject_after.py @@ -0,0 +1,17 @@ +import json +from typing import Any + + +def load_config(file_name: str) -> dict[str, Any]: + """Load configuration from the specified file.""" + with open(file_name, "r") as f: + return json.load(f) + + +def main() -> None: + config = load_config("config.json") + print(f"Training with config: {config}") + + +if __name__ == "__main__": + main() diff --git a/2025/anti/decorator_inject_before.py b/2025/anti/decorator_inject_before.py new file mode 100644 index 0000000..787a751 --- /dev/null +++ b/2025/anti/decorator_inject_before.py @@ -0,0 +1,30 @@ +import json +from functools import wraps +from typing import Any, Callable + + +def load_config(file_name: str) -> dict[str, Any]: + """Load configuration from the specified file.""" + with open(file_name, "r") as f: + return json.load(f) + + +def inject_config(file_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + config: dict[str, Any] = load_config(file_name) + return func(config, *args, **kwargs) + + return wrapper + + return decorator + + +@inject_config("config.json") +def main(config: dict[str, Any]) -> None: + print(f"Training with config: {config}") + + +if __name__ == "__main__": + main() diff --git a/2025/anti/hardcoded_after.py b/2025/anti/hardcoded_after.py new file mode 100644 index 0000000..5ad55ea --- /dev/null +++ b/2025/anti/hardcoded_after.py @@ -0,0 +1,81 @@ +import io +import json +import os +import zipfile + +import lokalise +import numpy as np +import pandas as pd +import requests +import streamlit as st +from dotenv import load_dotenv + +load_dotenv() +LOKALISE_API_KEY = os.getenv("LOKALISE_API_KEY") +LOKALISE_PROJECT_ID = os.getenv("LOKALISE_PROJECT_ID") +LANGUAGE = "nl" + + +class LokaliseTranslator: + def __init__(self, api_key: str, project_id: str, language: str) -> None: + self.client = lokalise.Client(api_key) + self.project_id = project_id + self.language = language + self.translations = self.get_translations() + + def get_translations(self) -> dict[str, str]: + response = self.client.download_files( + self.project_id, + {"format": "json", "original_filenames": True, "replace_breaks": False}, + ) + translations_url = response["bundle_url"] + + # Download and extract the ZIP file + zip_response = requests.get(translations_url) + zip_file = zipfile.ZipFile(io.BytesIO(zip_response.content)) + + # Find the JSON file corresponding to the selected language + json_filename = f"{self.language}/no_filename.json" + with zip_file.open(json_filename) as json_file: + return json.load(json_file) + + def __call__(self, key: str) -> str: + return self.translations.get(key, key) + + +translator = LokaliseTranslator(LOKALISE_API_KEY, LOKALISE_PROJECT_ID, LANGUAGE) + +st.title(translator("dashboard_title")) + +DATE_COLUMN = "date/time" +DATA_URL = ( + "https://s3-us-west-2.amazonaws.com/streamlit-demo-data/uber-raw-data-sep14.csv.gz" +) + + +@st.cache_data +def load_data(nrows): + data = pd.read_csv(DATA_URL, nrows=nrows) + data.rename(lambda x: str(x).lower(), axis="columns", inplace=True) + data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) + return data + + +data_load_state = st.text(translator("loading_data")) +data = load_data(10000) +data_load_state.text(translator("done")) + +if st.checkbox(translator("show_raw_data")): + st.subheader(translator("raw_data")) + st.write(data) + +st.subheader(translator("nb_pickups_hour")) +hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0, 24))[0] +st.bar_chart(hist_values) + +# Some number in the range 0-23 +hour_to_filter = st.slider("hour", 0, 23, 17) +filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter] + +st.subheader(translator("map_all_pickups") % hour_to_filter) +st.map(filtered_data) diff --git a/2025/anti/hardcoded_before.py b/2025/anti/hardcoded_before.py new file mode 100644 index 0000000..7845ecc --- /dev/null +++ b/2025/anti/hardcoded_before.py @@ -0,0 +1,39 @@ +import numpy as np +import pandas as pd +import streamlit as st + +# Hardcoded constants +DATE_COLUMN = "date/time" +DATA_URL = ( + "https://s3-us-west-2.amazonaws.com/streamlit-demo-data/uber-raw-data-sep14.csv.gz" +) + + +@st.cache_data +def load_data(nrows: int) -> pd.DataFrame: + data = pd.read_csv(DATA_URL, nrows=nrows) + data.rename(lambda x: str(x).lower(), axis="columns", inplace=True) + data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) + return data + + +# 🧨 All UI strings are hardcoded — even small changes are tedious +st.title("Uber pickups in NYC") + +data_load_state = st.text("Loading data...") +data = load_data(10000) +data_load_state.text("Loading complete!") + +if st.checkbox("Show raw data"): + st.subheader("Raw data") + st.write(data) + +st.subheader("Number of pickups by hour") +hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0, 24))[0] +st.bar_chart(hist_values) + +hour_to_filter = st.slider("Hour", 0, 23, 17) +filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter] + +st.subheader(f"Map of all pickups at {hour_to_filter}:00") +st.map(filtered_data) diff --git a/2025/anti/inappropriate_intimacy.py b/2025/anti/inappropriate_intimacy.py new file mode 100644 index 0000000..207bd1a --- /dev/null +++ b/2025/anti/inappropriate_intimacy.py @@ -0,0 +1,66 @@ +import json +from dataclasses import dataclass +from typing import Callable + + +@dataclass +class Report: + title: str + content: str + + def to_csv(self) -> str: + return f"{self.title}, {self.content}\n" + + def to_json(self) -> str: + return json.dumps( + { + "title": self.title, + "content": self.content, + }, + indent=4, + ) + + +type ExportFn = Callable[[str, Report], None] + + +def export_to_csv(filename: str, report: Report) -> None: + print("Exporting to CSV...") + with open(filename, "w") as f: + f.write(report.to_csv()) + print("Done.") + + +def export_to_json(filename: str, report: Report) -> None: + print("Exporting to JSON...") + with open(filename, "w") as f: + json.dump(report.to_json(), f) + print("Done.") + + +EXPORTERS = { + "csv": export_to_csv, + "json": export_to_json, +} + + +def get_exporter(format: str) -> ExportFn: + """Factory function to get the appropriate exporter function based on format string.""" + if format in EXPORTERS: + return EXPORTERS[format] + else: + raise ValueError(f"Unsupported export format: {format}") + + +def main() -> None: + report = Report( + title="Quarterly Earnings", + content="Here are the earnings for the last quarter...", + ) + + export_fn = get_exporter("json") + export_fn("report.json", report) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/overengineering_after.py b/2025/anti/overengineering_after.py new file mode 100644 index 0000000..4cfb7ee --- /dev/null +++ b/2025/anti/overengineering_after.py @@ -0,0 +1,59 @@ +import json +from dataclasses import dataclass +from typing import Callable + + +@dataclass +class Report: + title: str + content: str + + +type ExportFn = Callable[[str, Report], None] + + +def export_to_csv(filename: str, report: Report) -> None: + print("Exporting to CSV...") + csv_data = f"{report.title}, {report.content}\n" + with open(filename, "w") as f: + f.write(csv_data) + print("Done.") + + +def export_to_json(filename: str, report: Report) -> None: + print("Exporting to JSON...") + json_data = { + "title": report.title, + "content": report.content, + } + with open(filename, "w") as f: + json.dump(json_data, f) + print("Done.") + + +EXPORTERS = { + "csv": export_to_csv, + "json": export_to_json, +} + + +def get_exporter(format: str) -> ExportFn: + """Factory function to get the appropriate exporter function based on format string.""" + if format in EXPORTERS: + return EXPORTERS[format] + else: + raise ValueError(f"Unsupported export format: {format}") + + +def main() -> None: + report = Report( + title="Quarterly Earnings", + content="Here are the earnings for the last quarter...", + ) + + export_fn = get_exporter("json") + export_fn("report.json", report) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/overengineering_before.py b/2025/anti/overengineering_before.py new file mode 100644 index 0000000..5770be1 --- /dev/null +++ b/2025/anti/overengineering_before.py @@ -0,0 +1,69 @@ +import json +from abc import ABC, abstractmethod +from dataclasses import dataclass + + +@dataclass +class Report: + title: str + content: str + + +class Exporter(ABC): + """Abstract base class for data exporters.""" + + @abstractmethod + def export(self, filename: str, report: Report) -> None: + pass + + +class CSVExporter(Exporter): + """Exporter for CSV format.""" + + def export(self, filename: str, report: Report) -> None: + csv_data = f"{report.title}, {report.content}\n" + print("Exporting to CSV...") + with open(filename, "w") as f: + f.write(csv_data) + print("Done.") + + +class JSONExporter(Exporter): + """Exporter for JSON format.""" + + def export(self, filename: str, report: Report) -> None: + print("Exporting to JSON...") + json_data = { + "title": report.title, + "content": report.content, + } + with open(filename, "w") as f: + json.dump(json_data, f) + print("Done.") + + +class ExporterFactory: + """Factory for creating exporters based on format string.""" + + @staticmethod + def get_exporter(format: str) -> Exporter: + if format == "csv": + return CSVExporter() + elif format == "json": + return JSONExporter() + else: + raise ValueError(f"Unsupported export format: {format}") + + +def main() -> None: + report = Report( + title="Quarterly Earnings", + content="Here are the earnings for the last quarter...", + ) + + exporter = ExporterFactory.get_exporter("json") + exporter.export("report.json", report) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/overriding_new_after.py b/2025/anti/overriding_new_after.py new file mode 100644 index 0000000..b11e910 --- /dev/null +++ b/2025/anti/overriding_new_after.py @@ -0,0 +1,47 @@ +from enum import StrEnum +from typing import Callable + + +class PaymentMethod(StrEnum): + PAYPAL = "paypal" + CARD = "card" + + +type PaymentFn = Callable[[int], None] + + +def pay_paypal(amount: int) -> None: + print(f"Paying {amount} using Paypal") + + +def pay_stripe(amount: int) -> None: + print(f"Paying {amount} using Stripe") + + +def get_payment_method(payment_type: str) -> PaymentFn: + if payment_type == PaymentMethod.PAYPAL: + return pay_paypal + elif payment_type == PaymentMethod.CARD: + return pay_stripe + else: + raise ValueError(f"Unsupported payment type: {payment_type}") + + +PAYMENT_METHODS: dict[PaymentMethod, PaymentFn] = { + PaymentMethod.CARD: pay_stripe, + PaymentMethod.PAYPAL: pay_paypal, +} + + +def main(): + # using the dictionary + my_payment_fn = PAYMENT_METHODS[PaymentMethod.PAYPAL] + my_payment_fn(100) + + # using the function + my_payment_fn = get_payment_method("card") + my_payment_fn(100) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/overriding_new_before.py b/2025/anti/overriding_new_before.py new file mode 100644 index 0000000..e63ba6b --- /dev/null +++ b/2025/anti/overriding_new_before.py @@ -0,0 +1,30 @@ +class Payment: + def __new__(cls, payment_type: str): + if payment_type == "paypal": + return object.__new__(PaypalPayment) + elif payment_type == "card": + return object.__new__(StripePayment) + else: + raise ValueError(f"Unsupported payment type: {payment_type}") + + def pay(self, amount: int) -> None: + raise NotImplementedError + + +class PaypalPayment(Payment): + def pay(self, amount: int) -> None: + print(f"Paying {amount} using Paypal") + + +class StripePayment(Payment): + def pay(self, amount: int) -> None: + print(f"Paying {amount} using Stripe") + + +def main() -> None: + my_payment = Payment("card") + my_payment.pay(100) + + +if __name__ == "__main__": + main() diff --git a/2025/anti/pyproject.toml b/2025/anti/pyproject.toml index d1f592a..e53b298 100644 --- a/2025/anti/pyproject.toml +++ b/2025/anti/pyproject.toml @@ -5,4 +5,7 @@ requires-python = ">=3.13" dependencies = [ "numpy>=2.2.4", "pandas>=2.2.3", + "python-dotenv>=1.1.0", + "python-lokalise-api>=3.2.0", + "streamlit>=1.44.1", ] diff --git a/2025/anti/static_methods_before copy.py b/2025/anti/static_methods_before.py similarity index 100% rename from 2025/anti/static_methods_before copy.py rename to 2025/anti/static_methods_before.py diff --git a/2025/anti/uv.lock b/2025/anti/uv.lock index c7be281..76357cf 100644 --- a/2025/anti/uv.lock +++ b/2025/anti/uv.lock @@ -2,6 +2,22 @@ version = 1 revision = 1 requires-python = ">=3.13" +[[package]] +name = "altair" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.14'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200 }, +] + [[package]] name = "anti-patterns" version = "0.1.0" @@ -9,12 +25,206 @@ source = { virtual = "." } dependencies = [ { name = "numpy" }, { name = "pandas" }, + { name = "python-dotenv" }, + { name = "python-lokalise-api" }, + { name = "streamlit" }, ] [package.metadata] requires-dist = [ { name = "numpy", specifier = ">=2.2.4" }, { name = "pandas", specifier = ">=2.2.3" }, + { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "python-lokalise-api", specifier = ">=3.2.0" }, + { name = "streamlit", specifier = ">=1.44.1" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "narwhals" +version = "1.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/6a/a98fa5e9d530a428a0cd79d27f059ed65efd3a07aad61a8c93e323c9c20b/narwhals-1.35.0.tar.gz", hash = "sha256:07477d18487fbc940243b69818a177ed7119b737910a8a254fb67688b48a7c96", size = 265784 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/b3/5781eb874f04cb1e882a7d93cf30abcb00362a3205c5f3708a7434a1a2ac/narwhals-1.35.0-py3-none-any.whl", hash = "sha256:7562af132fa3f8aaaf34dc96d7ec95bdca29d1c795e8fcf14e01edf1d32122bc", size = 325708 }, ] [[package]] @@ -45,6 +255,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, ] +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + [[package]] name = "pandas" version = "2.2.3" @@ -72,6 +291,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, ] +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, +] + +[[package]] +name = "protobuf" +version = "5.29.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/7d/b9dca7365f0e2c4fa7c193ff795427cfa6290147e5185ab11ece280a18e7/protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99", size = 424902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/b2/043a1a1a20edd134563699b0e91862726a0dc9146c090743b6c44d798e75/protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7", size = 422709 }, + { url = "https://files.pythonhosted.org/packages/79/fc/2474b59570daa818de6124c0a15741ee3e5d6302e9d6ce0bdfd12e98119f/protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d", size = 434506 }, + { url = "https://files.pythonhosted.org/packages/46/de/7c126bbb06aa0f8a7b38aaf8bd746c514d70e6a2a3f6dd460b3b7aad7aae/protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0", size = 417826 }, + { url = "https://files.pythonhosted.org/packages/a2/b5/bade14ae31ba871a139aa45e7a8183d869efe87c34a4850c87b936963261/protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e", size = 319574 }, + { url = "https://files.pythonhosted.org/packages/46/88/b01ed2291aae68b708f7d334288ad5fb3e7aa769a9c309c91a0d55cb91b0/protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922", size = 319672 }, + { url = "https://files.pythonhosted.org/packages/12/fb/a586e0c973c95502e054ac5f81f88394f24ccc7982dac19c515acd9e2c93/protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862", size = 172551 }, +] + +[[package]] +name = "pyarrow" +version = "19.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/09/a9046344212690f0632b9c709f9bf18506522feb333c894d0de81d62341a/pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e", size = 1129437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/8d/275c58d4b00781bd36579501a259eacc5c6dfb369be4ddeb672ceb551d2d/pyarrow-19.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c", size = 30653552 }, + { url = "https://files.pythonhosted.org/packages/a0/9e/e6aca5cc4ef0c7aec5f8db93feb0bde08dbad8c56b9014216205d271101b/pyarrow-19.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae", size = 32103413 }, + { url = "https://files.pythonhosted.org/packages/6a/fa/a7033f66e5d4f1308c7eb0dfcd2ccd70f881724eb6fd1776657fdf65458f/pyarrow-19.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4", size = 41134869 }, + { url = "https://files.pythonhosted.org/packages/2d/92/34d2569be8e7abdc9d145c98dc410db0071ac579b92ebc30da35f500d630/pyarrow-19.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2", size = 42192626 }, + { url = "https://files.pythonhosted.org/packages/0a/1f/80c617b1084fc833804dc3309aa9d8daacd46f9ec8d736df733f15aebe2c/pyarrow-19.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6", size = 40496708 }, + { url = "https://files.pythonhosted.org/packages/e6/90/83698fcecf939a611c8d9a78e38e7fed7792dcc4317e29e72cf8135526fb/pyarrow-19.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136", size = 42075728 }, + { url = "https://files.pythonhosted.org/packages/40/49/2325f5c9e7a1c125c01ba0c509d400b152c972a47958768e4e35e04d13d8/pyarrow-19.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef", size = 25242568 }, + { url = "https://files.pythonhosted.org/packages/3f/72/135088d995a759d4d916ec4824cb19e066585b4909ebad4ab196177aa825/pyarrow-19.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0", size = 30702371 }, + { url = "https://files.pythonhosted.org/packages/2e/01/00beeebd33d6bac701f20816a29d2018eba463616bbc07397fdf99ac4ce3/pyarrow-19.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9", size = 32116046 }, + { url = "https://files.pythonhosted.org/packages/1f/c9/23b1ea718dfe967cbd986d16cf2a31fe59d015874258baae16d7ea0ccabc/pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3", size = 41091183 }, + { url = "https://files.pythonhosted.org/packages/3a/d4/b4a3aa781a2c715520aa8ab4fe2e7fa49d33a1d4e71c8fc6ab7b5de7a3f8/pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6", size = 42171896 }, + { url = "https://files.pythonhosted.org/packages/23/1b/716d4cd5a3cbc387c6e6745d2704c4b46654ba2668260d25c402626c5ddb/pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a", size = 40464851 }, + { url = "https://files.pythonhosted.org/packages/ed/bd/54907846383dcc7ee28772d7e646f6c34276a17da740002a5cefe90f04f7/pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8", size = 42085744 }, +] + +[[package]] +name = "pydeck" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -84,6 +381,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, +] + +[[package]] +name = "python-lokalise-api" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/38/bc1b7d47068b0de8ed115628ea9a5b097146521f0ef021782c3dacf8c06c/python_lokalise_api-3.2.0.tar.gz", hash = "sha256:e68b5df24aee49688ca4bfa3fcd47f5aff8cb6604bd0ab2fee7c384380aeb61f", size = 24647 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2e/4dbbb5e7a6148c5c81942e968154897e5289a99d293c716b78065e362c4c/python_lokalise_api-3.2.0-py3-none-any.whl", hash = "sha256:0227414b7e7fcff14b05a29ea3d612a99a117fc71d2452792df5903e3cc55404", size = 66771 }, +] + [[package]] name = "pytz" version = "2025.2" @@ -93,6 +411,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, ] +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rpds-py" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072 }, + { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919 }, + { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360 }, + { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704 }, + { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494 }, + { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185 }, + { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168 }, + { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622 }, + { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435 }, + { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762 }, + { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510 }, + { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075 }, + { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974 }, + { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730 }, + { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627 }, + { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094 }, + { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639 }, + { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584 }, + { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047 }, + { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085 }, + { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498 }, + { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202 }, + { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771 }, + { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195 }, + { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354 }, +] + [[package]] name = "six" version = "1.17.0" @@ -102,6 +482,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[package]] +name = "streamlit" +version = "1.44.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altair" }, + { name = "blinker" }, + { name = "cachetools" }, + { name = "click" }, + { name = "gitpython" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "pyarrow" }, + { name = "pydeck" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "toml" }, + { name = "tornado" }, + { name = "typing-extensions" }, + { name = "watchdog", marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/c0/7286284567e5045f0c587c426d0c41aee5d10c0a2e360e627a83037e9f0c/streamlit-1.44.1.tar.gz", hash = "sha256:c6914ed6d5b76870b461510476806db370f36425ae0e6654d227c988288198d3", size = 9423685 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/17/fc425e1d4d86e31b2aaf0812a2ef2163763a0670d671720c7c36e8679323/streamlit-1.44.1-py3-none-any.whl", hash = "sha256:9fe355f58b11f4eb71e74f115ce1f38c4c9eaff2733e6bcffb510ac1298a5990", size = 9812242 }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -110,3 +573,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be76%20wheels%20=%20[%20%20%20%20%20%7B%20url%20="https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] From 1a2bc4253f006ec26b62c1b80358fcac84c3fcfe Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 25 Apr 2025 09:22:38 +0200 Subject: [PATCH 03/44] Added a few examples --- 2025/map/basic_loop.py | 26 ++++++++++++++++++++++++++ 2025/map/performance.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 2025/map/basic_loop.py create mode 100644 2025/map/performance.py diff --git a/2025/map/basic_loop.py b/2025/map/basic_loop.py new file mode 100644 index 0000000..9d7dd83 --- /dev/null +++ b/2025/map/basic_loop.py @@ -0,0 +1,26 @@ +from typing import TypedDict + + +class User(TypedDict): + name: str + age: int + + +def main() -> None: + users: list[User] = [ + {"name": "Alice", "age": 28}, + {"name": "Bob", "age": 17}, + {"name": "Carol", "age": 35}, + ] + + adult_names: list[str] = [] + + for user in users: + if user["age"] >= 18: + adult_names.append(user["name"].upper()) + + print(adult_names) + + +if __name__ == "__main__": + main() diff --git a/2025/map/performance.py b/2025/map/performance.py new file mode 100644 index 0000000..2cd2f9d --- /dev/null +++ b/2025/map/performance.py @@ -0,0 +1,41 @@ +import time +from typing import TypedDict + + +# Setup +class User(TypedDict): + name: str + age: int + + +def main() -> None: + users: list[User] = [ + {"name": "User" + str(i), "age": i % 50} + for i in range(10_000_000) # Increased size + ] + + # For loop + start: float = time.perf_counter() + adult_names: list[str] = [] + for user in users: + if user["age"] >= 18: + adult_names.append(user["name"].upper()) + end: float = time.perf_counter() + print("For loop:", end - start) + + # Map/filter + start = time.perf_counter() + adult_users = filter(lambda u: u["age"] >= 18, users) + adult_names = list(map(lambda u: u["name"].upper(), adult_users)) + end = time.perf_counter() + print("Map/filter:", end - start) + + # List comprehension + start = time.perf_counter() + adult_names = [user["name"].upper() for user in users if user["age"] >= 18] + end = time.perf_counter() + print("List comp:", end - start) + + +if __name__ == "__main__": + main() From 3bb5896525b8083449b0b0195d6ac6738884f04f Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 25 Apr 2025 17:08:30 +0200 Subject: [PATCH 04/44] Expanded map examples. --- 2025/map/{basic_loop.py => basic_example.py} | 0 2025/map/basic_example_comp.py | 26 ++++++++++++++++ 2025/map/basic_example_map.py | 23 +++++++++++++++ 2025/map/{performance.py => benchmark.py} | 4 +-- 2025/map/complex_logic.py | 31 ++++++++++++++++++++ 2025/map/complex_logic_comp.py | 20 +++++++++++++ 2025/map/complex_logic_map.py | 25 ++++++++++++++++ 2025/map/early_exit.py | 25 ++++++++++++++++ 2025/map/early_exit_filter.py | 24 +++++++++++++++ 2025/map/exception_handling.py | 19 ++++++++++++ 2025/map/exception_handling_map.py | 19 ++++++++++++ 2025/map/side_effects.py | 20 +++++++++++++ 2025/map/side_effects_map.py | 22 ++++++++++++++ warnings.txt | 3 ++ 14 files changed, 259 insertions(+), 2 deletions(-) rename 2025/map/{basic_loop.py => basic_example.py} (100%) create mode 100644 2025/map/basic_example_comp.py create mode 100644 2025/map/basic_example_map.py rename 2025/map/{performance.py => benchmark.py} (92%) create mode 100644 2025/map/complex_logic.py create mode 100644 2025/map/complex_logic_comp.py create mode 100644 2025/map/complex_logic_map.py create mode 100644 2025/map/early_exit.py create mode 100644 2025/map/early_exit_filter.py create mode 100644 2025/map/exception_handling.py create mode 100644 2025/map/exception_handling_map.py create mode 100644 2025/map/side_effects.py create mode 100644 2025/map/side_effects_map.py create mode 100644 warnings.txt diff --git a/2025/map/basic_loop.py b/2025/map/basic_example.py similarity index 100% rename from 2025/map/basic_loop.py rename to 2025/map/basic_example.py diff --git a/2025/map/basic_example_comp.py b/2025/map/basic_example_comp.py new file mode 100644 index 0000000..60569fa --- /dev/null +++ b/2025/map/basic_example_comp.py @@ -0,0 +1,26 @@ +from typing import TypedDict + + +class User(TypedDict): + name: str + age: int + + +def main() -> None: + users: list[User] = [ + {"name": "Alice", "age": 28}, + {"name": "Bob", "age": 17}, + {"name": "Carol", "age": 35}, + ] + + adult_names: list[str] = [ + + for user in users: + if user["age"] >= 18: + adult_names.append(user["name"].upper()) + + print(adult_names) + + +if __name__ == "__main__": + main() diff --git a/2025/map/basic_example_map.py b/2025/map/basic_example_map.py new file mode 100644 index 0000000..5e792a6 --- /dev/null +++ b/2025/map/basic_example_map.py @@ -0,0 +1,23 @@ +from typing import TypedDict + + +class User(TypedDict): + name: str + age: int + + +def main() -> None: + users: list[User] = [ + {"name": "Alice", "age": 28}, + {"name": "Bob", "age": 17}, + {"name": "Carol", "age": 35}, + ] + + adult_users = filter(lambda u: u["age"] >= 18, users) + adult_names = list(map(lambda u: u["name"].upper(), adult_users)) + + print(adult_names) + + +if __name__ == "__main__": + main() diff --git a/2025/map/performance.py b/2025/map/benchmark.py similarity index 92% rename from 2025/map/performance.py rename to 2025/map/benchmark.py index 2cd2f9d..2877ed7 100644 --- a/2025/map/performance.py +++ b/2025/map/benchmark.py @@ -15,12 +15,12 @@ def main() -> None: ] # For loop - start: float = time.perf_counter() + start = time.perf_counter() adult_names: list[str] = [] for user in users: if user["age"] >= 18: adult_names.append(user["name"].upper()) - end: float = time.perf_counter() + end = time.perf_counter() print("For loop:", end - start) # Map/filter diff --git a/2025/map/complex_logic.py b/2025/map/complex_logic.py new file mode 100644 index 0000000..6124aef --- /dev/null +++ b/2025/map/complex_logic.py @@ -0,0 +1,31 @@ +from typing import Any + + +def transform(data: list[dict[str, Any]]) -> list[int]: + result: list[int] = [] + for item in data: + if item["flag"]: + try: + val = item["a"]["b"].get("c", {}).get("d", 0) + result.append(val + 5) + except KeyError: + result.append(-1) + else: + result.append(-1) + return result + + +def main() -> None: + data: list[dict[str, Any]] = [ + {"flag": True, "a": {"b": {"c": {"d": 1}}}}, + {"flag": False, "a": {"b": {"c": {"d": 2}}}}, + {"flag": True, "a": {"b": {"c": {}}}}, + {"flag": True, "a": {"b": {}}}, + ] + + result = transform(data) + print(result) # Output: [6, -1, 5, 5] + + +if __name__ == "__main__": + main() diff --git a/2025/map/complex_logic_comp.py b/2025/map/complex_logic_comp.py new file mode 100644 index 0000000..3fc9fc0 --- /dev/null +++ b/2025/map/complex_logic_comp.py @@ -0,0 +1,20 @@ +from typing import Any + + +def transform(data: list[dict[str, Any]]) -> list[int]: + return [x["a"]["b"].get("c", {}).get("d", 0) + 5 if x["flag"] else -1 for x in data] + + +def main() -> None: + data: list[dict[str, Any]] = [ + {"flag": True, "a": {"b": {"c": {"d": 1}}}}, + {"flag": False, "a": {"b": {"c": {"d": 2}}}}, + {"flag": True, "a": {"b": {"c": {}}}}, + {"flag": True, "a": {"b": {}}}, + ] + result = transform(data) + print(result) # Output: [6, -1, 5, 5] + + +if __name__ == "__main__": + main() diff --git a/2025/map/complex_logic_map.py b/2025/map/complex_logic_map.py new file mode 100644 index 0000000..b02dd59 --- /dev/null +++ b/2025/map/complex_logic_map.py @@ -0,0 +1,25 @@ +from typing import Any + + +def transform(data: list[dict[str, Any]]) -> list[int]: + return list( + map( + lambda x: x["a"]["b"].get("c", {}).get("d", 0) + 5 if x["flag"] else -1, + data, + ) + ) + + +def main() -> None: + data: list[dict[str, Any]] = [ + {"flag": True, "a": {"b": {"c": {"d": 1}}}}, + {"flag": False, "a": {"b": {"c": {"d": 2}}}}, + {"flag": True, "a": {"b": {"c": {}}}}, + {"flag": True, "a": {"b": {}}}, + ] + result = transform(data) + print(result) # Output: [6, -1, 5, 5] + + +if __name__ == "__main__": + main() diff --git a/2025/map/early_exit.py b/2025/map/early_exit.py new file mode 100644 index 0000000..10a36ea --- /dev/null +++ b/2025/map/early_exit.py @@ -0,0 +1,25 @@ +def scan_for_suspicious(events: list[dict[str, str]]) -> None: + for event in events: + if is_suspicious(event): + print(f"Suspicious login detected: {event}") + break + + +def is_suspicious(event: dict[str, str]) -> bool: + return ( + "login" in event.get("type", "").lower() + and "unusual" in event.get("details", "").lower() + ) + + +def main() -> None: + events: list[dict[str, str]] = [ + {"type": "login", "details": "User logged in"}, + {"type": "login", "details": "Unusual login from new device"}, + {"type": "logout", "details": "User logged out"}, + ] + scan_for_suspicious(events) + + +if __name__ == "__main__": + main() diff --git a/2025/map/early_exit_filter.py b/2025/map/early_exit_filter.py new file mode 100644 index 0000000..dd1a694 --- /dev/null +++ b/2025/map/early_exit_filter.py @@ -0,0 +1,24 @@ +def scan_for_suspicious(events: list[dict[str, str]]) -> None: + event = next(filter(is_suspicious, events), None) + if event: + print(f"Suspicious login detected: {event}") + + +def is_suspicious(event: dict[str, str]) -> bool: + return ( + "login" in event.get("type", "").lower() + and "unusual" in event.get("details", "").lower() + ) + + +def main() -> None: + events: list[dict[str, str]] = [ + {"type": "login", "details": "User logged in"}, + {"type": "login", "details": "Unusual login from new device"}, + {"type": "logout", "details": "User logged out"}, + ] + scan_for_suspicious(events) + + +if __name__ == "__main__": + main() diff --git a/2025/map/exception_handling.py b/2025/map/exception_handling.py new file mode 100644 index 0000000..da6594b --- /dev/null +++ b/2025/map/exception_handling.py @@ -0,0 +1,19 @@ +def parse_numbers(user_inputs: list[str]) -> list[int]: + valid_numbers: list[int] = [] + for value in user_inputs: + try: + number = int(value) + valid_numbers.append(number) + except ValueError: + print(f"Invalid input skipped: {value}") + return valid_numbers + + +def main() -> None: + user_inputs: list[str] = ["10", "20", "invalid", "30"] + valid_numbers = parse_numbers(user_inputs) + print("Valid numbers:", valid_numbers) + + +if __name__ == "__main__": + main() diff --git a/2025/map/exception_handling_map.py b/2025/map/exception_handling_map.py new file mode 100644 index 0000000..82bbcdf --- /dev/null +++ b/2025/map/exception_handling_map.py @@ -0,0 +1,19 @@ +def parse_numbers(user_inputs: list[str]) -> list[int]: + def safe_convert(value: str) -> int | None: + try: + return int(value) + except ValueError: + print(f"Invalid input skipped: {value}") + return None + + return list(filter(lambda x: x is not None, map(safe_convert, user_inputs))) + + +def main() -> None: + user_inputs: list[str] = ["10", "20", "invalid", "30"] + valid_numbers = parse_numbers(user_inputs) + print("Valid numbers:", valid_numbers) + + +if __name__ == "__main__": + main() diff --git a/2025/map/side_effects.py b/2025/map/side_effects.py new file mode 100644 index 0000000..fd22500 --- /dev/null +++ b/2025/map/side_effects.py @@ -0,0 +1,20 @@ +def write_warnings(log_entries: list[dict[str, str]]) -> None: + with open("warnings.txt", "w") as f: + for entry in log_entries: + timestamped = f"[{entry['timestamp']}] {entry['message']}" + f.write(timestamped + "\n") + if "WARNING" in entry["message"]: + print("WARNING:", timestamped) + + +def main() -> None: + log_entries: list[dict[str, str]] = [ + {"timestamp": "2025-04-01 12:00", "message": "INFO: System started"}, + {"timestamp": "2025-04-01 12:05", "message": "WARNING: Low disk space"}, + {"timestamp": "2025-04-01 12:10", "message": "ERROR: Disk failure"}, + ] + write_warnings(log_entries) + + +if __name__ == "__main__": + main() diff --git a/2025/map/side_effects_map.py b/2025/map/side_effects_map.py new file mode 100644 index 0000000..09b6666 --- /dev/null +++ b/2025/map/side_effects_map.py @@ -0,0 +1,22 @@ +def write_warnings(log_entries: list[dict[str, str]]) -> None: + def process(entry: dict[str, str]) -> None: + timestamped = f"[{entry['timestamp']}] {entry['message']}" + with open("warnings.txt", "a") as f: + f.write(timestamped + "\n") + if "WARNING" in entry["message"]: + print("WARNING:", timestamped) + + list(map(process, log_entries)) + + +def main() -> None: + log_entries: list[dict[str, str]] = [ + {"timestamp": "2025-04-01 12:00", "message": "INFO: System started"}, + {"timestamp": "2025-04-01 12:05", "message": "WARNING: Low disk space"}, + {"timestamp": "2025-04-01 12:10", "message": "ERROR: Disk failure"}, + ] + write_warnings(log_entries) + + +if __name__ == "__main__": + main() diff --git a/warnings.txt b/warnings.txt new file mode 100644 index 0000000..13d0c7d --- /dev/null +++ b/warnings.txt @@ -0,0 +1,3 @@ +[2023-10-01 12:00] INFO: System started +[2023-10-01 12:05] WARNING: Low disk space +[2023-10-01 12:10] ERROR: Disk failure From a820b4a2edd85d637fd8c7cf7308e65e65b3d9a3 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Mon, 28 Apr 2025 12:54:02 +0200 Subject: [PATCH 05/44] Added new class example --- 2025/functions/new_class_type.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 2025/functions/new_class_type.py diff --git a/2025/functions/new_class_type.py b/2025/functions/new_class_type.py new file mode 100644 index 0000000..dd10133 --- /dev/null +++ b/2025/functions/new_class_type.py @@ -0,0 +1,11 @@ +import types + +# Dynamically create a class with a __call__ method +DynamicFunction = types.new_class( + "DynamicFunction", + (), + {}, + exec_body=lambda ns: ns.update({"__call__": lambda self, x: x * 2}), +) +double = DynamicFunction() +print(double(5)) # 10 From 9f30c514f58a2fc9b57c5c592741084e8bafb129 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 29 Apr 2025 14:46:54 +0200 Subject: [PATCH 06/44] Added function examples --- 2025/functions/1_lambda.py | 7 +++ 2025/functions/2_partial.py | 15 ++++++ 2025/functions/3_decorator.py | 30 ++++++++++++ 2025/functions/4_call_dunder.py | 12 +++++ 2025/functions/5_exec.py | 8 ++++ 2025/functions/6_eval.py | 7 +++ ...{new_class_type.py => 7_new_class_type.py} | 0 2025/functions/8_code_type.py | 47 +++++++++++++++++++ 2025/functions/pyproject.toml | 6 +++ 2025/functions/uv.lock | 8 ++++ 10 files changed, 140 insertions(+) create mode 100644 2025/functions/1_lambda.py create mode 100644 2025/functions/2_partial.py create mode 100644 2025/functions/3_decorator.py create mode 100644 2025/functions/4_call_dunder.py create mode 100644 2025/functions/5_exec.py create mode 100644 2025/functions/6_eval.py rename 2025/functions/{new_class_type.py => 7_new_class_type.py} (100%) create mode 100644 2025/functions/8_code_type.py create mode 100644 2025/functions/pyproject.toml create mode 100644 2025/functions/uv.lock diff --git a/2025/functions/1_lambda.py b/2025/functions/1_lambda.py new file mode 100644 index 0000000..785c236 --- /dev/null +++ b/2025/functions/1_lambda.py @@ -0,0 +1,7 @@ +def main() -> None: + greet = lambda name: f"Hello, {name}!" + print(greet("Alice")) # "Hello, Alice!" + + +if __name__ == "__main__": + main() diff --git a/2025/functions/2_partial.py b/2025/functions/2_partial.py new file mode 100644 index 0000000..f3338be --- /dev/null +++ b/2025/functions/2_partial.py @@ -0,0 +1,15 @@ +from functools import partial + + +def power(base: float, exponent: float) -> float: + return base**exponent + + +def main() -> None: + # Create a partial function that always raises to the power of 2 + square = partial(power, exponent=2) + print(square(4)) # 16 + + +if __name__ == "__main__": + main() diff --git a/2025/functions/3_decorator.py b/2025/functions/3_decorator.py new file mode 100644 index 0000000..72588c0 --- /dev/null +++ b/2025/functions/3_decorator.py @@ -0,0 +1,30 @@ +from functools import wraps +from typing import Any, Callable + + +def print_result( + fmt: str = "Result: {}", +) -> Callable[[Callable[[Any], Any]], Callable[[Any], Any]]: + def decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]: + @wraps(func) + def wrapper(x: Any) -> Any: + result = func(x) + print(fmt.format(result)) + return result + + return wrapper + + return decorator + + +@print_result(fmt="Computed value => {}") +def double(x: int) -> int: + return x * 2 + + +def main() -> None: + double(5) + + +if __name__ == "__main__": + main() diff --git a/2025/functions/4_call_dunder.py b/2025/functions/4_call_dunder.py new file mode 100644 index 0000000..15e88b4 --- /dev/null +++ b/2025/functions/4_call_dunder.py @@ -0,0 +1,12 @@ +class Greeter: + def __call__(self, name: str) -> str: + return f"Hello, {name}!" + + +def main() -> None: + greet = Greeter() + print(greet("Alice")) # Output: "Hello, Alice!" + + +if __name__ == "__main__": + main() diff --git a/2025/functions/5_exec.py b/2025/functions/5_exec.py new file mode 100644 index 0000000..f603d17 --- /dev/null +++ b/2025/functions/5_exec.py @@ -0,0 +1,8 @@ +def main() -> None: + exec("def add(x, y): return x + y") + + print(add(3, 4)) # 7 + + +if __name__ == "__main__": + main() diff --git a/2025/functions/6_eval.py b/2025/functions/6_eval.py new file mode 100644 index 0000000..d2fd86e --- /dev/null +++ b/2025/functions/6_eval.py @@ -0,0 +1,7 @@ +def main() -> None: + func = eval("lambda x: x * 2") + print(func(5)) # 10 + + +if __name__ == "__main__": + main() diff --git a/2025/functions/new_class_type.py b/2025/functions/7_new_class_type.py similarity index 100% rename from 2025/functions/new_class_type.py rename to 2025/functions/7_new_class_type.py diff --git a/2025/functions/8_code_type.py b/2025/functions/8_code_type.py new file mode 100644 index 0000000..ca8a8c0 --- /dev/null +++ b/2025/functions/8_code_type.py @@ -0,0 +1,47 @@ +import types + +# 3.10 Bytecode: +# 0 LOAD_FAST 0 (x) +# 2 LOAD_CONST 1 (42) +# 4 BINARY_ADD +# 6 RETURN_VALUE + +bytecode = bytes( + [ + 124, + 0, # LOAD_FAST x (arg 0) + 100, + 1, # LOAD_CONST 42 (arg 1) + 23, + 0, # BINARY_ADD + 83, + 0, # RETURN_VALUE + ] +) + +constants = (None, 42) +varnames = ("x",) + +# Create a CodeType for 3.10 +code = types.CodeType( + 1, # co_argcount + 0, # co_posonlyargcount + 0, # co_kwonlyargcount + 1, # co_nlocals + 2, # co_stacksize + 0x43, # co_flags (OPTIMIZED | NEWLOCALS | NOFREE) + bytecode, # co_code + constants, # co_consts + (), # co_names + varnames, # co_varnames + "", # co_filename + "add_42", # co_name + 1, # co_firstlineno + b"\x00\x01", # co_lnotab + (), # co_freevars + (), # co_cellvars +) + +func = types.FunctionType(code, globals(), "add_42") + +print(func(5)) # ✅ Output: 47 diff --git a/2025/functions/pyproject.toml b/2025/functions/pyproject.toml new file mode 100644 index 0000000..1ec9234 --- /dev/null +++ b/2025/functions/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "functions" +version = "0.1.0" +requires-python = ">=3.10,<3.11" +dependencies = [ +] diff --git a/2025/functions/uv.lock b/2025/functions/uv.lock new file mode 100644 index 0000000..a8b8ebc --- /dev/null +++ b/2025/functions/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = "==3.10.*" + +[[package]] +name = "functions" +version = "0.1.0" +source = { virtual = "." } From 4ee710c568626b197f9a0f2f9bcbd12c0e7c1be0 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 8 May 2025 15:57:15 +0200 Subject: [PATCH 07/44] Added initial abstraction example --- 2025/abstraction/input.jpg | Bin 0 -> 82063 bytes 2025/abstraction/main.py | 107 ++++++++++++++++++++++++++++++++ 2025/abstraction/pyproject.toml | 7 +++ 2025/abstraction/uv.lock | 44 +++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 2025/abstraction/input.jpg create mode 100644 2025/abstraction/main.py create mode 100644 2025/abstraction/pyproject.toml create mode 100644 2025/abstraction/uv.lock diff --git a/2025/abstraction/input.jpg b/2025/abstraction/input.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5b0fb06f958ab855530d234af9cd3d618fa1995f GIT binary patch literal 82063 zcmbTdbzBr*+b}*flA@p>DIo$9OLvI0(%mWD-6f46DJ3DDE7IL8CEXyk;Iedg{8qp5 z+|T>of4raHS!U*%bFRM5xn|Cs!~N|2D)N?uhvjM~fTNKf0Vs$o65e>I%h*IR5U-(-*+P^sU@fd&0 zMff@%^KTeI6#w9F_#pzu`WL@FKJJfP6i#*k$o@w+5F1-I(%<+9KV>8Tqd&%IHp>6N zxqsm!{SIO7UpgcG4qqPf|H#cl`41TNU%7pGXn)reBkB4MKt{`zr|)Fjz5t{M3DVY1UaZcT>NZ2{A}#Mqxyf;zsQ<4Ax6(#~kX!oc6Hx81d1Qk1{tu2)#h=r3%>K&r$eidcd!o|gbpOw|lnZ?M| z-q?)A#NL+G!^nXZ#KOi32zu~)*gDvnxfoG-*xJ}R^Lq$U|3TwNz`v_msj2>;xL6BO zYra#Y61R6UqvB>^V_~Bf#-gI45_B?s&#(MO@^5m)oe=fk65ZY1S=>2T?48V6L416C ztZeM8?Ci`43}$CfI~OAlW;19C)o;E=#`dl*Le$j11^rw8 z($m!BUpWr0PBwp}n3}Mf*_hdy*||6)tOK$BH##F%goXdjC1!|}@W&?zjQ>jMU}5iK z?`&c3@ZahGZT`dLzllaT`oEL@6ODhJfv{E3)P&!~(#6K?kC*>7{*SxGY+U|#?Qi_w z{<5+B9R_|IBRg{;Y7b^pv-d`>HZIh{Vop{@b_Tz@m>}z4c>kOJuiF33R*?0-XdsAi z4*y#-J7?klqpu0F{@e0D!4mw}C;V@WT+GCs%n)Z&;oo{Nvw@gF9IF3xe?iv&j`|cgHgd7FGZ$tPWc`o3{{!cpy{YAU&)@QX3*z`Ah*$NWO!*h(f8hKd)PL#t5B2}8 z=5M+Gf%D&}?VVKZ?QMkrciR3-?jOp(+y5s0%k}@!@&6iCf5LD{a%72EaFxLGp z5UTi3KcwPdVFP$y%e+JVomBupo(xGFKp~}*B17aLWB{2ASp+}_ak25Rv6CSS1DL=4 z&IST;kRhW1SpU-{$dGydvrX_Hnm?TB{&0S1se)QS2JOM_?SqVyt-fo$UjRM;NPh}4 z65=s{jEszeih?MpXsEvn8ampag8p~G_){={mk0lr-{c5FRK(u{bTssT>;IpD`=5w> zN_F235MUymAf2EfQ3J>XNGJqI_uWWe5&019FAe{gfH)!aL@0)V`2Y(W@rXx4K|w}E zK||<^Xz~61C_^Pcd-NP6hW=Q|2!q;@kUcOi2b1P?We1V+*a0nvu~X0ktS3*2NuJTY zpnu80$jQac!^_7n{zgJlN?Jx%MO95*LsLuJ1TpB$EiA2^U0mJVJv_aFKZJyag-1lj zCnP2%e@;nF`B&LL6 znRC2zgkb!uY16JJo7on5Itu+2q@Fj5l#RT%OixL1 zxh09jadY!!HpuMc8M{FggppbONU^>xrB8FYrOV=A&%@;X8B>k#o{yI?nyv%@rPM-| z>zNo<%AXAxdjlI#0M#6RB<15i6uuiqA4Z?9PfS^Jo95U=YaveJD&5}rG*@apis%}I zzeER<%O-7X&KSz*i*^-eyTD%cal!HYF252XHPYl4uuH`?F^R7~doegKayzgBjHj{Af@|FxndV}t+pB_sA8@edo8HyReEAJy_u88)Tc zq09NNkmNQdMe4vCw~R*%GR_opfQvqNGMO)7W|o-Nv4jy}1ng3Fxhn%D%VsU1XlzVA z-rRbXmnN*o%UTZS)7md;d>Vk;J92z_rOF zyU;Ta=>c4t9v0iY2vH&Yw=PA36)PtFPQ=eVO$?2<37_+EAqcwkIknpAjbO%QaAY!pw^2) z?s01+5nbhJ)9gZ+8=>>0=zB8$PrJ^HZ^bKqt`?ZhJ+WT#FspOeH=>e8D@no9?Ub>m zk}uu(XW!V`EV z70;nCh|%C>2GV>5v6Dw3rd|Llj*^`!+4LCUO{~&FKIcNk=sU}7#U?4jeP+#sNLX?n zr0)ko;;uRYI$pcgt>i~Vt|spR zX2+$fGv9~?eO?EC)&8dfQqah6C2Snqqmx0xpuh{4Jov0nk@1_k`F12_#$ZUkrYI(~ z4X*x#)6gvw*9C4-x4eQ(u43Z3E`G{FK;AGPE;f<5liEarlUZ)0x_dC@d1 zP>C*Odgaj_9QB!X`#TL9y(mfs_gYRo_kwNGroX--8_+sx`e0i4wPKI(31omnXXcK) z_Iqt8?h}JrJzeB+fr`HJgzF{+<`=@3ys9fW8Bpujhw4dO&F_2S$NTn=mZZDb40(zo zgfLJrw-v9)~Ma$APn#1#2^?%_hXO1w@~Uw_`U9_&4KjI9evtC?D#}GczxY z@H}y-_D&WSOza4WYBCa|X}ycm(0~Y28W7AWEcVI}?U98a3r z8bkV6`8wnD+b3WAoHDb?l_lmpWn!bRv}D&djzN9_(=EeUxyzcomhVC)F}0}TMBM3} za_)|#z3m4w1HiKDoM1xBv}*2o-=EXzjBH0st1J{6uVrQ-1ldBt47Aa%V7caTEy{wuW)x?hGsX<{*Kj>@GtD`yprTwEXrVh4HB0FSkd4voxN_)U?aPMfU% zOb$4wP#cS0FhLYKxIvOZ0fW+eJQ}TUi{Fnhbz!XbtO9H5F$=0P_(vdwa$Qa8-Qty6 zs<3!N1Wn)%b*DTi30PPyvJ&Nc2*pp261z)O2V4aP5s#CTyCjL(=$O-dtX*744o-A- z48I!tz-}<4&?;HHI#d@avgDY|5HAH>IZ#P1FIzn@VJp&T6RbHAJcc1Q4<2YIFDUM3 zkzC51Z*3S2v6#!S(k<&rK}RJ!)QUIs?HY9{FMe9)(Y~woXvGY`VuDr)CiQAWEK7d3 z2yfjZG`v#ne<7@xO=)mfqhvPkc9O?Va8BALra(K)sFe1h7UvxiT@IKMx7iUcD>k3~ z3C}7~Io=VztFELLDkQ~q{az_D8tO9gd|cc#CD$bIVnTRysq}L4O}xjigkLAOOp6;F z>r(e)H+}IqjxhqNlFYfp_W;(md8I2^thm;==ym|Q-JXFH7T9|EJjRf7a^KVGTZF1+D-gs$j?ld!EU zneK59P#4Ss5U|Po=l6d4l8lMf450i4VJ`Y6ihG=9R2{Gt#^a|C(H3GIF+A0rB$?#GEQS2@ZY)eM1~ggksEIH;WkYLM9Rs(IHk^ z6K8&CVpgNm19Z4x4e_wNmmyx^0@3sWciif9=(Unu1gt^t%uT(+Z_FV@q~-Ylwa$T` zn<2iK_A|UMISB#7Zg(UG7LvoFV~@`gkAV6{;K`hy;vGXS8mBKt_ox1n8}XzQ z%#~UrLM2A|F~}ylo=?g!`WsLwj+3JSyY>8CMBYW*NGJwYT=h`{D+xIhch3UZE&iV>8g5( zLz6{5p+?tZkb0REw^Pd3949f>`o3aVc%yZ24|H255})#=J?fNLK5yv7q}p%{3?g*d z?eV+^!uMg`7rWJGCa6jk$Qdguc0f#Nrq~KJxUX#60Iwe`bq_q+=gXwNj;9;6do50Z zj*r}$aN)l)0vL#XJ$)gUUTR4*l>OB+gJ@wQ!^_*T$5E}jXwRGXRm;%jJs>y5#T58O z?ML^MvG~Wt(qrLU=nwkmxuGXuxr4e`a<1hn?dBQokVF5`}B9&`h; z8Bv~`w{@g%jiTlq8YGzo)x1t=-{KFGQi)0Vl|Sv`{YCr6LMf2Yy}bwoyOrD={2&zf zjlCVW+dM83eh;iF!e`_*;Dx3RO}&^aB+h*i7^Y;8n*=Wm^OfdbLR}5; zwB{D|$e(R7F{zWzld$h&3O%;KV8qAD#g5JQ&^w{DPwYVrT3yqChmWZ`IkBlyB<*x{ z3WG+u6n4pxa?*DOBObj@e?czzoiq&1qgN!~p_OhTznWn2!i!EvVCc4h;Hajlcu;cD zjuDguQ3~+Vcu)DHku2%pQRs$3*d6Djk5I|JTz}~4tTqFQV@xU^X#hM36nO>fXd~P` zCAWL&D)bsd;$k*TV!_ML`HGJJ3>;#Mc&akYc0fDnP}Q}{0|=oM?1^(chG>5GiG~_ z7Y_1_;x5%>xu=MW@$@+$`ic4VCtQ!>WoQnsRGyh({DPvSrA#Mx5A)E|<3?u+F>F)~ z8kdCX^E@*FVbno&_}k1R!grbF^` ztlUve58+zJ4fYM0B1trInM^_(G{FlsE-FP8((y9!-cx+Uc9-(|cQrJ!e&?&IdS|Em zy9R0jrG#-K+BCGrFsoSm&YTe&02KVrDdOn7`ZF|0TkhvoNE3ehWcWU2~dci=| z{CAtgnbvRWW8+j8ZwmQnGQ9DoPF&dt%29}~%es=}y<92FRp^{|_jNdWi-R6Vs|%Ji zEMZKyVsH$QKMN~=V8Jg;Y&4NSLy_0F6l+$gGYa+K=#Ou7K34cDa=k?|$Tmo^KgvyE5vVmZs(< z6i>;{i{E-sq!hc61uMT2KPvsGAEd-srfKI1vCB{naSkc*Cp=ipP$9#9Zt#fLOF@t4a_aVv0lbl+Hb1tsLux`;WLHk1;$phwaISX~2V$Cu~=#Z22r5F}*yfWer`U6OG3>=mf|#kqnmIx01aESi*Z>z2tKfFq0uM>1w#a86FTHDwa7*VQj0ys` zXQ24VUv!(Mz5yAi`W`Dc?$3j_$e*Gvo3FK#9C=b!faw6yAtEFDeWG{wI$ zU~{D*p~_%DZv`K+MvBxl3%Gxs-9nMCj=^lJe%~K8SNgpM4Q)~B~c$pUJsC&Y|xPVdK)*;#T0yr&B;knb^KKu7J7lQ=2lVnG^#)= zOe0|zDaEJ@Kq08ZzH>In@x!^OJh<+4e-cq-F-SBKyPVSaKoUzNu|vy=UHrxBtQ&aP z0B8P1)ap(eyFF!2gcU(f9K0SxGq7|h4Qs{U0m zOsiQBJ=`^$u%BdMocX1F6xpX&`ZW|dr7a%Z-jA8>3aRmxLA;QxCM`#Davt6Tu_B&a zHv@48Y$cQ!B73_lF56pHu@QoNxEqsKwK=VvI*#Odyu{L@am3Ktr^iLPks(63B`fqg zsy;gIUp4i*xSM<%r3@Aq@EhyH8C+s`>+W)aH}&(R&MLI};1HM*4m?pB9020zSAnayDF zI-|&%AiC;sJm`dUjbfNt(ki6R9XOuS6p}YyYY>%sJeF(;*~O9OKN{a2oCxi>XIJlkHl^icmYzh()&@so!{kG66HBrl07kZ;~h{VFY zgqbOwOBaZT6@8eANfS76M08~h^$%`jsjh!2gyHq~WfV``})ODqa8`b!A z9wkU2Oy)7HhoWIZpuTc2CPI#(a11vv34rvLxri`~I5Di6RX`T}v3zVxw`Z_2sj5Y3 zqHUfF>$R3vSsR}7@q!C{G*)k)EJ#P5%^7PM&yfWOX!uk4696$vi63}76ve>Ncjz7w z?}Tj==Kh*wY_0(TSYoF4^A6V_&kul5&x!sO-`>1)lT(NrS6i_apwkX#|vYYFb6Bnl4b0|P)vlsvhWWR0oFLV`R@fxZx+qR%XsLwr;B8coPJiu zD&#v292kCOYY@d_j2Z}h_YwwqB5-_?q53=$)|)+h|^5wKtpwGPM?PVD{6rLNdi z=RzJLl{7E!7~4g3N+N7zsg-dGzixag)7R@DFr_6U7B>v)d7gqs!z+ABncB7?0)rcr zZjgM?o;-pnye4@la@_O2ubxC7v^{ZD&hB=6g;mH65eQo%T1Z*3oY8(%qDA6K|5BP* zBuQXMlp7W|pHJh?+q*QSnUJjL3CZ`ocuQH86lJ&h4R22j0*%!5ulC9)XyY18i6D!ad__9P;f|!94*sv$jVPT1+aEbg@MzaFF#BXR z3Cf-kWW3GHkMZuueCTJ+lwfjijZQz#3p3BcK9zwUSFYLd7zQEyh>BOWkCQscttsS` z(Y<4>&OX|UvdMN4L$r^hwpB=N=dDBYUX3W@!Ke%u?!1-rcrO*WN?#d>w#k-P`%aF`vg&vtc4*R(UMBg{ado;TMBh`(^n+2YtH%@h9;lnB(`Pu!W4Zw;(8x{2 z#)gdWpzQYTNNzVGBLY!azO)JCtv?Hsm&?$eD##f^DeKf*;=nlnLRd}T%HSp5%~^3d zinNu|lOUN#TzF~W4S9p&>L9+ID59N88P`BXjw&1mhltLh{|z1gj?C>B zBSI1L2%q-xm#E>nYFk?A?^iqM1IJtCc+&OZr>R1OHA3gmx~N@dA0EpBxYAJVwl$6; zHuigh65n6KCe97@8|ea`{ZG;DKXSu6>1rFQN_^ z@rfm;8!NJEy*t%;!;)xr<*^_SA_kG_E;I=y1T{wZCF6vCm68X3&k%!QSY+8v9UY0A zpjfU1j-K^0t-+Wm&Bbwlm~4*(jg(r{)*jARj~JnTWa@@^r5Yp6L4A;0b%;KKg8XbY zKGSS32QH)4P!eND=o^v6SH|lUD4+duJkOQ9F0KpAFg1MHuHAc~b6}-|A6Tdw9g>Wh zxdy}i%uHij7tR7eo%0{QCT>xIPEp5qi9z(W%-qWFX z*fyZaFsB&i4!knJJs8Zm2fV7KZ_W=yI%NbwHnv>E8bj^NWGNdQ_1q6G+t29ZcJ(KC z0_}aYav`gxb=O?+edY8ySeQWEDZIz;vq#v>VN0nQVJ^5{}11x&xv4dx1bNxNC!Yq+Q{3z`flF5WIW4}->Ao<@&DBEMhX(Dyd7 zc7BnZ@9+WPrdpdB-UC!rD2Q(jsUu1~?$79Vvd%sq+kdhJB%ksP<9R}t+N4hKLxW|K znLNjQI?FWUqH*v*3-EotCJ#FS2CRi>PjgFX@Cg`$C?@H4V*C4IZH94$53c>Sh$a79 zBxNGJly(T23-PWdPZln7*<3O)2a{b5|g{k1&u8O8!Jppj%vK_R2`NoqG5$4~8?Cz_d= zTyW?mDjA9ao7Kbo@~++@eeoOp%tejD;Pm&zkuo&Udw{}2b3yd{W$Jsfm2>iM)<@v8 z;sfKQH;t$1L>A#H)s8XUk+%(puGR6{)saZ3WTAIivNzqgIn}&5wW%YXpldvi zqYL;d9mD1>>*1=O?i?iP(Z6e8J?N{@sZ&*XI^E(|?rUDO0K#UsVccyZ#RF)gzq9~% zdjivo?!DMa$%}h$UF;+=I}8f#^F96IJSl2cuVpsEizD?yNLu}0g(%gZ&(VJl-9kPd zZ?YBMrQr0`ctC}e=>Gt+L@9pdXz=c{M70M=xe#_8_QT5+VrAzT=zLWk@pg}arB98= z`rAFd`V*n>D}TlTcjMP?yph=GNg7K@EFGHPa3pq z-(hNefXNII{JoZHvGQ5vtjkv6UR-sGfqgz(1hSCp5LIdv&^j0aN zh_iaW$-+hn{xnS)9TTG}2v~NAy;OJ#@KT*|2a1zbj@Fvzg?xm|wD9-BP{x~^xqaWg zPX9drsC0DiJ%(rhTAB`Og(E~ZyRHwmL6M=Fle(}$8RDDtY=A%g<)-#qZaD`ujpYom z(I}KKUgl_3isq=KS1%w3Lhjtl1pmG`)un#I_JvTP$q0jzwXm*6!|hc^3?G+-{nxnK z&V}@g%2BoC_b;71es8i+mrL(0vwsZfhI|(1zgGKRm7+bgN_>O_<$5%9)mG z!;+`Ch$!6yW4?a#Az9;YGT2#2NDeoByyybP!07#yY5odL&064vP8Sz;?gVPL3w&P= zR##LM+M|e^0I7I6a1S`#xnA*?IXY$CPu|T=_hJ`?On;?Hapo6`w!p z4b1^8+LoLyMEy1YTH7lT3;a1*J$90Ux?mUcj6j3h?>aI#epZzHEJH2+1e%QnVR;uZ zqqEDsbP>Jym%B<~LohknbRvTng*r>Qxi|Pwo;65ULp65)Bei)9awg~xyHN;D2G-qZ zE4^RGJ%DugeDi$lM&kR# zBmXR#hw7wMI{S7BjUgIFlesv4W_FZ>{6v}Ib+q|Hz79b>G#@9M3}A4I7d?dT1zz}v z2+6J&Z^KnEAw%4!)@}*ej(l6>7)wQxq#n-23=4w^Nu`pR<-*7{4-+ zlVkWz2*)uCi1te?K~OQgN$7`%dejO&_Jv*VIi!t%Jf!7PlXF49wm@Zrbs)SjphH@X z)>h&&_EnUD+{J#^68W=v1M7*J_S)}hQbD6J1ZE$zILLD7En=-u0+u~AdXH};rUd2I zbaeV~TdJS1y}D)|t^5SKog`Slj1Q%!b4Ovo{ZcXu4jiGk@>U2$!F(y$23Y9oo9i`jb9v;3GZ9CuTZN!oc`=L3B{3cv#qLErQ9}xe}dvfy@iZ zj(A;Sc)R?Wm*~qH-0sBV+1*_9RZ_>RAKj5Lw+^?@UOQWVvzM+bpT#mR0a7_-{ zc9x9_XSl-5p=>gx-uEKo35$$;`3vFFDH$&?7sAuN4pD<3q5qF?X z^=EA#{3th%YpEL!{lZu9!UjGKJ6}kG_9L&tx}PnY=!MQv1}s$0B4#f$R~qZYWd`gT zBKd5*#GPAAUxgrD+WudjNr$ul^w^J^f$QJqT3T8~#$W*Ei`N>TefZ3-sr$JB2-Qrz zge##Vb{$}72(9B<{*Wfa0dwYSI!Yhcoog0;>lmPFLfqqdI~-y}2VGtPDH}@ z-bbo0hVemB>RKEdKQ1Rw&aWzl$e6)`7qYsJDh84gq>?^#>8$ecaZcD53A3pUFG+Im z(g=CER5*9bVa%V4bu(9C5HB2rHij%~pKx2ZJ z>%{c=));Q!Z>G0ol@BihaOFzI~9KC%N(qW#9P zpH_YA4~1$^wj&RJIxS?2U1rH%*)*mJ?&wK+)I~C%)6dR{!l(i#xGr0+b+pPuKc1xk z!xM469%tZjd&1JvFqLCGq0(dMS@k${%7=leG%-$$QGkLEMbMAvJk<<06|r9ONMF@7 z?(#vtM4vw3?rMqk=;G7F) zkGU*qAK^p6&u8YMKa=G&gx|9bs7;B9d=j~}W7Z0nohrlR_}YoO!b_qt?VC4acDtxYr9Ctb zl}O1ZyD>>43jAF^=GmosC&QdabudWVWb>7O97(y{{&4vsx^+>bnVjW}ts-^I!? z@)P0}S=H;C$qo@LE3qS1nR(I1)bALdlcv^TT&=S3O*%odjdGU4-pAws%=$4yk~Z2w z(pit|cESKoVQ{V)90}W<(&okvYlon>qG0PQL&UM}1}lT6rP~UYhG?<6U3x(i`R_R&+O_k1GVKU+%o zzu(by7#0`)C^U7{$lIvg{U(8Sl&;mz*cG_1Pf|AL-zfl&y3nwZT?~W}E*Owljq%LC zPoj!8v@6$B>DD#~XVxD-Q$5yo*c6CKx?CnOcVU7iGhvnA1Hkx)XCG73T@QtyW?TX# zF?>B5_drOB$S@F~Y&k5$^9f1V0#_t)G;QV0ciEbSesy4y9hchn7379LIz10@xh=VUQis|rV=R3?c|*NUgdq#fiAH@Ylgp^(B#&%AcCATnmW$v3{ayVy*{;-gdA`@>qe_L^a>36 zDe~bVDD775@U8K_bXBr+@NM{8NpB20g*9~0u0i}AD6p*AnwYuLHYo}Zc(=!UL+ZY2 zfZaV>*Bo2h|01pO6y-ui%=h(-eDB3hZlC;$rU&OlmuIZ$6xPG%r@Kolb-Fw%LJ9|fjg^e!(hU4Eo z$XWYA*=_4?oAW67N#E!~*v6a#cjXn-*}n#{pAy$ks@wHhXWy-L;7V%~%83b7WWKuP z=_)KSm4?8f#_P}BjVwv#7|V6jHhYhS zn!g(e`hxUA3Q~Vv#C=ihb9Ye?pNUB!x+K@Kh=uFYCGGjjtPJ)WR>d05YsAH*Ud>c4 zZ1tUOZ1nSPNv=YUz$gbdb=()#ac9WQL?{~u;#X%qUh0W9EZ-wTW@1xQ4r3vd`EEw< zcTIYS9N=57%xjp+t8%i^z=3pUtRea%DPE-gRhf zXS0dk(@I_GsOs#inY!)cl3}CvcP&JoXY|v}Ci-cjdfa<}I|dE>q=M3PKWBT?I`PQ% zT?LUg8s<6rjp;)KwLv=M7l;inE9JIy?k}_1p;U2q z9$+1Op2vf|5<>XT4r}tPttOy=8yJy6ESSl*TA#$vJ@#At(zde;9v2~9u&*>TU6uL~ z`_>Ko!Ni&|p6d6OBHy|F z3T%JGlriS&cKsUie2#R3BPKnJQeINzYHG*7R-?;0E3AzWFV~oRZp)x83S}KPQ|0Dr zW++*Ew{uIZESsGsgZOftA`-OerLnL3RE0JDqs`m07JOLQ!UOdrOXXU~=Ex4@E1ZYF z?8*(y-itR)%G)mDPN~bi)Y;As}xMy2Z51h!u}KYzw3yJN(sP zRB-WqL^5N3n2Z`7g$bvChH8aBXww1cLW=A09X#}EzY*o;&ZJ2;(~)zLv6T)B!^jo+ z+>!`@YTCh7hm;Im#@|l1HqNYV)x$=9T|AJCwi=NSYoB=01Wg&WFodkfWupIV+xJ#+ z-L->sp1})JKi+*UBuxk?kA>^Sc8ic|TTH}0@L(5VrV-HlZgO&Uu{|68ccJw#&XN+e4oZ0)nwSZX&!uxjpq77yhXvNz7B;K(QJ6J!u*GnUKt>)MXLJJ*Bpu_x$+t?oVj)%P_FWWD(-nx}Dd+Vm1J)K}h zmU;ZK(CJ9{gK_pd0jahL;SR@#&(WWvkV@H5Os&oDg2R1y9O}GWyL9Jadu?>Wty7EN z;&MsldFY0APky?=I_lPzE__QBvI01cr7mXF1fRVx`Qe*C7y#T(hFxle4MNGeFTzh# zGyubD^TM2>3RlmZz!wuNf(s5#SPszHD~%s(l~YE-RtoaH>Oum#I^O9j_SG1=eo5V1 zQFKcw^id=MD1jQBKkb<1-^9QJ(ot|1C_<~b#jMVsAm))8ubnr$@*T~!I(sIfml6sq zAH{@mtfs1d9$D9p^%Ml6X6*G}O78~i zd)oWjL42zjWriP6Of?mPQq$ySY)q(LwzpT5wU%qXWAr~jzOry_3@vjJB2df6Wx`<* zpv+f2*sDYTVs;i)@gd0n>a^XOK)AG+sv5^g-p^`!JSX0I6WZC#GzhB%PalwkzNksw zrIFU#bjuWq-KitkdQw{ZpdAk1*7xG9uE*w(sR#8Tx5R3BxbEWzTcO5va_WGkn7nJOQyRS3Eb@0L%dzMm^q7n8OE#WGY44pKbC?}7}58`AFq5)T8lM(c8V>BkaZ@(*$yDKe)N z?jGT?-UV3S12HoR8o}R-WV)_j^ZUDgVM?#OpT3x%Ujd5;>JS(_Z1Hxf5GIT9@>c(D zw}4ovxGkx`TcAvql#-$-e0f=?8gGSnLvb2R1_A0Q5_cC4V-^o}xh%MHB)n@Mlb2_l zmY@;&Z4%|d1M9KMt8f!K%FM#lIU~q)9e(0!fr9rH=3BzJsD?*DyPEt=l@`IUx8`jd z7b+7Le0Ec&QoY(aW?SBB*Gx7{2_-=(FEXE6kT*HXJRO7>snd|%GPDq|XcLsIJ17Vz zuBF0ic1u%*Tl}KnRLX5&;G_{PdA%NC68>^5Ow#h`#Z1co2%JF)EOa1x-KlB@7 z;te)P@UM2k&b^Urk)E&`tTTiRNEx)7Qe-Aw4WpYt1f9_QobP6ZiPIJ|t0o61m)gt9 zV-sW9mHN*X(R`L4j<<-2Z?iv3{t`QU%cHNOmARg*E(lhD5)0lw*fkg}ydm(1u==lU zw-AtGjaAxt;VuF~z|Hn0I0>esSO%&yq-nbcbm9z}?tuddW^sFnPRlc{=i`te8<&3G zUIA#^#(W#?nlArU*)9HTJ$v1?tpH*ol4OE5Y4lClAdB}z7prPDe0O!4w%((%r80Pe zT!h~}_?$hkX#-$(Q-M1s9aFEklV9A`TqFcH+_Fl_Fw1`(SQ=kaaynvvm0>1{E=B&% z>mk33+;?~OM^D$yAqwy6Ay9p-ugk`cTOYBT@B+WUmF ze&gfG9TG-)VsDBhDYZlE!6(lhUkdrzH)H^yi9kJh88WLZ@o~GFCk+%%!fFa8aoc^p zxJFj<=Oe9JevYw?4k98}$5G%%We-uX_tf@0_z>GY% zWH^YCtDe9OFmKFnn|$7R`=9w^_YE8txHC`z9?%KE^&G77AYrQXAa|tKT(yYRGpqKh zwLDL&ciHxfB)=(i)4Hd|KQaw3O>2CW)ZX}Y?BvWb%=uc+$Lvt(@Ebl?_uE+-T9a>= zO-Ls_-jpGCY;-lrKDNa=I<4cBKDk%d8e=M}#l4Hmfk#Qa-@i!h!MAg+iRioi@h);@ z?dc|llqv&;g&Lu;`96Vb36?Lww4YW=yMiK9qC)*Q_W@|cPF`IW^0${P7 z_3!M@Rt~bxOw)YJ#|NFzpD5MG4z!7{>f;6#6>3?+z6;(MJXwX3@_nt_y$9&SPVG^p zRxfh3>{neg*rDg54qomw(HgL@9oWJ9XBqqb z{a=WYL~=jCTDkoENSuOgJ@)JVo!|}2WOUU8yQ*H#a>P`@Rm%YBQYWY>aUS{G^zeSY>K({K*wW!On`Z!^Pd-nX=@ zz8edpH~U=OqopFY4+@!IA%;ubl7|!&uc^QzUr>DImR|wiLwQdvN@l!}@kk4voMT&V z%))o9PKzr{5?JraUG-xAbYz~RXZnt!G1Hg1uD`gqPKokO5)x%xNfxOfQGsQg2R}^o z!5QC9{7YmUDgw05Xa_dXy9@B@16O>~xh1_)J&AnyS@MsEC>HU&=R9{I29IxBQcIGY zuG}5RY?fKSeEuAFvP839FUW&qWVLXfv2Z@Ncn}+sg|pF;e)p*D;cG3OUy(egr(EMc zQq$n~jZvN2WPUbBz6TirtzkrPwu^FVM{9R%ZR(FLcu!9%3OlZzb{>o``*(`l<8E{; z{@}sAT5B!vSPXs;5`!|&Yp|A>OM4H{T)Nqn<^M40XeK|1eKX2}SL!2p9Yc%`e5iow z)~eWEmm$SW?eJuhj+?nX z3@B;gm z5w??-%~natU>Ej#G111MvbgvQkGVq2$OjbHabM#a9O*hhfv)y#hDKJj(>p0`t zOlw;tyf?vR*6+sxQI)mh>g_9RZxg|`B}U$J0P*p{Wvt_Mnor@|%|}8XuFT?pkXaui zHDD;k9w0t<;FDVa6h6~bN`i`g>LOF)z0om@_tf@nod_HXqd%(1m8h?89X+)qPjD0! zCG#U1Ox6CvVo^%M=!-AxErV0GxvSw(s-Hkq$@ZstNWHxWH7c*ofz3=5k(2B6t~p_JDicKyE6VGE z_2Q+8RDI*xtTZR4GHFCa9-S){fi!a*v9>txQOgNt!jJH)`*FY@g*-%XcMg>0gV0K+ zLoH%B+z%CCCV5|_PEm$%af&0t*PT}i&08FbHspFx;OBvgkxqC6txmgs?l>JQkJwln z(4bSe6u1W-wRzzr`+L=YFC1g7EkuyTK)%%|0aI^kHnu|ap~ev8{*;|#M2N6DpH3;R zF~}7fxCb7UAY#=#*JapLCkL_iuMhpCd=}m@_<0mCa6?AMl-HyVbG9FI1oaUG?%*_+H$`^h+FhD1;lbGB1~#d4ppPsi_uKd`rs{6ph? zYf`uIJ=V9UC5l^V5#37?ZIrlgwY(8R*(U*1iu{{7%WBr4QWRDCFU#;hX~;2Du+`~S zsVgtbO+QEIe`dOOimh}VD9tUzIZc z+y4NxSI6xNOYeo=0FKW`hVSf?Tx)uYS*pkoJ1WI)ms1j|%67KELB`=;J*h+ScV5(9 zZxd@i8L@!l_i3lkc0+&HTbr<$`a&!6+I%vpO8Ad~h^>+M!RI%LHVTzYUT-=UXan)hGeeC8uM%p)sGDSzAF@DJRt4*u4$>pGM-pK1F$ zJ_rm)%sAkWt$ktPua9>cfK-OzLB zggNn4ectKb-;wmM2#&ck)Xjf0%$BEB^Ev-?14nvJvR@W|3!%l@rn`I!C{`7N%0 z!9e~m_{PEXzXAAoNi=v@Hpwoc>PQ@UP}`|eMw<=B#$>A;w1^j*l0HX(e|*qDSD}ICx9JRy$QA*&HaiEewYj_&ZUcSI$!vfoaCU}%aD6Mhwegqiqoy+^pWx-Vx`*U>F8r&9 zjf#d)MB{c!kl5pb52bXU8b9Eiel43?lJmn?mQ(4MA!K;2uhKSOmuoDGBDa`2^VbI$ z#d{CKPuU+rPYhhzc!O9Pj<0g&d%K7o+?q}3Q>NX1CiK{m8loO9UDxu8a|C~1Q8Hp{_rECWO6I$Uxq#ie-FZ08pY{5dJA-p5Pvy^S^c>DBNnUS?Kj1C_%jJaeLw^egP(3Y*L_S& zl{mQeKRu<8(x+CfMIVM*eaPp0Fw5fq00j6h3&@E}JKKW8jub_^@;ErJIn}&b<2`2b zd%Z785P9+z4RF8!4}LwX*ZvS_H@a`b4G!B>Z{OVSo&?TWAP^PL9RVx=^{x&(kPgNA z4@&(*!n`zGtJjpdbXR&FSB`mvzN>?zm%C@7rP(42Af ztvHW8%j-?iz~P=IYu3l}-7Hl|F2{&?o5KDC(wUz@)u)xf;0%%Y8sThwSK>`NK^$}4 zLW3B?5)b?b@~^sV{v3E3#>?#MVv@Pwm;RN`-szqL@X#+cyeL=Ia>Bj_r-wM4MNh2a zY15$<_#6FldtT?x)*d(2FQW}L&N~q6wDDa!>-wU^9tk42Aaq6_hJ7m|#eWcdBMrkr zV`1m*^h^)TSBqYJOF?kbY4!_l{<-P+SI+T9Q%amIEK;4iYIRVhYIHO2Uf$Sv8_8%&bIVfUFvbM;YNrjPO0SkvSi(mlvHamPyPtu-HtKNMO` zQ%4bbf5a8QJa)x?DUQHGjQQZs#e$S~GVLIqY~N^WAP8|F3Pum({HtlSlj6ys8m!-E zhToZFBOzlQfBMyL#Iks*dxg7c62{ITu>Fw;|~$VTsR|k;tnS8hYy5H1@E7&Q$CNxyMc`=#;&TQj55K zZu%Zgd+`A3u!*e#d8FX3++h0gTUrLauiM8uEU*tgGmy*a+x#oYbd44C-A-FO{mU?u z2OYWTUV*BeH%3;FGt0DbjtArIUj8x)R6Yx@Bxxw?q22g1Sk(MOERtPX%j7~ZUNgtj zn)Q1o@b$FIJ+7{?BJ+vU<@#h-&mJE5q2diIPHitojwQEo&e4;ftz+DHo5eC0xQ}X< z>H>^@RpH_A(1W$Ch^-z|>_(nlYDKltb!!5?vOzKb;S6$_K~{q=7BbwpxIh#vf0V7 z-5?v9B{xco8cSX!R1rP^LgE0EH& z9oq(Z{#Dg{GyS7r>gj1K7gjDW$oOh~Urm})^1GAjLFrKc0BLC--fzmCtZ1vJ-!-6=w$487 zW&MPIfByg-u7!~)E^b3BBmdC&ofC|&N|Sj58OLEswC&tDALojHkPaLX&lUYRsiX1x z7b3yNf6t|8B8>k4TF*sg;QcD{kBUE5A^r_<;usE!p6iTUP zD`mQmYI?=;?i^K+xB-8aI1X}ul|sWNXzKPOjy=OatyuEHem%`3U%UO;AD6XQ3mG7R z&{F!T5vXUpdk0=cM#!OftuHS={?!vhhUcYn)xeu7wkam%ZJ_@E`l~`L@z@Hl8W2cq zbK0*YYDde!>0A{s1^YpafZ!U8!Z-)FO0ZLmkxjtd2p>w~tAo&})gai4-ol}h5PBYa zRp#5b9qJiSoDidm@oM39Iw3O_RRbT=nt5C=6`?#tjIKDUOpUVzb6nGnj)v|t19mzJ za2$d=QYZrpj=d_{$Buv+g*RfNc3-rI?B{>`TK>%b7S(()s@mwcUk?)I8`&;mc|$@& zx~?N+Dp+m7=aJICEq`M_hIV%zHSuPz;%jA+`@;6Neo9HtlO^lUttihMg2ELW9Bw%4 zU$5T_yhC?oqWHJMaYoZctZ38z>$jP2i2>W+ghc9*sSqK90v^D3mEhM9V_84h|=qJUlnZp z2jF?_wQmth#%Yhs{q?Tn*2>I(v>I}?FlrXRBZ0Eg$a_#>pm$h)VO?q(f9GF(V_ z1a}y(VA4Jact$y{b)6$kw6K!slQgRkMrQv2Xn-@Ht$rb!=049GRW+h){p%pfobuXJ z+ucXw=Z}A3PmNwZ(QJHA;tv)>H;DeyB;R*&B1IHre7;%Qe7`7w-ztH}0325Xq5i^B zd^xdrJZ<8eeN;zq_m6L-!tRD=kIM6&=2aNPe0R@YwffoP?+<)k@Y?x074#C>@)yiy zV!qh>#1Embm~~HwUmrYgDA1*uA+?Zv+v6k^^awWg^~V*_Pl#%|e2UgQy4){^RlTgP zc$dI`fgT66)r|N0i`>JK%N*c7R~!X?Htoj*4r|xHY@dhvevA7dc)Dv?Mk9*aOH&}{ z&s&JhL#Ow+HTFNjpV@Q%5f_6~@jH30gLX00e+eVDI|0(X7yb%MEys#H9pNw8tHEUd z0Bm^U#1=YSNf-r&U(99$(j^{Y&!uOTVr5q!P2XpKk~=ee6*yog6t`AiJrQg6BmJAc zGkkCOC8u8eGRK7cBVlfkTF>R;Tj>!;oj({PJe!X4NpGcmgZpay2>6lvF#JmJpU0nw zvVUpm{utEYn(pfdBEgtNbWa4GkUO%ksITld;SYiQJ@CKazLoIT!w@C4j)`RzoxEr^ zg^m;mI%Cr{`Puttct=I}ui(4?0Ed1ggi4pu824ah7=i-|+XPPUfuBb=iy&lo$iruDx7w1$X zYOkvS!4=}av%UVm;2#rB`#SjKZSeb5Rq0|Va?44_Mvsg2e;(-&H}5=z zJRdGovj^A_{{Ysm!{LsjsQ8Lm-qvq0GHqC`qb$Chw+aCKtLu$J;n#$(XE%5HWxVf@ zS=bH39{!cl=Q3_5@;w{Cdc~ANOTz?WL$@1p z--~A-rC-#o;;;@~1dI~}qx`Z5_*bLe++E);>vh_3!*RFp_O46D9whi@@KeE){9OH` zH3OmOFaoyriL~mP&CpU|)3m!&ho0i$fKWi$8s*u(TKY~N!Mc?bvCo}mczh)*&Dnoj z9}-D%2Ce&GUonWfct^r^mZDGY&#c_ZaV&WL@QU6B^}`O8qx(t~)xH`gl*mky+z*?m zU85k8*aMGm!nx0cIyZ{!w4Gz(SHxRHvGG@qwK+AtPSsm-S^0{wTG^`sgZ5i!j|g5_MIk;Wi-lIw9~0j5TzjpNF6uRQTys<4(@dr@;X zIN)G?b6HnqUDnQv@>)X)gCpf z_*2FHCEe#s4xX$twjrK9E55ASKMLrs;y9vdSFq1t z{o&<@LjbWenr-w)W3JwZNw4UdzMXX$)R|Dcn?PnuRo z%UV0g;!^OFm(QRT(s*(~X>jn!a#Z~fKHk-jra^f<%+SDap^Ibit6D~5)Cng!I|%;( z3jIG7B^08KDZ@=n&l+jEbPYbG7z-?iB%bVjKML_JN5oHmY&5%Dc9oZcRC8ZV_`}2B zZMF(jmy$iozwIY%R~zAv2ODKNjrmQ96UA;hm59g6yqs5asM}a2`z$NA7~cc80=*x?^8}vmYbV(PAjr-Qct?$O0jay& z*-9ad9$UL&t=_b*m664Xo2w*|_gBTwj&~mtJ_+0WGx%X5=@xoa1I>Z}5$-mRn{y6u zYx9G_HagLceMe5WVQh}6E=S4>bQ$OkUC^}~{UYI(-r$unuy8N|2eo_0gCo=K{t;a0 zS}Uo(ywnm#P(Waj^v~ie*5*o^rqSr(Fxpw3MW=XKH6Ii~VPL*b+NN08aq^SazQp*e z`#x(wv-g56ekXVj#5Wn0mO0SlVb4%kr(e>(T=6c8tayImH3{P@({N7M?g*~e#@`w| zVetduWsk$1LS^zSq~4NrW?XO^)Qa+C2b^m zwufW?(fHkC+rXzHk&FRS1q?BcK9wltGx}HcDnA@dkx@wLk4$!{0&D=SGyFH6F+ILlwH~lISO8mI$ zE6uHj>}eU0jFa^IsaXl0VYN29C;eD#)4WD=JhV=Nwky$8Ktb z(T&4CpX678UkhV(BRk7h`Gs3V9S?c{T&sKokj_Dj>Cy7 z@Sd+L-QRdGShzM&+ucs)N$#}Ev`6wx?pfrC*JC-{2+6NkkM^nY{+V>^@gv~Zi!?DA z07{p7P4&C;kKLFqyv%>TigDV$Co{>uvp;V_ad+0uq}P_V{14Z%tm;@wF~ef1$#Us< z#kBic-_kzLy3tMTz(^1*WMduw08W*eXW^X#O9pQwEZO4#W9!nsKG#2P?+W;G5+A}E z^|VTbw@S7toOb5vYvpejf5BAlTGmTX1^hm`)NcABUtd9Q3H)qDXxyoPOTzEC?C3M7`apl8Vs|sJR%_b%zjlIn)sXcA@D!^ zHDB#ZdHZMIZj->i2TP3?!Lmv?)URE(U+TyR{{T`kN29-Xu3QG+B>NCE)wQxOWfU&gN1fjdHJCv41Z5Q2)-TPd@%TJ;qMM< z5vHG}Y1WZj!(xLA5(UDKudRA2G_aJMX}ihkeu;S;5W`||6{AxV9!N)J)xEX0nsz?h z@cb=%q+GIsQy9qOj90~f7PN~Ml1XGZ*oAU^xgT6t+dc-G5BNsspHaA2kS@{>QcgQp z$DbNz(&eO2^A$a;R0Rt3kNSx;{12{{Y~l*B6lpEWRIWQpuizOHjY~ z^sCakyD!?ic- z9~XRW@mx3h(*26dN{riB*+#^qC`jJSc-<_74aXP&1zs{+%5={b-&jts9J@&AisE(I zB!gDC^31k|{@-;ASl>Ck!>cam3#B-QpY%g+p zVzeK@I!2QTcxBlxK4}{Rr%!5rrGGw=gKoKxfGVt$Y5pJ^u1gXzxd$KQSDC{(W)iBF z8xbGgdH#h?@mD#7nki;$c-TLe)sv-N)3nEt(pXOCZWliP0Lr@iI~y2c`#zro1itxKps(v6m}hsI}u6o55{(ycBL1D?HO7)SNH4K9>ehTuQt2!wTFdoZEy7T z`CWp`8sjBTek<|r8pBoP`o*l}vmFXed(84(6HAjy*6#G!q%ZboDW7jzgHY1pvDHu6 z+kgZ9C%sa?@wK**f2`|3<}Wd%UYWti71QcB8YZizT6mMqWrE=sd0)McZ}6|^?f~H| zCUKZY4VYE-aOH0PXkj-6`PcZj#^B5lN{AY@k$FNt)W-)@pH zkhfG`i}duQ(!4qrNi^Xqh@a*8kIJ~|&3bQyJ_X$PnZ=)mZRXXdkm01*VYEVc3afP^zb+zfA?{_>`pTg6&p99!CL%4(4az#6UZ~cDWB+S&-^TX6aIbM{{8;|_BHxP zqW=KFCx2?aPfLdWT{6bZN}-Y`m<9{mBaD43Q~n(t`%{0KmVq`Vow-0p!)Fl=K`C)Kf5HsY7yi_57-v z2JA+}s5`q*r}txMsSX))jynouLj$D|b_FYzVw(eKJo;7jCm93qto2dDHY(Id55TQ? z?uk$@N0Y$LD#!sxL(Nq}TLkp~01C3N{pyZ%9E6NrK7x$8sBW~MG0)TRr>N=aSI?$` zRKa%R<>IA7o((j7l;tc27q?2~bxE+UFa|%up&>}zI2fmv<2)K~DghlUnx#VF8v?HX z0GMzG8>yev&l zhfHH)7;Rp65g_@Gub}>dvUQl!=HZT`F+Qg= z&7a$E_KY3|(q!;Qh3>O)vt*JF?zds=ag27)<@om(;@xaQ!$)L&o=1gBpR{Jz#{U4@ z-~JU}iYzAhV=iyBtC;73&;^VbNGcl`%C|-w@zT9}<461yAK}mKd!gwbJklnX!^P1Q zT}MronPr94kD48$WRSA16sf|V56znXbRKPg##+!Dn&#p{LasVw=Zsg<{{Rbq9Qez^ z-YmBBa}?J_U;`YCk@#Z(gZ}{5uLiDLN-J9l;i1Eqehd@S+0Us)xU$qnWG)_zWU^y$*SE6wo!&a+7Vk>O0$)x*Y3nz|3{ zrSLaTvO0#RrY+p)6iMfe-F=T3KT7Z)j(VrVABI{>9}H>Mw$dV;7}0Z{naJ(wUr%_) z;se-e(ttxg09FGe``~+VUzVQ`V${Ajd{4FT9*FHH+O9DxWG)eTbDoL^_*Wz+(H~c% z_vX#7*+b#CjlL}WO1Jo-sV>br&gDMMxL`_A!!RAW#?fD;AGC-36tCcK!nOYZghO1^ zX0z1ODJ(|XR(P0ts-fU|^u>7gxADusKLNZiZ}8W^4{GMXEwtAve{_%hN{@^kxFLwo zCkxL{%Fo(A#(oj_&#g&+twRmSPX&(W(sgRV+8tmd<3dm`yVfv=X9dn zkJsM=e%$(Zh&&G7Akj6;O;*XtRGJ6Q)*Njks*&h$Yv#Wn{?hUIswwU-Zw!$y1r+k~ zb_4wMuf^CcQ^FBVsOS^@m#`>!lN||6%8N4rW_qtZArfC*2bcn|D$+Y^&&Y3v$ zP%DVlziONPdsDSOBD`C_v%0$czFcJSk_Se>^*+`4C9Qlx@i&ciC41@LmIrUl3Fm=< z(!B=J?(~be8s1^GfMPyiGt`m}06FyQUq_hc*xXk$uTj2j&zS!JS>4itKcLSIc=O@c z#9t2$H(L=hKV>2C2>DO2J*$tt@HOXzCK|7VuEqA3yR3NK5I(ri9kY+Zzb7<*h`N+| zdAzcbVv#Vpja4uS?gjzf_BVCFIQY}1c;3~t zooN=|P%2p*50UOb`F}LO!?cb$+1M!~I#yZZe1;>FcUT9{A_st6K z2+DEt`eb&l%GlV_!LDj*Nxr|K!#3cJGSOt0v$^O$3i5finDy8kODXwB8R{#GlK%41{%7-H zC%t<39EP=M!|M*wjka1Zxc3zZ&u6L8KAd&m5v`QgX6`$uEa;pC71rLhmxXLDygPjA zV{{E|9)4tT+aBV$&1>TvT5gjbpWw(uECBOmBQiEPBig*bNAq<}LSGPH$YggsWaNtd zGZDi-Qo*c!dD&fZ$L%%Wvh+D}>QZ(6oDu4A-(TEM28rb~*7s#s$xm~P0a{-aehO-u z9;0BgS;u0xI19bTQ~mt#Jt~HqcdqzS=G(^hj!d@f9tg-CSH3INelmEg;!lgb31zN) zM}jLkbr#GjqiYz4b{)Nc!nv~iiyg#SjVaRQg0{EcPcK96@l@eQ4JTbUEiI;&JeN}N zJ-&gaeVXgUT1AxC18Dv6n5gvq+VFj=Yr`K5{u^6bH2SBGZJmczg396d4?)Nk;8!TW zZHXm6D*e?T{c8371H+ot&d;z~yv}e{Ncl&hKT7=zj59o!1B#9k7#Pkp(q8kL=6zfC za)J3)Hl=B+DcjL6@;xV1@c#h7?Jm+dq|~)b-#B!F(IPBA?VtuS{?1A&!}qB13qzu=ci%qTGlD$MANniQTbQK)Z@4({>y@+Ptq=5 z`f^ndIa8O~lm5LAUhsv+lcz%+zK^15_Hs#%Adf74x5<t8A9+Mq^eicC9#qy3fk#d(K{yk#zr;;kYpX^cx@d2e+i1Aic9b}r-$~E_g1I&52tD|-&_5n`DK;KJegQ_@2h|Nrvv)Gc0nVy0AQ9lo4BB+t7c{yMA@~T%hiC^Pm6I`3fVJ z1Giu+K@Jq2Mk_uf$qkS{O3+^FEBa^JAC9=#3l4XFMwjL|`cxk-bDFaf0gz7umog=3Szr=nx@p;Yvx?Fr;3Ropqi6`>NUjok-IsI9eddvvR4LE(D)(sf;| zi;XsmJ9)_cI#kJ%!5u0nTjk(=YTRkK?;|xH+|!oBSY+gKKb0sZoWpO*M;?^GH*yC~ zmC+gKDY58q5BcJh;GM&oVGV#O!ydWfvuuPeKs!JfKgyV^hV=gc3XPcbAC)LL?}lp4C~hoF~?)L4;>VKK+Gd{$$`cse@DYtbHs9KNjdNb!Ao z=4fJRVUQrRh30XNrx;W802=;sKWS}eQ1M^Hhq8iSw#r z?yz*ZDJV11{{UwX*|SgZ4~0MBEAf50okn07`G9`yahw2sJuA~Kv|T=b8ClIAFvQ!F zV8_jav}EIo_xsy(cYz+_v9Ak_+;rqun`su-R~|U~C6gIe8273v_XEFAucsB|%M&TS zW_80&ihdQl{{VzT;wGaTX4=y6+4wfnr>9TU?}K{H&8(J|-hL#wjXb<_ zo(IfJdUUVMAB^@f=r5=E+@n3Ta(w8WQ)(3lKE#j9*jK0g6ZoZlV78&>+?LH8k~8C! zDp7DpLIB7Z{#Emt-VdHW(I2sJMs(v3X3y5Y6=^!Yr=_=+F}FL*A5Y>r{RMmped4Q6 zhc|kq&7^?c+^3R{v=@n3@_E81t{b3Sk?HsEh`e26;(vx$Yvy@Z9!SftRNeqAr?5bN zX2Ce&Mo6#3pN(EEp32c}G{*hSvo!J``J^B&BMwjTx85VRK(Cv@VceaM&@-&!(NT$; z;;$F#`mV2T_cKW9BQnPral6atFi+fB2svyPVUy5TghO+!L31^()Qtp&U8%SY-49OO zb*~}2*1jQpP56rzh2W`0te3K=mPApXlWKFG$&3I!4St>2{{X>1^@eM2?fqWoP-nx+ zL-`wl_CPqVs&a({l$r9`W?NGqCZW*y&i=~nd^on&azd{@Wtg)r;$MXrJpdiLiml;) zf?gcfv}xbOI;Ff&%kqKtw{Hc%_)B!O?eZFC>05{7Udw#4itM z&8zA9Y%y9##EuxYe+{FLTKWF~!T$geJWB=2ti&l_lz({+eqE=y#zrgmk6rk42aB|6 zv{wVkk|re-bJL7={44Tz;s?Qqz8q?va9=V7IS#xZm#*M?5nXsJY%58{qu_XtC-xt{ zp#9UP(|mENv!=gjn0rP>V+c6;j@IMf9FG3H3i>kh;v#A`5#ME0Tp@6*4^BDHW9eTi z_-1%xv+^KxCh`L(JY#Nm53i@zvb8;aX*A@5DV?TO^HG0`l1AQoejnjqeUi}Sv{Kyp z`gx0tO%D{yHkqo;szUgUAy#%g;j@xI4i6m(uaJCg;(>D=%$KZp+-_wNfzP1YefjpU zPw}UXT5T9v-7_=Gl=RvZWc%Ry%|0vfANB`_Ztkr}N!~FElg1ieG7qR+ zn)&ZQ)UNKeIMaE-Y-c^qdRV?+V+eLRlzEx^bKu{IdOg`zH@RYklYZg2Fv#a0=cRjf zw~3>@)1!jsDDES`EG5HWci@Wrs_>MTDQN`C?ed`Sji7KprF))@% zycaO>4U#VAMFKmdAN9*5NxU+&7+zdI6rKdsnBEX7j;6aVs;9t_CuVkI4Ny;l1_6t6gf0ZB%C}RIBmE2<`aS zn|PkdAQIVW;}PQ+P%DIjqIZf%vmfm;T-_?&Aj$4SS zTiM0ivGX|5(!sp%z7Ic2>b@Uo5^MVDwVG({ZNS_fL$v@EHnF8m5=n0XQp1b`SofYC zjkK7q?QTqu82O-HGJU;k^Xz74gT>%E<3C8go`=6eGo@0^9<^!W+g}cNiYY{?sK;+0 zVcs*e^!+QY_>=KlUHG--UTW}1Z)s`72#eF$SI+uHt@XqO(#-s)g>Dy)nLpC6>N<~z zj6%ibjxr8^b~1ec_5!&0VvP!TpRG!>jFPfZ-T8J&>}Q&0xVn&ZucQ4A%foga7SiT# z5_r<)I8s0LQGXNu0=+t4i5I%fmfamij+i(>AK#ZAt?Bfyl~cn@cY3!vgs&!Bt9Gwl zg{Mn3hhSLZ!bgFD`E;+;Fnk+d5td@}oWd#zEgY|V^ihw0H=;+!W8$f`6l}{QPbZlt zEZqir3evi}fzfSkS85;|zuG>v$IE##kpwH2V~#&T_*VQ-y}S)P*$B7<`<}J>ewFm*ouWsrcy{_rmRNFxMqj2! zKg){oeQHaO6=)x4xQ$j@NN||yc6YDPatykaY_Ah1OW%!}mt?eS@;!{d5A3;TE~oTG z`x1E9Q}~nnHF$GdLmNwN39*>;{pG3h&VK+a>i+<-YhIE5b^N=06aA)qeeiF>QutfL z(jT);boU@Y{&7U9lf4EoDe1x6#}zo+)khUh)*8^NxpF;f+LDdOYHr=9j8&MICm7uf-+-Zb;Ny?wQ*B?n+Zm}631lP70VSNb4gP?gW|(YdEp&c zf%mc;_0CrZo~H*D@L%lPVX4^o-^7|(Q6$fBl7`v$m6QT-bB(}s>0fv2?{BPWcN%k! z9YdZ@K3)jNZrwi$`B&h@gj!F=En*anL~jDh7dhn}myVq{&!u5vd?ldB z^39p%3>4?De0R<<#dvq@hvHA|y&GM>xjB|yOp^G=9$JMS*f|w+sr!yWbL{Vnuz$i& z;CIk~<@aMF3%L}Y4?%;9_zU4T#4C>twbqZVOCW)WQGxSCB0t?D0O6Dn2>$>I{U9xC zt67-hk0j(jpa5WxU@P-iw#!E|^d1q^8E$6SBD}lm+hshmA zO9Ae4UmECt9WLR!OQlh{qF*>1<@}NI0nZ$Bj+w7nlgBf7=Te7DfuwZWZlx-tal(zM zxcsVsU}XOQ7hDSEr<8I^==-Yq>}O20<*M9vfi1m_5P zl6^QC75cUD15LBAx0)>l{?1W%0Q|8dsX6`}W3S*V@z?fe@#1(#K@!R$wzHX(`Re*Gs455?53YIv>-4Y9ZxQ@(@uk(#lKSRH{puu& zSds%|amdCG2ESasG-w_Jv$%raNR(uLxv=%=kAZ;;=Np=ttAh69%g?}6;gie^gQ#!J|)%eHFwhWYnDr^ zIXt#mUmKCR<$vAaF!ZmeJSA^vnunKlApOFr+z;;tdSqjsKZ^xVaa;$)-vd~m7B;b` z?S%PbV~@J(tCi2w)4n~cjnY0LTj&^Z^3+1GPIY)8FAdDc#L0#44s>abrk%Xq` zO7&!YqvAh_T6TrLZ4vykh_1?`D8YCkdnqG=2Nm#F#ors-_>%rMc;jfC?@4!H1lxm< zF~&2&8RM;KU;Igr#a8L2!v0LQip>#x4dK@zPvS5JE9EZ~U&(uEHkxpf+CR=4`<*a5 z9At6sJ6ETYWvVjfX!CfFJLZqGj>ZDt%V(-=f!X||ZbnDSxdE}*6~V`~XX+Z&zK038 zoe}@(1{xR_snw`{2RPdqH zgOS%8$4*WUwmMhq*)|%TDEn&KMIWD4#5-8flHy%!S2q`pxw6;|}s zqP9G_%H<{_Wn8v%k^V(wNhb9G3etrOFhv~kUM$IrvDjI=IT5MJ>0eHKG}0GKwEoz< z%A*6@3qxiZw-zL1WM;$M{z?=WPV_JWt~Xh&C`1R^c6w*4CCIp9|uD7YB0QcBoU86TxPMUORxBbd(FFykMb(x?v59)wR?}k zFAhn0bsNrwM5@3B;2e4y-layON>)Ct@We68ZMoZGDr9rPIRFe-RSF|WvZ{4%0X%>D ztD3O1g3SyP;DsOO^6g!Ym?0CaQn_VD`F%0#k81MjO5$d&mn+)bI0xnA42*NdboO_X zre#;kK2Ka8*sc!Vcl$@0tMYEh$521Xu7zACR&vf2LaxKx{{XGhp(I6*eei#Zb=0>= zp=Lc7<>!SNIrgsW$J%d({w;V(bZ-(V3!KF&jj}?(V{+%es{a5G;EY#;=$fE`M#%(h zRBs^d;EWD2jN|gJO@mjo8WimaW=NuD`MYu3zvEuEQ8el*PiLWxdOBSE$nkH0E_@Z@ z8}AP4a*;HAsQ}_g&)s$Z0A+{daywT@nrDaM)Aac?v;)sjhs+%A;1YeaUrKyQ@O0l7 zz8b;eeIO6*Z7~dx5y6@w0eHVmp*_Ys*UO#{hfvigx3mlL7DW;f_W>iH!nm{CrWjfu zS*LcF+-;Z2Q>v-7k4*7@!ccfowx}+BNbBI%5CPn9I9XPLJ=;OqB zw-D5hFD=5!xN}7)!s_X6(nsu`D)6pblH%b)c5sqU{sLzS;$MXxJn%?g_(`k?f?v5Z z;I2onr_!@bHG5N5;QM|x^j59$PhZu%H~#<#?}$}ye53ofL5xMI{{UoEa#q>yysoq( z@IQqubsbh)Yl#XASQ=u(``s0D?^v3fOw;|05=W<}wR$IlZez2tx`i-a=yJ{1h=VvEnu(#d<)Afj8 zOUt>{Hg9NAwyNU1cSE|jgHSqhF6Cx10qDGrqosXK{h_7huiO1hpsxfHx#RS&kgar$ zO2@{g+CpH5<|==@GsZr({SAT2u)I5v<_jA^r8eNb-+I>5NB;l-`s~*Rsya%CPq^j& z#MUy)XaFKP7z~{74KCOI=&s*W(L(A|voIUqaL>3EOZy!^zQ3J+Fi_zfMM`n04Vpa% zQ7sw&)%ey1+MedM5HaA4d)5RqDf`XMY{L77&ONL89UqciGT)h;05UpMk>icur9`7} z+xgSgM{q`JiR3PW-FtSc$O@mWX2#pPaf-IwbJyC7kXiXpACRd7Z6c|!%YqF^gs91_ zZc>SV%q_zHXO0CtW%~B?s)$OEdex9X$UF`zp$mzUki)NGQinVmkEz-^3Y02>2Se>q zD|8!;ix3da`eLNiE=HSZZekm@eeXaj?v-pFQ!8YwcszRgX0dfGRx5erlLA|JZLYt? z=syblTf|NyYF|;J{eQ^%-X6=NZ*MP=y{KOgutO|Qg22AuNZ|e6JqWLvekJPnFhl3t zf_C*;21j0T>9^@#p{QSZdSEt<<(P{g8?*9f8TxeN(!Oo+(pZ2o9D8U}9 z+uFY-giorm>Hh!>b@{G5Kjy;fU?-ytl+PcR99N^Hj_~^(zu!T$j3bjND?2Ti?5@eu$-9)~XVIk#ZU#GzlsTC z9sv9+k=1Q(bo({kCdnV<^8owH+Ce{JnT8-fy(0^gkrvM@bQ;S_~{9C zDz`Aqs0jsfKw-$n2LxbsBEE{hd9Af?wnqt7l;kUXyMg2Y2?y7ajAFi7_|pvfF2ALC zzR;{MZFI7~vMY1Q``l)|C|IkXFaFz}FO_^rX`&$>Oh{jRV5&bohAWW$i9Bp9{8@Y8 zyWqifKi;9na_FZjI6U+t@~=_-xO_MLucFv^s>ozO!<8Q^B$((&9B^^kkJ)eF`p=<9 zsz94H65?p4Kf>wCkEa~twR-gVey_@A_diXv3)pTf;JCPPk_P1ij1?oN)D8uBFUH>v z$0m_1w(;T65D)vqAc4^G1$t+NbuaW|YcxJv+yNY5;daQ6^C<0%6Y1NvTlj|+zP+Hm zv`P`}gOyh5LXJQuoDWY*@a68v-Yoo5@n?p0zYYi0qC+K!xM)SfA=r+7We!03cdj<) zjO9l>R2~%ZHT2hZ+DMeF6UY)(K0mxX>JY1(tgd!^13ON=PfgrDms z9&YI_T?Rnhs-qnX4T?4-594_C&Nuk?bOl@Y6&%bsVhs}~o?F1I*J) zMBT~6Tye(>zyR~Ld^BW*gUlsQW2XNAg!bo6iXAIdRfM7|h@vASc*J4)e-3!-$>@4< z&Eqzf;(M#ul6lYo3Xn268-L&^d}h|Ke`AY9{9&$UIen=dqeFo{6v&h&vNnS#A)>z9imYLc@2d7 zp^QGO|`m83{ts|CxP=2 zIv$@e>(aToHGN9YSC0DLGLlAOkPU&!kr;&e4+o||?s4f}&I2rWvE*i%x3pRwjsE}$ z-JG^TXPK=ed_@z=2GzR}i3Ds%Mce#X7(II8yraj`+S}em7PWEqS#E4%o6b?6-W;rv zNVz$}fJQm!K|L$fwX}PGgvmFHCzY-zzq!?Xmr@=`PB$@>0ueKe4x{DH0pN76(XiQ7JS8uz;@zUZ6XfEmSDJ-2c6t2X z9k|ytm~Lgih7Ai%xAP^riPlb3ZO9#S{3xUjzUIDn6aN5hY7=TvA%-R(M;SY|MmSX` zxyMu7=DpkYi}7u(jrjOA;n`KJbXhjsJb2qIz%D??XwS?3@IIB|+Et2#2OlsUyVr${ z%_oR`qtO$Nr5bP9tz5M{0x$r}XL1y?pbyF$DJ+uS)UWqp|^}M#G#I_Qz`S z+zfgGD>^F3@>w7`kSG<>>GrZ*O|d@r9Cof^-wikjd9QEq27&g;1;qaVDj@^%^r)!p z2f6$<@Vq)zm-eN}D!>eaGONM##bPwC-ARAxRB}g zBS+k-teNS+1abwLvttdQd+ygEWBl-SVjZb;$`{LOiEWhFhtUApZb(-~c^6 zEAQWh2UE~=m04A9ASt-~qbbJ#uW&uNuZsR2>8DWFqK&}7<0s#z75XpXO%C45$yp`F z&AD%J&j914ZHtwaglo%j0$akc72C9xC!N3IJu73%m>AMmEM*)6jkv~pW18h>hT6wd z^XJ5=Im&#v3Qtk&r>Mtz>n+>Nk$@qSpyPlC7|E|NQrN#_hD*C^c$aZsyr6Wz$?1#% zTabm7yqNy>aksWfzys-7cGAThgKS$qU^fGjTO9Q1`BLdkH<&I{3}J2K`Dc$xNtvUz z(xgjh{H^(nIUEd+Kyh8ZoqZD8n45(ujl-bb#{grDdsiK2VHmTFn7-%C;Pg1;WcB3H zJhDByLFNQwCj+|@LC@d@2eo$KWBb^moU|nPS>vrIOSDf9THDQWbdTlzo**|78Q2G) z{A!`~8L>b@nu(fly`1Kmnx zow7oW$2c5wk8lNjKX>5kZCk_g_BnmKzm5L@7c?Ij_+rb$ zX4x}s<*=J0c2sf(J9e+tjt~UXI#VKMjGjf_s zmhH2km&INP_=Vw%Z4XDc{>;{+kN4L+l#u$5O5k+e2KK;2x~!=2B9hCD?{8Z2j|==p zyn^mMB0FgQ%mz1sh6lGbq2k{eOACjL`I~-T4@`Sk^lyZ@?*U(!+L**r`6(v z?GiL+Yd3uQb{^H>UMJM=EF^WyVuEf0+yL70Q98st~HRoEXTyyPE^233ekwX>>)}k?X z2T>LgSmTV;(L~ZXR9)WGv4#WZ=qr0ovmR62NsQot4@&udB+k|Y4Jy*#q3K{Se^{D= zG_9qFPk~@~@(woz>Q7vbmF501Oi$=m#V12qzbIu4o4sl z!}716yhV7?C|-7B4cPl*y5Lu}>EJGfb*DRF&?wqDA1KKEIPG2^;|X`N{`)Nd04Y)i zMn*v+-nFB-aoF?AO_m5of8OQnbt#J=PialBz?=Mp@IUgwlrx@w& z-xVK-JY_xPOQZO8Jk3rN!_3+8twA{%$KGIm=p6SX3g$KYiLIR7+W>Ai1VlN*JijPh z4{|H2)O0&959x7OKpbsW-zg*+9R2BAp~=tCa5@U+QAnn&kDPo}quWEMFNu6jcXfMr zZHf2EDUKr|b=wLMnCCp?_N>o{+GPGK@XR`egsChl8w3r+Zsc*>@ck>5_|2$U*=V;C zTfdkOE*SI#a7Xg?sD2N4i~C>U+*)3tB8g-D+(-L}pLAejV*!+=&L88G%p^K{?uXk~#s7y}Q><`zS>PynkbCJEJss2kzkFxswFG6Y-Q6 z+MkvnnbIk?f(|=24l+6%9@Xt00??=M=Z`P0E$xN9zNd*JD}Wij7qH_86|c4TY#gUa zo?ZU{?;>CB%Bz_ZchGL*pTf0luh#oV`w0rpSjupF5r-V}{XJ{U{{U&p zH8^}Eo+#7hDK?T;<`aU%w10VUafNa6aqCn*8u-4)#ad0ax2Cu#>5!|#AD5rQx20$7 zTaPevTSJ`J^gE9Ud_>YbX=NLWts3B5Z%_kBr7Qe*oiQ&q6r|iu|bY4wGx*Z7z9kq{AX1R>v#OIOKEvE6~4XUyYYv4fR9d z&k(U~Ch@U`#v^ZU@a%t2BQe6tQRa_B^bQQo>C=xEcIP&C$!XIc=0ct7+BztBLp%< zBv7h}V<+zo-SPWJ;`@y@ds*ZmWQybFNmK$iGLKL105j0n=10e0hx!(=JKS4Z$7=ro zFU*(RvG^QbT*ZE_J>p%=*!$!%a%Sj)HLgv>@^MZDJ;%&z%ZN)XCELL$4;Qv&^|lx zP3E7Zrk|-Q-F=iaaE3!E3|HkbxCrV2&OpeUYV2x$U0)hic!` z=CIVY0WO_&J;}FrIod}P^T5$<%(lmxg0ZATPov$l16PXz8E4QdTdSAzni#ETr z=fgkjKMi>iYdVq`t)-4IuM&ZYB~j6EP8R@jMmkrl_`}EE8u(-3Ikl}(yt_*^0TUlH zoM9tD$34BP@jv2M#0`Jq$BZsKQ?5BXn+Tc8#(?~>udCb*mJcV(JM8zi<#J^3e`pIY=X@M-#YpTd=0&FzkutW6fog06dU z&u^&xD<8+&tVs~CPFhpQ>9tn9nCgCSTBY63Hq`ImP`i7k95LhQItt6*&=1myA9geJ z`cV`wBLyIw`gg9lw2^{FmWO3Fr>aM504F_%r`EpI@E(b8rr5=IIN9ako;rJeRpcKH zJRq>fbz6WCV-1n-z!mKG_p-`Gg^$e|@Cnt$1yfxxBfK z(l7aFLjM5u>CmTTaUV|n4Di`y8q29UJvQVXEA0qw{{V1ze3*{_1I|YP4lBg|9BGTB z>5#?;$WRiX?cI;*iuKtqK-1@$6;PsNvgeWS$6u{;SE+U+h-{{VT$d^a3}#xc+g z4x_R4t#1svSzI=mtbEWIwxj*kd>(n@<{s6|cu!M|*_q;AtaE|${s;LCR`t@ub!^Zw z6T9y2d~G}r{_4PIVnEMoO1e=>` zoQ~bP^!z(l)^JRh_LODnabp*_cxT5C1zKp=(Rjm4moaLN!brB@B#NUYeVeX3SBdEU z2a8a)ZCx1Q4%Zm_xv#bL4JOmY-WR#>&C(W;K6qJp0#qDh@Z%naz7+oegv(a&-L2)7 zlC*66G;T45UNA>uG19+Y$+-5P3+A%N%K=Xl8OGeRx>xU~U%>F{Vbw`S^Kf27m-=Rj zqulvX2Hc;A>0I}T<TuEi1@NRUxiY`Wt8yIhVqA|*Rm>xUmA1fRUExehQiL$HQx+jqhXaJvGlJ_ z@aKl@HA?}t!a5#@kzB=&kt<*{pkp0~M0EK=N zoA6yo%~Hd)RoeECZr;8})Zt*Mwe2)sdLD=W01I=*zYn7R!TVB(m3~d}XXXC@XEo)? z;{9V<@l;+Qn(-zwFjrlHc>FLcyzzd4;%iGtCh-&>ZL>uy4a8VwBkDSSH8+4h7ipT^ zym#?N>V{PSo(4hZ7_ZOhVJJ|lsnXKzU0T}hsy?OTF?C|7N=?Q)FY-Q?_?4(=`c9`| zXKAO~s*4Co#A-fcyFIJUf8ist{{Wv0fBWHo!jt30#CkW4V$)gJ7_8wOE`4jozqcX( z0G^Nk0DRZ`Ux$tog*ebuWA3E3?V{_-;9H!yUR-#k}JO@)dz@z<1)b z;tX&H_*eBarFMQk*Hd;WRj|9TM{3aw6cs0>V8niGu+BXy`ZnS-#b2>RbE9b+b})Wi zcc<>#$TggkI2`uiRiu;=&NI@IpiS&(fsuzlfU6)VPz6#_fC5epIb>G(LB=Z?;h#ax zD;CL9_qpxGO&agy@##=P6r6eryKf1*m2Kf)nYs$Y5S1u7%S4HU9G#J)XKZF{;|QZQ z)cJD#nkYfWF~&Q7RYK*K#?m-J{n9wvMmqP+WJ}^3ds$u?naA*{Zb#|*SMtvoaWu0G zM`$gP`fgE|DiF6*XHmH@C!G?Q-F{^QpOkuxSBdziP=euzE16t#7JD%rJN^~i-A@(5 zZ@7zqzrx3NAH+R<{VT_{sUnj75yLL$18^NjQ_o8Hw2w`k#Ab1ZBZ4po zw-w^PE0#97Hn#{P5;g%|xjd2hb`|P&daSm-FKzN;d`**{`M@LB73co|5L4`u704?i zd8_;*a{mBB*1IX_T3Q|8Zr<+h_9^Yt%$0yx*B^Pf;VZt?wLN3PR?v7W!m=cq-lw_lZV23vKrxbW)-*gS z@wxpwc2!qI!1Y+~?7TtasPw%GLe|DDOUy7`&e$!Qy_Nau{PZoij7UXwMIrQMsnYz(ol>t1}_ zv?;w$i~csH?eB%3&0*z|0pJ7IgX`M44}$oYNHDM;B6UCBKXmcb4)y9k87%J?!*WY1 zZySg4Z~^ClaCr8w8u&k{CAv+f`DCX3>;dEf+v)Vhb}!+Lqu(`aKkY3nv*Jm5D&q{S zy+=RZ>HMp2;nuOM{ipsBZGLrDi)65KxX7KOE$!3PipG6SG`oOhx5BXdr>gQWaol6I zZ{KLkcQ@Ls+hJA`i4^0Z$=XM+x216VOK3ZupX2=rJVW8>EN-p$Lp%(N{6owmg!L(zQqtklK0A(tbl|UQ{T0x4_%$mC^ts9{((aNb=MC4O=ubTJ z(!8(YN5lOmMAc*QmCGb^+)9?x1R#eE+^^RduIx4<%aolBUk>=IPPtKUr~~DUjE|dY zmCg^>1OEW6SpFyQ2?FvGZtef`^3Sb5d2dErz=~()&gLON2zqEA8w#HeJ_6|mU zeqKg6z&$#1SyEnjk4%znI`eq)5;5{~@}a;7uLnO*2mNT00{mo9Yak20E9=x zH*IbG*Ja8#IKvElzfRq$-w|{TMi>~}A2J3Y2SOe;$l&qZ{RMp2X#mw4U$QfK$N@Ry zIL6|nb_cKHUFXB^igC?9_6EH^)^E72(2m3W0k56L;H~e#{X>TMm)hEfA?wib{{Rzs zX6x*%c_18&91&i3W?xUfSDPy8HY*HeM_#`5_dkjJC#GLpdA5-xfyeOtzo@U9b)O4G zV?WPqQCFUOd-Seqbs9(2R-+r-euZ|hS}w@*x8~0WC$>koxALuhd&gQggtQ5KN#beN z7-Go)<8yl`?rX(7N%0#`@P&-8Zwa`Ra1@*Xf6t|SDe)g)@lV8?vp%tMR4ab!CE*o) z0O%{w!eAX)Yq8+wS=4Z`_g1D~#{U3~n*RXB?-G_~FxWs!+gpQ`MD;lSB08VId--{@Zyg2|^w@LD;{4r3e^KHR1!5!b zbgn}F2fx*xD_~C61KSv{sQwaof9w{%Zg6r3u1`_9!QGNqp&!z)J}l}UM}%I^cJntB zUOuCt}-TTJ380!!F27$jsK2WsHH9W~j} zBbj9RR1M16$KhSH^CH}sRveswjJX{9mgp)P>?Vy*uw1>oa}(uBAI%$JboytddN+o3 z7`5FX#dRPzOPuaQkbMcy3+>Xmm<6S@NpE_HHyj-28UFx);FIZ3x3+CQIW=88{rs$m zi;c^W4?TGPRV@NKO+rg@?E0KeKr6c(j+~GH{&=r? z*EPs&EF)`X^43q4!1<3nsp;E~!?k=>@y_ObX6H`OEuD$Dm84*J;0>#vW0FOEzYJ21 z8Wpi~{#!4YrP%a;+3pE%Ax%NkJDaXEf&T#374?9M6tByU*zNVM7vUF&tTayy z!qc#SDSk!!tCRHP_TszEV&io3O>P!2%ak2(ae;x;pH9`k16o;>4qQxq(mfV<-eduY zQaIayM?ao(UoL!o@Ez8T<8<*xm>KOg&}3jTcHJlX&^;A~91mRAzUeCID4uS1@&`aL zM;R5|>bhi}De%m;k(5c~4EEU{^3^&Fo`Hr}BOPn>yz2)XS2Y#7*x`i*FLj@t`fLMM zmOC3`hGG-21GRe9-+^>r1-P5U_iOh-+P(dSVO@B8Q}E}Ap8o(zw)0i^+D8G;LVW

!VsyG$)o)+g>jB8cOu=#)1r5M^V_+uCR(zUN{*GFvf z=|+p>hrX?^L~yr15biC7#ixcLOM7Y8Vcff$AJVuZCZl|0l~twMK4Pj)2e};ueaGPTJXfscU4Fv|dNj3x;<)3N!PqS@Gk>Aj|;?@+*@&|;~UGPYVlY90D^qp z6YG4=h92*e<@bM)=6@9aDrtWbJXCy1Yls)hNVXH4ZO^CHyx;p;)Bbv`bzc?!9KmPf zDXr(Ujb&KV%=VS4@*Z4N-^@T!boLS?STf=iqSr=wwjJu0dU zraD!$`4<`O*0hT%K4zRQOnZGlm2TPJN2Wpch9}G?WPD_F=lqK0Bar>$liIC#vh3JN zZ*6Q_bYOnzh5_CFezo~;iy6zq;8q@<^Zb$YoH>MzO1#~Ui&VXbY!lCbt;fmz;&|)R z`d16CYT3JXw{a%l?h(e{W5?xM*B9|6lnmo>`_7r$f;h)MmE{`U{5IOc-9wm*jH3aJ z9+)HbujRg0+4s>q&+X5(Tf;1gxd_Vb>yLioyx&;W+C58h{{X677WE&Vde#n|;-dxp zmKV9lUcfeetDf;4+_Gy{#@ay9$Vkaw_I~Ld@rv3pv#{)iF|6J%iJ`!04%J*|2OS5s zN8>FnZ#-Mbca|H}3~e0t^y!-8E&NJvwZz~g)~-|q{{Uq4^~a@qHRhZ=Njw(F$1-F} z?ZH)Cv2WJ{-nukJ9tI}U{4Z~F<1I;iw~sN}Mx5oJ3R!*s0D%DYKGmh*{{Rh7rs>z3 zr-@m=wsm9XUG+X%k@7&K_CeF8PEIRRTCj)3-Xi-}?Ydr-CzEObJ0zJnEyg}rae@Bc zPiooOYLm(U-7@pv2ewJ<1!?Vcv64nFg|3<#_qJCkcm<@#Aa}s&Ua=L^#%JEdNg@rb zI9&`(Z34EtBOX@+>rQZuBINQ8jJKnLg#_*Xqu)Fyc^ z#T#jF^m*3R{#~VxaCsfD!}JyMkAc;p@dTEwCdMETr-kE;0zZ+jw>(W~H5(g?c=}15 z_$22(nd~d#o0w0BJZ#c=X9~pvk$^{R4#PFmhI(0u`c8Xfn#Mb*iBQY)cTtb8J$);c z(tJrLhA#vgOp%!YiAM+t;PL23uNBZ}Q_rYF_E{IqV55S-0gvQ;E0FOfoNfHmaG1e3 zBcM6so`hnd(^oWBJ?$pbb(oA+>d{CP48q$a$ose*1`l!9y>)&Xzc>2ScADkC`b+@% zEk$ffNtDg;P3X`aiI{}<<4{fa4 za09p9-iw0VuF=<;^PdwB6HBGbZL8i}=;%vgOm86%OyGbUBR$8c#dyz%w7cCoQMFGS zIJuTD+^R?;O{Inw5FI&g*cl@oum#3CipN3mC*r4t{3&fEu8E~yokfv{lx?K@p!Jcq z;B-GNe3_+OYd5h?tLopnxt)VY89O|+2RS1gV!ciLIpa&IR_+aY`t0x7mKo#SnaBtH zfn6&6G4ZX+eV%P(lNx!_Ou$WR;P9Wpv+ueEk6O2UQF7ll4C>l#zVrRuJ#jlh0L zBv+_i_=3w^v&Fjt&)nCL-TWZX*{&kiC6exV&K1)zC_9hLixvsmtC9{YZr@CUL4r86 z#oi)dsLth!bOSrPXRbPQuR3{pZ$s>`JX0t`maeCbd`q>kX(YE$9mWGMWj%&#=Pg%4 zhGsVt;yKujpkNQj^u>K^t7!Ie!dR-D5?r0Zob&@7Fh8w%&bg%M?`|TAT(d6S$B86^9k3VGuZi>`%IP#SsM1;w+54$TIY;xJ@^8PjSPlSJDKaAHOvdkZ~pNTYmz74(9 zO^v0~9kSf1DL`g{KXD($ojbEj!fsLXQVcM{*l$A8AWf5W;&mvtCCJV>((@_E>9@o{4W6G7U%aiK zpv8I5hwLM_Q#8ssA5J|6E7|mjI@WOCLspfKTJWmgwt|QwK3a|xsrqLFx7NCAcY;Y&NX$N7K~s>! z_(1;vBDq}x>I+ReCV`)OAkN;4j-URlS3w2rB^F5X3jvdidvTtkrS1soRy&eJt24eB zed42UW7f58dEAYTS%LuX6hK6K04zZdJb#l*u3p?;zSYLv6a&Y zB;W&_4t+f<>R%Yb_ehDQ%3U<$wl>BCu>0yqpy|)?eJjF#8|aCv{881eH-eBDn`rro z$`6!#=bm~2UW9Q|_Fl^R6U>qJ&xfJ^0Es%JDBfEJO!VLmzos~J=qHZne`Oq) zse8H)(-KhWRJ$Z7e~5?2-!(y$z@ytx2Iu(agX!TxQz}ECK9qrS*6J= z7>xcrenS8?2pBj%gm$hYNqdQO@fQH69lLa| z>F=Y=>g8DMR}kBpjg(~{@g~1B>$19$uZDClXQ$wIx>IUUY0m_sEM&;r&&%4n&kot% z{{X@j<1KO+&IO{yF*q1fPinCzfjnKNd^pp-B}pD^FDEd0iO5DB1A1o%A52%Octb?e zW%yI#TZv>3=NJ@$?%fS~UMptV1y2d_c;=NwJ4P>iR8wB-^gjOpgK&7L=Qg{KEM)s> z$n{6$pT>*pj}81&O=8VF)mUPaVCcsOKb?Ia;eQ{+t#~HW#1KdJc1RD&pS%8XT(`wf zhF24K=IZtZZ?h$?LpLkO@UPOaG`%e7zYpeoZ9s&NO8!sd9}zQO^=|``Ir7o{+?*O) z^lRjNj$KAD#n!00+~lt>wO3gQ`#DQ&(Sz{2S1YG#kR_D1F{po&I2q$7-;8}L z^CW!{;#%GM-dK%TWJsJi>Uima{(UOOgRXyQ*v9PGcvvvxD~xsN*RCyY_;1&^CYx68&?_V zI6vWD#qgh3k67@_>P}b*BiM*jb{V;l5!3iWoYhHtq{*EwzhQ5uT78DV$nn4ci;h3w zU3vB5x!X&Iv$jbkE(?qiwRs)zF;MuH>MIL`TVXihbHQFuMgtfXUf%NZTO_)Wh8f$Q ze;_*!Jt`vrOR?krExMi$5!&7^V~of%xL_P~74(FYeXC5eg?yEGL>eF30L9p;e~*5)=Sfwt#^jCCCW_QhRD=r(uP^1~X2v{oB3 zO0nYy`d7r?740Rn*TwQa425vZfz}Xa?PGQ0gPkmUp)L>(5L-k@}WO@K24(>aA?DJWEK1osC}Xs;u8$6e9ieEFzkN{^i5jU(t8#Oor(cDY@en( z{uSf@01PZ-7H)8S#w6_oVB_gtw-K2K@1bI;yK-&G{)dh$Ny%Js(D7YYOAjZMiRCw3 zaybXD_*Y%veP>Y8ukEzNaXa4JMI2seAAUzzqK!vCD=`53;0k`NYLX|J5N+woq4|F= zdgR@0b&E-~o6j;Z>*e{8lN^b=_?6UxtUra6f(Qg)ifJdIXW8HISwHwE$L$&WA^Z>c zeesW6k*@SV5;V42nuH4-jr^!3v;YB+k|IkU=?;9|fDfN&{9*Vz`%qYTHalG(;x?8w z6}M&bwFCjKV@1Z)Dtd$i2v-Dp99Q}${9y3LqyGQ}TmJxqel7*{YjNWnp9u@Nwy^;` zN8#hWeqoc3wc7mL9IFsP2NnKq(EJRz@fMpNzoM3Vd#IA)FPEPtMGmclgZD`7UhO(` z{hf??kI-vh+QZ=nxoPECYbgxQ+1V&$u76QuFTg-Co4(K;jW_ zNoB*SEJ+(#u%xCjjs<=*c(38-h21f;4b3cgaOwT!)yNl6MPGy+lyJXTUq2$vt({$G2xuy zxg!AMI6Z6e+6#%T!HO`f4^Mz-A;LnIZYk%5nz@M|-qsa64%U!e5-bjd7 zhS}TAyOmExXze4N(1F7!uj-rr4o3LL;J?|&_Ivnq`wV;`w71c09TIr$;E!Tj>3S;1 z95FGr#>6r&&;bL;P1w!NKiTm-iG;~n?_eJk61Mqtsj z!phqmaG>X}O7gD^X!2^>i_Av$>^p7$06w&pLgizt@Wz>M3`?~9zL?65v5?m`>Xvvm4u{bk&mL=#~g@MnM#q8M^D4) zUbUpdBoPq`oacrE=Iy}zE0WM`;6a&KIOAwyKMsPuW*A_%gh-FNL4prqj@bUaDq8_G z$5}+K@T>y?KqHgw(?6Cg<M{CPLlk_u^2f-We8d8WxAm!>g+^92AivZt?phXQ zk(l5w%ELYOlm0~#L3Lpw>Cu25dthYJq#^#JR zX4N7Him@Ezzf2ELTCJ&-goxo7I2-VNdVK~v3cn=R6SQ*79Qk<5cE>^2867eFtCaBu zsx2Tg#!*7@SmPUyu=-cr@U(rkcPT9*n>j1JERN^HI*8M={{TGBH+-P{-%rp|ohCT% zB=SKK&OF2%jC05zO7p)DUCVK~(U+!j$G_9CuRzx|Y4qO;O{`h3pEZVMlxO%qi*M6| zUq|8hi`kZ0OBsX0S;nk&O?KMeYoXtSaHCokrLsQv{gAcIGsHe0*8c!!zlkx*nm36d zmQ6l(Lx}F9#!PHRG7i=l=t1?aUqtZUqv5}S{wc84V+nC@s=;j88(ZXM90S^wndZF44xE1fNZD0^b~8?rw49Coi))x1%z_`5}yUDhOL8nxQZDL*Q+ zg}~r&FaQ{@jpKh69xsP$aJ~kDrwZ{{X{{zBvB?&sab2qgfxbC+(5@O#Bb{UY-Z|StYIZjxJ@M zH<)hP(rw(UkKxG3`ubPUpYZaJ+k5_fwEqD6;Qs)Pe%F%>O3QJ6AAMe**0A+??76KiFKun*eUJar{Mw9?gW9GXFJ6_BB>DIpjy)>cka=$2 zmHm*B_`U3EN052NZQB#Kk_h&$b|>67cdZyDVa9%(bfnRxKQpbjP6qDvZA$d*SqV7X zl6qpaW1MFKg##JN%+`rZ026~<4vk~>+l7p90Ldir#d9{&?i|-kHN?7Zism)*8TdPT zfO=xSAH)p%`#Ud`x1D>BsPX_7> zWpOWu^$LKVHQWyi1yx1Dp5xcqv+dHWkY-l7V{hVZEv@6)SSi|i zfN{nT;m&KrCXx+vPJItleYe&HfIMdhpa;{ndL6d3(Oi9o2?CNxA-Wv);<&$v7B@Of zvG|_a6Nt8}fEN!NeeC@QYSII-=UNW5`diPYUoIklmpmWS(!Gb_p6xmd%{B&846}*B z954j{CJCa9_U4%Ld&dK`L6=+*tyR2`FOzKuNlQ~9x1i+;F33DBQ8j6C?IzIE1%c4RUU7e7Y&8Qq0vjWa*zfpzSEEaCEG1~AETDlQ z+?|NWTy_4HPg;R5?W6NExUV_D9@zE+wP{IQcUC{K&-ga~0OBRDkN*H;9~*oty2Oz9 zU&U7tZN>+es#?K%{CmkPmHcFQx56{{595!HJ{jF2Mm#s-eRD{F1po_cJx=D{+zfW| zBTQHI7ykeT#dwNK9dF}5!}d8*28XJs^)h{~+~e?te@gytzu=?IdEn3ZDaXfc5+UTQ z9yGi>eZnpvw!8EjbLW0}uVKOaBxh|;6!G`MZ8G{Fnk8>A{pBYZKS9)E(!MY8x9sZD zAMGs@1Z3qEdyij3-|33|EzmqgapD^|UhaHGN6brSEsld6bJO|N{ZB~JUhSG&aO%T? z>++oA)3<8lh9$sCC*owk2z5icIEKQbBoW8?_N?jt7;Bbe{aec3f*1U&_JO<;;i(YZ z%V?WJZ~+)l52s(HVqSPF!|5YU6{@2ip|j9?cdppuQCz3wF!*Qj8&oQg*_dMhZ8H3$ z9W#zkw?ST^;m_HZP`H&P*C%t5HzFwj53gKT=n_X^q}>S8D8m=($_QNJxc>kkS5s#! zQ97F_(KmyQhaCQ01s`1OD<89;@N6f+T_?jIwQt047g*W(V@B|Gy}z1Js0UEKY^W#x zy?Gv(ug>rIE0(Pfia+3>zZP!FH=Ajy+u9*L3~=ezojrkO1K&0M34g)5+99|2ckzSA zcwR5G-6XPu_u#c_wp`-`0d%~I{%Swqq5dX|#Xq($#Qka*%11wmF2QV_#KX4V0Cd6f zzMj>cX}&}b$=Lk!hen^w*&2igyLSHo6I$jd?Cp0lw+Ec#3;s2`0|f$P!vTfrGx}F0 z;_X@+dr2_DF^u4I+M=6mq{eel@`m zx~{9`ztrda{uS@u9%yYLU%Rv?UcZp-ThBx-&YI6kF9(?vZ;zKGeuLZcrfN0|CE9r? zAwiIN2mJn3zi!t~f$ai-dX-M2)1LLs-NhxwtRQ6CKp!)n2cW?3SwIu9)9CiLu}UIC zxVh}S4?qodPi`S_^N`Ft0nmHXG`%73@f_BpAh5QSC^LVmLwj^$HM;X98{{SkgG3*Uk`)f>CWz!=jS1-4#@yF-Z zwQU8PN++H6+rTo+G0Xn|3i|cNN2e9X+v*;FlVfa7dJslH80C){z#rf${1aQ>-hW}~ z$_=y(KGigTAkW^|M|NS1`Y+T{lmvE^`4LxovkdAKnqS`>LQE^x))i#Y;eNe--Sn zwBH75X?gpzEM_viADM_z)9c1KuB$}UR%=KtrjeY8)p49IarEhqFaIVX}uNk{{OxdW=WVAHzuPotRsyxmDmuQBm;%ihBUx`3hFpKK2NV>LDZ0Ekykw3bwT z$gi}q01|r-PAksf@dIjCiqaf_c*yPOEA@PAA(drwjNk7nXqS=asB_QB9<8IsEMSbr z6}m21@=tsZ5ffjo5AsGKxS0rv&{w zSEO1`J*Y1+Tz%y{a5^43*X220Ezrb5ta=^Nrkb(kegW{L`dk{mkb`XYGZjP7upf6n zhAZtq3I71XN2_>B!YxMJTSBvdK4G!j)pO8_@vS>Y)#cQu)->pI=PEqAw(!fwPCwbk zE7CkEuMJ;Yw$MCBHo8=~kUrdWog(sOzKAUfUl@ z!!>D9r7GB~$4xD>{{Vn=-xu{C_(%M0G5`xLq;Y}|W1mX-w(iDFXH4-G&YV(7mdHo2 zj*90TSJ6MUwa0*<0amt*-cE#6CIkJ;6vVq51k@ z!sPV&3jV<249_a!e;2TrX-&dZDDtIdlYEa<^xLMEXOSvZaCvl;?9+GC&!?&Lg|4gr z01E2T9}4N?$chA0w*#phllau%@UyRf=b`+CJH)ybw}`w&d#Yh%N0fvvdD@@>SikUt zTK@pgDC^0^WDvvDl?0*gyM5XpB|Pp4**Ete|I_@&j!)vL#~5Tif6r>h-p3waKEka> zDeK$civGwy9jOJ-*|&e2CxKdVISt4owP0Jy#{jQR)vpZiwC>0sN~UENmgdA#wgDV} z^{Yk;ru(?>#bL!EBq8rwR^>W%&0iz1G@01x1l?Ul7h-v&@qVIo^evp@g%SOD1ZSGD z;iDb9NpUL#R9s~C&3RXhF5doJd9CG#1qdH5eR4g!SMtA$`OB@3SXk`^__OpL7{U8G z9@Dwh_(k8%+p+jt0ejC02r z^fiq)l=qhw?0WnzBo|JXKkB#x89lr4)B07$T?A%$ zi%FYFR&1=Rl4BtM01v1mJ*#g_)hu;=IO*D=?p9t2&J1jX5JP{TN_Vi&Bi8=e;eQpyquip19f4OJ3LXbNw*4!o(KM@# zH^k!Z5~wb1-7r4x?hhTh3bpZzLz7+5f3|e>A8ONpblLnsee92?;am@fykIqpYfFWc zmW`EraqGvWbgy|F?D{I|dy9Q6K|W**NIV9|0C0aCab5)`tS$97wYdT{QWt9DBR-Yx zR?*9IWx4PNZNJQ7zJBqP&1 zYuf$`0z4|A$XtQ)o_dbNboy7${x-k!G~7F{%m} zaktd*)cRLNv?Zo{g{*fdn1z0I87+`^djZm~L2D$|1YJrMc^g=dlw*(wU&^_-FD@ao zbdFv(1A;#z$FJ#KK$0nyJwJ?FKEDu*k5!(sBF14;Zf@_=l%m6rFWg({C5XM<@sg0#XuFLFulM6Hq!th0&=Z z-5n#OVIu?-DW!AL&FEB8LK;SQGhpoZe&4?tyLMgM_dMsh&wW1kzwJT~e)@mp;hc$K zsYKuq0O_s$A823tSyNBl<0A5id2PRAM0US^Qf-8%UqSlV@N_6NnJfqT`$tg5@wOp@ zhyqItMP=Omyo4^$Ed-?hw=G~a@d?Fp;Gzl>rjRa}?VL8Cl67~o{((**$Qo&s?&l!z z_L>dQ&798}`QY~EQK^-d@EY7R+XS#*WmT)2#O&HR{9W^J04;3=HTRI(C%D?bE!<_w29iuM#+*c~H@CcN zP%K2JE4~2Up^kHwDM@LasL}Whr+;26gvh$sXjS(_djP#gL(z30n;M3RgN)UKp|Ps%Ij?Nmc4` zh94qj2$DKtgRo9@z7V?W(_;*$o;9I4+IMkzNVNUeeS&U7|5B*SX^8yp z8-mXJg;JO0TpQ^I0}%=jtlk9&1yEVeoR#Gg%xhWeeR7(~vt!>?KgomOuM>RsfcM;+M@sjotC>>E}3w< zrFnb6+dmuw_Ws>xop(|NqhOS^4KI~eV{J+0T(t&*|+c6=BX7#!48*iU=t9CIFpRsu5 z>mjD3S*7;Te>GZ;Gna{mp@N+ypAx#@aL$8RF^9gK*TM$VO;K*gX&*M3Npkkbo5AFQ zv;4z9oK#N+O9@Waz%BCV!AkA!8Yc}$k0&rMgt!%>brDexJ1Z5C5MAZ;EPZx8&Mi4R z8Dz14Nm98Fb1L-xAoyOQ1QY7>)?mDMziDTOf8#Z!F`p3hA9$)*W{U$U4Ukx$Wk(7) zTQMdNx+d<)Nq=m6eDwYV?m$5LF(6#etWf^^Y^1mOBC>P(@=e2hhpubz-Bkt<<;)o> zQT9pEkZs_mu{*Dj9$Vfq7WVOx#EU>wfmv(l^F*8Hf=(od`5qLNIe0+}bk`1CM`f*WT8zzU z`^Xm=77sbqTVbQ*9hfAub3p4_oQ)G49K^$KnJtX_*r+i=x77x&B!o)4KVPI;Xy0Vp zXv-Qq!&*$N)V`X(D~X9;^X{`dc4Y5N!Zxo0f_Usfu)QPSvpkZU32XlZ=s#i#U?5gr zI;TXoB3ZQ--xum41%mH&>ou0gjp<)pbTY&FoVX$h{DUByfHFN@TOaB98@Vok84LqV zcEB9JVxy`~L?z;5-@Ohdl2G66E7*wi21K0N-hb}nw5ogI*6EP#F8n*e>BKmYJSMBeEwVGO+zQoG7 zR1H6L8=4i!zBIWNI!rfZlWg^$i^w`gs*loP#nHSDc6 zrEIxht-a&i4~!M0B+gke4_C(SldIj5SRu~*?RS}<^4vRrf<(33)|LN2SBJo_190}8 z?=E-4T!D&ryLO6`lxUZ}pDJj!psXxPI&S*HO{@CkUe}iyHHrBB zQ>$8ft&aDsFAm2m*)4L(m!?qNUiS+N^F5?F#lE4K`k-t2pZPO9JIYkoO}H~ zFJOHc{Q6F*XIu8>z&a!mSx+m*UaeT{XuUZ1={}&L+ArU%LVk-?-WHXIkQQYNc0%bz}n{uY#Ajvq|c;v z-{t+TaN$Gjn$smhJM2e8B?a7e9wb@XdpYI|BsWD222(j}%h~^xU2B_ASL`q#___o_ z_^tW*-|`Lxydu7+A1LYubUKpuh{xG$2+jOVmZ@S<= znsq9RecC?TX7GG_ZwBl2ZfF;B<+1mgeo$fg(sr7eZOUM^!!X&p>z0h4SLv6xqc$4F z4){OmcV2f>=5BYKZ*QAZE-3*!A@KOT35pKKic?uW*BX{J6u>dPV67{>gR_@w20e6~ zXCPtQoN9BzUam-a{sGxPa#?V-3K&@kWgb0X`|KfJ$MkO|x}55#$9S5N1Kfthor~$U z^yL8N${!Zxv&dxG{OPO1yJdvvVY0C~srX!`|H^G@0V~N}DI%6iAG`?#hE+zN1UWqz zHz*-`Vmu0HYo_l~@~v_!G1pZJvUlDyD_F?5khBIqJNk)dW;B|SSWi^Xu&_o*ouiU! z?PoF+SlSbeK>(I~dn6)F!Q-~VwKGt70@+!UGEP2o@hp+7GWHo`_#FfU$LmJAs2ZcZ z{rSK|s(*ik9eBKKpL6iA1_jY_vRq(rymrqMibG#2%0)`y(NPs%HAI=m%HLeYql14X zyZs`*JqUzScvi4?__YiaioFh@m)C}0FSPPbIF}X7Sc8165eto-({TpPw%5O>M6}kU z+6J-4|s}r{=)s6wL=yEzn1OfJ{j)&sk|4*Fnc!j(7Q6;tMi%~(AG~#`=uZ)Yz z0q-xMZ$zFk`#aB7ioen0_$$ktM>7LhpX_5NiB6o=kF&8H@MPfr!HHE^XGI@Fn(-Gp zI1NGN!0Id5U+|Gz$qT4G@;)DYm*n&E(sL3rr%f`ddZUA%$%QCIw#`$*YQ=hzVagJJ z)B?x@Lbol=k@y?1-LNG1^j{MJja&K`#l?^GzaNPNU4nw3=IYx4a>cLgI#_rUgc`w_ zwjAFy&vFu5?taX4Yv|{!kg;BxMfFsed?Qq#_|QKq+ZBv9t?f59RXP=eC{lWPUb)W2 z)Vh0prJm z1(4{s^o;UC@5sSlC$Xn-+9AyVbJH_niTt9dA0Ik@$sPB28vW@z&`|ZuKn@j-XD{E7 z+dj8l$9%$_&X!1%sT6NN=uUS|d?Fwur8_@uXvwl@%tY>>O{XLX4cVS;{3EkRe>`AU z@=rT^0Vn()Xs3Kqjcuk$=`)C@m>mK(n+#VY|E>X=VAJ?v7lYXIrbYyY`>{67{xfiV z*?}Z-?FWIim+~#PE|Qh;7H{tL4Xd&fO|yc^Ta`CH{Y>Iac0XrTmD;aI;%nVbWxniN zkK*ZZBG|~|rM@Qb&ZW=N{Jm(@j*Vk^e#yogTIMx}eO1g01z!%H*u`A~#%|GU9M%H16cOlaVndyH^HQAj@Q~{#5Wk4`g z$IW}JM(^yxQup^=TUl^17S^?2YGOJgG)R75;b|hDfb|t$1-)1F6e)F%&m6^KxU?S= zz(DF2#PWi1w3|KPN|o?4ss}2o3xd6D%*UR<6?tL%sI{3c0$daJ`1E7^WbO@$c{zK9 z5Y5!aW>mfzZ4uq8n22HDwNa50jnTy2+LXD6|ThJ7$s>B>Bm4N<-hh z2P&gz))|@uo1NKT(pZW^ydx8XZV4`zy zi|ucxA9xbKo}P-bmdNLwiI=gv8eA@gJ>Z}2`p&zlv$(|2WaURC{x5ojtpO+m;lG!^ zPI?tx5sX;)dzH)z#FIpN7*kQHuRIzje#71idbcIo4vhWRWHm}3C-+CLLC9Q_{Ji$B z-|_eBN>S185~}zF?C(*5*j}(T9Gc09CGRk-&Wdw5f!IQ8&SR@4tb{22>#q(?y+h6* zY@SIs_nRjwarTPCo1M}uAYV~JjP1p+e9Df#1hry7V@$*gsFx4ptsSfIdxyF0;|C`c zHlN#78^H^?sU z`sK5;nXOgm{Vgk@#NOVY&HLB6HIt5>j^3|RugCsb4NtBiK)}<6RS`eD+FAOB5nOC1wJn~ZPtjS7J|eyLn>xgaX#+CEi*5AbI^5{p@oC7hn@`Ev${#tin^tqB)5CLr zIl~Wy+^mN>XE|FNtlzv40(R%tK&KQfJ#ReW*61 zM7Q!)$Nt-UY0QmusAu61&EmlzLAm>0*YCB{jl&sjjnQBMVUrKZOdnNO*DAMoXK2V@ z)f!}mMNK~uxf|4$t&W5B&TohbQFoSa00lfykbL>jc-BLOiT^U;eRZH`2aM zb74DgOqaZELvX1o4k|O5}}dO5!HBU zi$SJXG@Ae`PX`!ct4wcMqte>4YNT344=**-X z2TnM@&^|gs2tj1wSzkClS%5`fqW=ui#58*$9nRYK$G`d7Gen7ks!o+BWkNNSeahp2A7H9ESrd=jbXzD>an#uj8^KDHn zHo0y}^S0t|U-*?y751N^7`Gq$9FnLW#-hSOejuI1q0lG3go{$mSH9+FrAAOUOT(f$2kGkPRW~g z?Q)^R{4P@_On?IHbHupk%kP64ALBwrR@#mo0)>(Jp4kqc*7VK!PYC)ssOKPi(9VRB z2{@MhPuK!*!fLe9E$|SmFPfocX&5wHSdk9bunKrbchh`T(85KNm|#zKxLE6;G+4yG zZWv0nZlZ#tmYDL-#&YX~=sjkt4}&#uoCc@T2!bzXO3jbVV;elpv0SOO&O%%lS>Ums zA|T-e0q`fbxzf6%+l8n^+{HqJC~Hxx+S`x}pxx!QO=L3IZ{ISWm@SyH;13w9N+nNX zq|gs+_>@Uccj_K4g*n+BFBTIUzL>I|e^30H8S6W&0)!%}&wMuQ10OsTYYt6?kP2U~ zC7bko>4C@bg0wOHfKC&n_o2d)c%B0YfLHdpj(My425EKAL1S=S;}Q4Bpps3tE3R}Q zh}!nGS%AV(Z;0z7X*=l+=(w8k4g4QxE}#G~h;?qPwWy zvs_^n?fDN>tJ8_RztA+m(k490LD7}bQPklgebWsnqVd@;Vz=P0{{tPl%k6AsAL-0^ zc<3$ou4dj9W9^T);MOM5NwMa*=6a3}i(2GzWP&WtTl!HA#o#B<%R7P$3o`)^u&|X+ zUNzEbF#?(;eygk^$J4CBrvxMUa9T&6PL|ZL4A?MY{@_Q$-;+OlA*dUw zb_>6K_clgUbz(w%UrG7zla^o24HF0Z-+v?g4fATV7|e)XiSZ>V58(vj%4Dd`e`Vi+ zFE@c}bvgvYW_|hXPY{op_PDwY zMI6;{MK{QP`YH+)2NipLwh~wcsc0B@Y9#A@v5Z|=Ma|M)gif^yVCRkF&CQbcz0n_O zqApVmlGy^P2TpouIG1J#gJWU{DJ7R%o-%W0;L7%URhV^FLd>AY%x_GUdSTO`+bQbJ z+jfFJO!}qsXRpdks>{z25FR0y7uT?y@%6j@T>c4e{euwhU^!RQJ1KqO4;daiveg+t zK*rY)&3Z-RGYo)vFzP6*m)GbwX1jvy;$OKA5K0LAPWMcfmi%JqrZ1wc(y$#e+i-a2 zlTr_qmY;3?q9ea|+Fj;IGO>c*ZUC5fU4)BN>CX zn`9n-Ql@RQr9Ri6>|2$YF z$mywh4pddG(TutiQgX+0M<~cR|Id2o#FIY8W$SIT^4u8{rl+Zv5tjXCejcl3Du%Yc z9BBv(LOsUzo6_@Z{qA}ZK8GQGlVc;$3GLA>q$9Z`IPo{n!xd$M|E9#C#WE359&o@!>DK~r#F=D5m^Y-ebc;1>cv2Y^JAvmc#GuyGR_ibdm@R8}=#_zLw7cDW2$gv*c?CMb3?i1({tZZBKQ(YdQn)~CA6 zANtcqws!jU_TV3*N0%`>3h&}R{R2kS^yxrW_sD-!xID(*hXjbl6gvsV4$N%T)El|` zn$;h?!ps*+I5BqpZN_#MkM6EwuHLD)Cu%FCl0pW6<+<|NZZ4TEZ#;RGnxNKvjKRx1 z%+i+Bwpp}0pUaa}#iz3_xnNNuU~Eg&HRWhi(0eQu>vv$4*;lx3Fq>8RWA>#pjU}&* zJz(83*G)zQmSW-ExSJa#@r>M~z6Epkv){?&dqo_VqUWu0;LBe!GLBr*M}a!|7CS=! zl&mTy=#Nw#AWTWoS?HTC@)R_9KBLDyis011j*%~4^Wb}%i2`$PY3l4!4} zdHNu~=*$;?HkoqUx7*j%cJ%5wMKX0+q-T#Wtmtt=<)Q6 zwN1wi{sdf;Dhsdg_0>TICcA?G1VZD>=zyG=&AQRM($;Y>^_i5MD!Vk71zK-#W=-Jw zO2h3d_obtR&T1DWF_rjvLaNHOK>O;f@e!lsy^{xuVHxXgRA&A+*0sL9#XlMI9g4l8 ztX-JqNwocfSako4j^B2OW)}vbrn640zGt6UuKUkS9+mrgX>e>91>2;<)CL-AwYhe#ZL+3J4&xR(7 zJH0-bUnoB8k5&6vQ}OukdwRoOn~y1_{Vd!~)1vvrS%qhJH9yMqp0fE8&EbU2rF=@e z11NA(S+6cDN<1b9VrA=BVBJgrrj`Egc1p_cJnpsk1q2vYVe>cG#fvxjFk42> zh=tj<$5^cbt-=8Fa9US`fa6BLU=pzU4ReoUv!?TxO?GS*Nu3N#>r?)(33=aRmfuJx z*!oq}va7igPAqB4=e5QF2`76Tke7@~m(_BuTP4;xd>{%qg!6)E?1Wo?IfSl!*@Jqs zecjeJzb7gmUDGTIt{SAXj0*^V$42!-Hm1nnYMZdqw43|!a@_I6wx&CW!q32>GUX|g zBZYlHo_g57mi=^=rK!TbJ~=z*dO962N=tvpIucAOuXlQ30ov~M+21w5b0Z8rHt(+| z@5Z_ApO;Kc5X%01NlEggO4@a#RjERId2>D{nW6_z%KL|?0?KAXgW zQa^H_Zt&&cYq8mB+(UQG2EkSj6q&uZ*2E$02f~Z}jjWVYIF8=iP=^}m4^{BoHC4h% zgB!zt^wbvWn7NF}5y8%~uq|^Ovw7Yt6n+XyBdf;YuN}T(%r$l#-kTrz_%S#??F7{e z$8y!j|2)3jF9*_D;e4^KtM)S;W({?g4pP0+N=Undf<{}$fraevvw4g5;h#lM+Z|kI ztQu#~?jqgZLS)K5yZgq{-N8=A)-?pl`&mOK!Hr20Y@ggm-n6(M$Q5%m(OK8mHHHld zmpOnAV2`gS*nqq%J?bJU2nMQotuF^?;HsoDt|8mn4QVpE5TYywXr$au_nOo8Thjq) zI?HvW=7&CKd_CQz&K`qQ?QLxk&AjOT3C=i=?&_CR<4dXbUb3u|Bh!+3UvPjhD>D<( zT>3Tnh1W5&5A-4i>$qzF=_mBVv2!G13Ro=BtJOT6zbgZ4TqbMyk$bHLh_Xfd6xsLi zRCcCmJ(8|rGfv}z!+<(Ru!@e&{_;d>1!;$$g)eU=`j!R_W>X#v5&=jMK=972bg4Q} zNWH$_*el%QIVNK5G?K=0LIT-8D_g5juv-7OdZJjR%AAaY;8Q|mt1v@gkAOCy45h>; zu!|(t+k)2oZ01<)v;PWkCR_2TvCW6NkF3hwAtU{0iZ2@ay7GG7b%3O>zz;@Ox}-*- z{Bb7-JRv8?Tkw}=5rPiSV>yy*==cu=L2(|}<)zM=PH1E&J?BWCj&qG!%oO|PYSS;7 zb{BtP4|KTvdA3wI(oMSa$S!8%_Y@k+dpL)%H=j0>%pU2kJl*WuW^rMTR{Wl8&7q33Q zQes?Yp$w+*Ldj$83yc+P{ZY+Kclr);Su7lHmNc`VWLvdrt95k{w+0NE@bq+J-$~ir zmZmne7j|@uwEy*ieLc>?gSGDj#T)ayr2$4F`<@Fk+a%@SEFQ056~V_bzvd~6;T(mP z`@C(}Uo>-$BFn5|kVB%`CXJu|J|xF_55==LK6ydB<~mXbFZa{V_PmpK{~iqOwY9b7lF{MH z)%7x%0I3bwSnF+M$5bm`SgC^CK%4c2006X4wvJigqjL%Z#0p}qHXa*!_$sJ(6S5f! z%s|dzYv=AwDfTxu^Rpfvm7~~*VdC*Sw;dxh`G)w9U{@c@J-{(jQX5JR%)%bk9;61;qDzQmFaj=p{rg>n8Z9Ykk!2Y@P7i0GW0m)sM8f#A(XMh?#Wvpgt{ zYglIb^gEYY9=RlV;#fpWA>-3cq`2vav+T-kNZcLf3yP$`TWA6ADwNj8ef77tBmeBX>BQB!$*5Thzq?XcNDvavf~WXQp;r%M>qPb&s=ujN=>CEWj-z3m6NRRG zifMR`5pW@63HT_=aIv4`h9{M%O*DNHrs467{gk8&T*KKdNTEZstM4}8Kt;lr@BTL~ zY!vnnPl(-LL9zVnakLky7?goy^Ds-(c~e0QtjJ7 z`e|0nEBhmNy%vdU2iMsJsz!H)gYWFO`9w%7C|MdrCueUYr{bri@be!9Bh3jUsVXFG zas; z9aVN%A3klN?NvYS*hc15sEygbRkD6Ne&%S>LfcuwT&zh|h%s~VkQuqJDcjK`nwdwb z3g}=k9e3$U8d2Qx17;G6JH`{@ypA88_lgx%ob;*zityKvgy*f_Fe&p8jwBsM)?l#u zDo$(p|A_yjWz+a66$oE0x&ftqOcg260^JbbvX~=DuBM3VOX0}!!c5G?TqPS$q3Bn_ z)RooxIm1Q*{3j=jLY$^omOkU?R%7LqhY$B@%&Lp||HZ7~!;ng*RIy@=|ADIIXI0ru38j1cpJsk8jNhZ^d4k|(NAIO;Qe5BLo zqdg-%J@1B)9=KBta_y?8>z}WAp%LPGKg_uAggtoZCJqtkkgj)4G>>oJj5n zN>1B8XSyhh|9Eomd50%g7nD<5`WPXqCY*GSBL?0^N+x}_IR{=bsGssp$z_S#M2Idi zVPf)ghMPt68#;d~zL@vHuu>8KO;^sl)4Np{-$1-tRWtNZ3sUzQ*G-$HZ~!0)jJRl~ zt~dIOiPAhzzZ zS@)mna2Sw$A{A;KG(Hbx&8vRwjMi{srG@&u zQ@PUnb}+zGr2e_1kJUZ{-5h4$ETQcp6ZB<#Pb9V2Z!Dk{WD@EhM0ffhsN2y_x#ot; z%IJ;ce<0vAnnt~zL=A0dEj7i9%%Kwq+Z>L?*QO=!Y)WySfIKQn9#BUXhNm7pBnn-I z^W0c>NlZNZqWBo}8rqc~;Jw1^T(^5M8nVQ>#k+qioHV;&^o+5WK@P%NT9CVSCwg6| zmm|T~#I-uy8C6*8mY(`UB)5rmkcu5~#~7V1Lb?fH)fmiLE8odNt=gVV26c)&(aAD@ z5j(fzN4EauWt|+RYuNy+qFI#^@T;^jVvojnV?T~Md%V~#V({HPa#3L*xdNIh$17?$ z{gA=nA)jmFk}LrrE^m~JU+6yrb5u|{MPBLanuf5?$Uezy*O?Q4_?Ws8cAEh#_dqJ9 z{sXAfau5Gk}YBDd3POgE>w)TKzmvGmiL)N{({HSBHD`j&PQj1vg=$zQ)zmf{L}h-rK5ej| zpz5M?lVamOC8y19Qso3)R22U>HTlYHS8B7))V?L05#*`b$)2LwkuDC;KzS@*38;nvS^E{OC1At@qw#P$k~pd?z0M(B<#PZ8dvV`NPB-2<_~%Hvq+E z6bGM||5{4gelkBLnbklXleRAtfk7&mFLB}2xfWT3tWEVp_;h7V!@?M^wEe)R;|*LD z_m~m(qXCk{T7|MnE*kf9kn85(d>X&CNT|I{^ECu3bzwgZ(Sq5BYcthNMRkn}tT}a) zdllBKgYU{rzqR(JFQ@v(NTwLL7>vRB4D;r-9d()}jU}Vkp~52$)&Q>&3lnCDgjOxg z0^&)6ja3#z76dQg#UeJh!)ww2o~h|r?%h6_(72G%Kshc)|E^!gWrA$K+eW+N3(@Y$ zx4eI3HCCJXpR+r^j~OeFwCO zsG}<mrQSwlqD#pVa z{ztct|Dp^J{sWD{hzhS)KO;m>is=?={{!`X3dT=2O^l0cWf`^%E+d$F zna(t+ld{UT=Sh&YB^hU` zOFet`m4b4f)?B?gqUM!pH*y6}eak!5J50Iw$IviHfsw_u1A*xlUdsrH!vK3qvaq>q zGJflbVF~B&19y&bQhmSyKz1eB<@c($d{r)?q?&6{#I^GrSPkI0F>aU8xS#tHvv-ql z$-zg$e~5iBpcE5>~}u4z3yg(@Gwflu=Jj2Xh~L zv>(8w*Un`IFn8X+T;g@hJ2T4w%=@l{FVh$pp4=1oK=Q-68EO4aaENa%=!n>@Fo!O5 zHoS?~_`yJ&g$+Ao9|2Vl?Od^lQ-P3>Wcdd%(&+#@1K+jMASU#d2m)ejoZY>BCU#pP zT`B8-b%_5!I-Qk#wVzfZEoPY^SN>rW+c7it#cL<4C$Vf9LLF@1F0S=05;3#j$n)4G z#BBaXrLFfzk5BmF%7qRN&J7b@Dskm^!E1Dkv+ZG_yfcC;_307_#TFY-?R zQ8A_1+!%{;a*n-p=8wke>V~XmMDAVW4G>I8D6}&R z>HgtK=!zl7ikD*-7i$zdbg&fxWeXaYfIZy=(uY<#tD;sckqVP^bpBjV=dFpx+del) zpdZL`TCSS2_yif-HzsiWGVMGv>0B5+c zEMJtzFl3en3VM$dhp5ao;XDyLAGa9Sju;pjQK@Dkxh5f|lu0(b_Q>s?7KDT8LBCZTNhp?Qosp4LsxSQktagR@$ zNUBA@X-1m3$pH`j_GgdLJa_5yF%3b1=kjj?(1-7*LzaG@n9y7IqdpuP2+u7GjOJMS zSs$SurhHCH%0=mPHrw((mSbPh&ZuvDD%#K=9~9bF+|pF5TGvS$hgy&WKE%j>Ad=+= zM)OOOZ#rPCLAv-?ejiJVdlO2P)f!R{F=6 zF5OLd2A&Yp1QP5A3y-(?B33WEvpDO=_WlE{=OS}-hdj;*ThqFwQ^K@PGE44KvAp*x zDrW29h#2y=TQAUT9G1v9?L*sjCY!|cGZn(QxG+aM_pV`_CD;k=39C=JY@U5gWI?Y< ztb8lzd#8<#+Y0zkKXc}6H5P{hMco{JOn=}j-;*0~u(V*lZrt_=>xiLZ)D)~0u~1>J z@MX)P42IQViB=+_S6ZR_>5-|M?3`ZrE6cfhya^43f*gO*ZIshkZGZ&M>vUt(WoRcMVoEoJKSquT&UW zrbVG~X1{AFv5)rwD@z=(oI<1_%<%Q&l%-BSQ}0xukRo9o%*ZLN-{Q}?Zw&twf>nz)nKe8$Bx9=tM-md;;hL{ zKXCU5m6pwgec-7uWR~d#2@9cLw$tR?%&x>`D*O}4A6bD#p*%O0QP_{ru7#Q5g7Cx# zA?eqPP+_$hZAuxB)q0#pmHy0Hr7jjIl6$Y37bM=i9VYw59)lk{BDrJE4@APqvPhM5=`rNvTAEp~epLohulUbY%Kq(o zJqNu!2G(~*E+*4(7qMrkSXtj?Q|lFYlzAttsWEZ&^*^(_%!MZvqWhBBXS8-Vdvn{= zFk~-RynG8`|fD7bC*@2g>(74J&MUb?2v)a61*?(r5wARAXmDz#I(c4 zi7hWh2jduoe~kmosq+=a$5hL@aSUf>!EXMrG9TKGESe*>?2G$6cVZ^BF|&p!=@MRN+tx`3JLo&W5a-U9->1%P6EgL)?)jzaSSC_T69tJP9_!t8J=9#zuT;K_j!xcym&7@Aaj~5Ed127CEEntv*zY~& z1ULb`k&xBK`b^Op5?f2B`DhXNA#f{L-)`YwjlKQQ@zZ zW7e|Wrwe^jX69ert|2>tpj}gykN|@JbIQt5AvH4PR+eMsRv+~_TqtrJNU0oRoEZiy zhSSA9NDe&=k~P8)K=pUg z!tc>loEiL$-y41T8?I#_0~$_yh;D?NPa->R?eV0gM~go146u=gVR8X=wR}Lo9Yol2 zi4vYfFGsVHehG&&`-DDiagw-pb-txVj-!9fWd=Tt4qZ;7Vn?20VPBT!P0Y-r$FV;t zD=i+tX-!`LaplIT6vI9r(M-P4IE0c~0+|8KK0F33Xspip1>^38DOA?$%6i-25`Cn^ zE8_sb!vd6BXQ7*$>d{ePgLQ>#*qMfny94_RR%Y-5CT1Q{N1v9_sfp5ru zJIbQP2jXo`pEv^^Tj+6y+kWIKf9_*hYn5+4z*gCC14{R38b|$<%L^4P>sX{+q|lT3 z<$=e4Ai$o?d(EXK@`C&+Le8f^NuZd1EA<(f3^2|qO0_X9XG&kmAU692Wo!Xj5Mnbd zbGW1Wu3;0%+M|BaEU1*i?v58X@CVD=uX~BgKPU@%Du7)PTIOK5BJahcIo+lXezs0I zAGY4|ih_Ewp(ntj74E77HornDO3{<-O{lCNMxd9!pey@6BWHgeIo9R!B&?h>Bwdos zn8+02?gWbe#63Im{#KUd%u6H_MdREFkbkmnW{)!L zO`~LI#6bQ7dFQ+7P``oGN+9S{#1CExzVu~XiuBl~>}57MQh@+3kcBPx;po^qgt5-6 z0+}V9G3@9G@4BTF!~OO5YiArJ=AG!Dr2DH$FU*-XS!Jx#aq+-+R*6-f{SUOV8QEem zaI?!h;FwmR{)Y{BOAu{8K2Xvyt$Ky$>^ikry8(}wphW+1W!6!IpGvz(?E|;tjaao@ z`XTRDz0l3L<6z;Cc`tlQlmPp}`XG*8Fl<6j+6y|Ehfol?(z+>p(@t}j>Z*?sS2=NJ zBuWzmYL4@df_N7(3h{KFnP%K0R~ z{Ome0ccuSG^CmGN;ImDk9^2yN=$M4`oECd0{VDv@_J!Ax+EeN#v3G9ee?Pc(Jly}d zNNQo|QF}X=5iV;13>6S@GBA;(itg`a-@szl>`pIm_>^K_|5&C6Zq_6ah+G5-XZB!j zV)RU-eX%KX-ehQT{4;X5l7~czQETm( zYr?I^w5m3V=e204aGn|XlGi5DrXgac&0wHzjIElMSXUEriXNdkV+XL$j-LW z>a3BsHU2}8s8k_IglHE0R)y}#z3_OUSmf9D4_tA`@ydv&j!6Noxj_Tc&`5!ezuBfK zi#~C>)$KU|!+l+jim=2aXdsQs?b%dqT0<4*!E9k#c27Of+7E=iNw8$ti2I-_r!1U| z$ngVrmVBe$@U?vUG`mx@9;zKCKk44BN%aM2(Lcb$rrhcNS~h

qy%E8}tNN62)=p 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 literal 0 HcmV?d00001 diff --git a/2025/abstraction/main.py b/2025/abstraction/main.py new file mode 100644 index 0000000..ae1a3a9 --- /dev/null +++ b/2025/abstraction/main.py @@ -0,0 +1,107 @@ +from abc import ABC, abstractmethod +from typing import Callable, Protocol + +from PIL import Image, ImageOps + +# ----------- Callable-based abstraction ----------- + +# A Callable that takes an Image and returns an Image +ImageFilterFunc = Callable[[Image.Image], Image.Image] + + +def apply_grayscale(image: Image.Image) -> Image.Image: + print("Applying grayscale filter (function)...") + return ImageOps.grayscale(image) + + +def invert_filter(image: Image.Image) -> Image.Image: + print("Applying invert filter (function)...") + return ImageOps.invert(image.convert("RGB")) + + +def process_with_callable( + image_path: str, output_path: str, filter_func: ImageFilterFunc +): + image = Image.open(image_path) + image = filter_func(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +# ----------- ABC-based abstraction ----------- + + +class FilterBase(ABC): + @abstractmethod + def apply(self, image: Image.Image) -> Image.Image: ... + + +class GrayscaleFilter(FilterBase): + def apply(self, image: Image.Image) -> Image.Image: + print("Applying grayscale filter (class)...") + return ImageOps.grayscale(image) + + +class InvertFilter(FilterBase): + def apply(self, image: Image.Image) -> Image.Image: + print("Applying invert filter (class)...") + return ImageOps.invert(image.convert("RGB")) + + +def process_with_abc(image_path: str, output_path: str, filter_obj: FilterBase): + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +# ----------- Protocol-based abstraction ----------- + + +class ImageFilter(Protocol): + def apply(self, image: Image.Image) -> Image.Image: ... + + +# This class doesn't inherit anything but still conforms to the protocol +class SepiaFilter: + def apply(self, image: Image.Image) -> Image.Image: + print("Applying sepia filter (class conforming to protocol)...") + sepia_image = image.convert("RGB") + width, height = sepia_image.size + pixels = sepia_image.load() + + for y in range(height): + for x in range(width): + r, g, b = pixels[x, y] + tr = int(0.393 * r + 0.769 * g + 0.189 * b) + tg = int(0.349 * r + 0.686 * g + 0.168 * b) + tb = int(0.272 * r + 0.534 * g + 0.131 * b) + pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) + + return sepia_image + + +def process_with_protocol(image_path: str, output_path: str, filter_obj: ImageFilter): + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +def main() -> None: + input_image = "input.jpg" # Replace with your image path + + # Callable examples + process_with_callable(input_image, "output_callable_grayscale.jpg", apply_grayscale) + process_with_callable(input_image, "output_callable_invert.jpg", invert_filter) + + # ABC examples + process_with_abc(input_image, "output_abc_grayscale.jpg", GrayscaleFilter()) + process_with_abc(input_image, "output_abc_invert.jpg", InvertFilter()) + + # Protocol example + process_with_protocol(input_image, "output_protocol_sepia.jpg", SepiaFilter()) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/pyproject.toml b/2025/abstraction/pyproject.toml new file mode 100644 index 0000000..96b1063 --- /dev/null +++ b/2025/abstraction/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "abstraction" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "pillow>=11.2.1", +] diff --git a/2025/abstraction/uv.lock b/2025/abstraction/uv.lock new file mode 100644 index 0000000..a0335c3 --- /dev/null +++ b/2025/abstraction/uv.lock @@ -0,0 +1,44 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "abstraction" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pillow" }, +] + +[package.metadata] +requires-dist = [{ name = "pillow", specifier = ">=11.2.1" }] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, +] From b1fa4e289080c4d933745118dc6382c458f4b664 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 8 May 2025 16:07:44 +0200 Subject: [PATCH 08/44] Updated examples. --- 2025/abstraction/abstraction_abc.py | 74 ++++++++++++++++ 2025/abstraction/abstraction_callable.py | 75 ++++++++++++++++ 2025/abstraction/abstraction_protocol.py | 65 ++++++++++++++ 2025/abstraction/main.py | 107 ----------------------- 4 files changed, 214 insertions(+), 107 deletions(-) create mode 100644 2025/abstraction/abstraction_abc.py create mode 100644 2025/abstraction/abstraction_callable.py create mode 100644 2025/abstraction/abstraction_protocol.py delete mode 100644 2025/abstraction/main.py diff --git a/2025/abstraction/abstraction_abc.py b/2025/abstraction/abstraction_abc.py new file mode 100644 index 0000000..0d45d7f --- /dev/null +++ b/2025/abstraction/abstraction_abc.py @@ -0,0 +1,74 @@ +from abc import ABC, abstractmethod +from typing import Any + +from PIL import Image, ImageOps + + +class FilterBase(ABC): + name: str + + @abstractmethod + def apply(self, image: Image.Image) -> Image.Image: ... + + @abstractmethod + def configure(self, config: dict[str, Any]) -> None: ... + + +class GrayscaleFilter(FilterBase): + def __init__(self) -> None: + self._intensity: float = 1.0 + + @property + def name(self) -> str: + return "Grayscale" + + def apply(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter with intensity {self._intensity}") + return ImageOps.grayscale(image) + + def configure(self, config: dict[str, Any]) -> None: + self._intensity = config.get("intensity", self._intensity) + + +class InvertFilter(FilterBase): + def __init__(self) -> None: + self._enabled: bool = True + + @property + def name(self) -> str: + return "Invert" + + def apply(self, image: Image.Image) -> Image.Image: + if self._enabled: + print(f"Applying {self.name} filter") + return ImageOps.invert(image.convert("RGB")) + else: + print(f"{self.name} filter disabled, returning original image") + return image + + def configure(self, config: dict[str, Any]) -> None: + self._enabled = config.get("enabled", self._enabled) + + +def process_with_abc(image_path: str, output_path: str, filter_obj: FilterBase) -> None: + print(f"\nUsing filter: {filter_obj.name}") + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +def main() -> None: + input_image: str = "input.jpg" + + grayscale = GrayscaleFilter() + grayscale.configure({"intensity": 0.8}) + process_with_abc(input_image, "output_abc_grayscale.jpg", grayscale) + + invert = InvertFilter() + invert.configure({"enabled": True}) + process_with_abc(input_image, "output_abc_invert.jpg", invert) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/abstraction_callable.py b/2025/abstraction/abstraction_callable.py new file mode 100644 index 0000000..c4d8e40 --- /dev/null +++ b/2025/abstraction/abstraction_callable.py @@ -0,0 +1,75 @@ +from typing import Callable + +from PIL import Image, ImageOps + +type ImageFilterFunc = Callable[[Image.Image], Image.Image] + + +def make_grayscale_filter(intensity: float = 1.0) -> ImageFilterFunc: + def filter_func(image: Image.Image) -> Image.Image: + print(f"Applying grayscale filter with intensity {intensity}") + # Note: intensity isn't actually used by Pillow here, but it demonstrates config + return ImageOps.grayscale(image) + + return filter_func + + +def make_invert_filter(enabled: bool = True) -> ImageFilterFunc: + def filter_func(image: Image.Image) -> Image.Image: + if enabled: + print("Applying invert filter") + return ImageOps.invert(image.convert("RGB")) + else: + print("Invert filter disabled, returning original image") + return image + + return filter_func + + +def make_sepia_filter(depth: int = 20) -> ImageFilterFunc: + def filter_func(image: Image.Image) -> Image.Image: + print(f"Applying sepia filter with depth {depth}") + sepia_image = image.convert("RGB") + width, height = sepia_image.size + pixels = sepia_image.load() + + for y in range(height): + for x in range(width): + r, g, b = pixels[x, y] + tr = int(0.393 * r + 0.769 * g + 0.189 * b + depth) + tg = int(0.349 * r + 0.686 * g + 0.168 * b + depth) + tb = int(0.272 * r + 0.534 * g + 0.131 * b + depth) + pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) + return sepia_image + + return filter_func + + +def process_image( + image_path: str, output_path: str, filter_func: ImageFilterFunc, filter_name: str +) -> None: + print(f"\nUsing filter: {filter_name}") + image = Image.open(image_path) + image = filter_func(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +def main() -> None: + input_image: str = "input.jpg" + + # Create configured filters + grayscale_filter = make_grayscale_filter(intensity=0.8) + invert_filter = make_invert_filter(enabled=True) + sepia_filter = make_sepia_filter(depth=15) + + # Apply filters + process_image( + input_image, "output_callable_grayscale.jpg", grayscale_filter, "Grayscale" + ) + process_image(input_image, "output_callable_invert.jpg", invert_filter, "Invert") + process_image(input_image, "output_callable_sepia.jpg", sepia_filter, "Sepia") + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/abstraction_protocol.py b/2025/abstraction/abstraction_protocol.py new file mode 100644 index 0000000..849b2b7 --- /dev/null +++ b/2025/abstraction/abstraction_protocol.py @@ -0,0 +1,65 @@ +from typing import Any, Protocol + +from PIL import Image + + +class ImageFilter(Protocol): + name: str + + def apply(self, image: Image.Image) -> Image.Image: ... + + def configure(self, config: dict[str, Any]) -> None: ... + + +class SepiaFilter: + def __init__(self) -> None: + self._depth: int = 20 + + @property + def name(self) -> str: + return "Sepia" + + def apply(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter with depth {self._depth}") + sepia_image = image.convert("RGB") + width, height = sepia_image.size + pixels = sepia_image.load() + + for y in range(height): + for x in range(width): + r, g, b = pixels[x, y] + tr = int(0.393 * r + 0.769 * g + 0.189 * b + self._depth) + tg = int(0.349 * r + 0.686 * g + 0.168 * b + self._depth) + tb = int(0.272 * r + 0.534 * g + 0.131 * b + self._depth) + pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) + + return sepia_image + + def configure(self, config: dict[str, Any]) -> None: + self._depth = config.get("depth", self._depth) + + +def process_with_protocol( + image_path: str, output_path: str, filter_obj: ImageFilter +) -> None: + print(f"\nUsing filter: {filter_obj.name}") + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") + + +# ----------- Main function ----------- + + +def main() -> None: + input_image: str = "input.jpg" # Replace with a real image path + + # Protocol example + sepia = SepiaFilter() + sepia.configure({"depth": 15}) + process_with_protocol(input_image, "output_protocol_sepia.jpg", sepia) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/main.py b/2025/abstraction/main.py deleted file mode 100644 index ae1a3a9..0000000 --- a/2025/abstraction/main.py +++ /dev/null @@ -1,107 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Callable, Protocol - -from PIL import Image, ImageOps - -# ----------- Callable-based abstraction ----------- - -# A Callable that takes an Image and returns an Image -ImageFilterFunc = Callable[[Image.Image], Image.Image] - - -def apply_grayscale(image: Image.Image) -> Image.Image: - print("Applying grayscale filter (function)...") - return ImageOps.grayscale(image) - - -def invert_filter(image: Image.Image) -> Image.Image: - print("Applying invert filter (function)...") - return ImageOps.invert(image.convert("RGB")) - - -def process_with_callable( - image_path: str, output_path: str, filter_func: ImageFilterFunc -): - image = Image.open(image_path) - image = filter_func(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -# ----------- ABC-based abstraction ----------- - - -class FilterBase(ABC): - @abstractmethod - def apply(self, image: Image.Image) -> Image.Image: ... - - -class GrayscaleFilter(FilterBase): - def apply(self, image: Image.Image) -> Image.Image: - print("Applying grayscale filter (class)...") - return ImageOps.grayscale(image) - - -class InvertFilter(FilterBase): - def apply(self, image: Image.Image) -> Image.Image: - print("Applying invert filter (class)...") - return ImageOps.invert(image.convert("RGB")) - - -def process_with_abc(image_path: str, output_path: str, filter_obj: FilterBase): - image = Image.open(image_path) - image = filter_obj.apply(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -# ----------- Protocol-based abstraction ----------- - - -class ImageFilter(Protocol): - def apply(self, image: Image.Image) -> Image.Image: ... - - -# This class doesn't inherit anything but still conforms to the protocol -class SepiaFilter: - def apply(self, image: Image.Image) -> Image.Image: - print("Applying sepia filter (class conforming to protocol)...") - sepia_image = image.convert("RGB") - width, height = sepia_image.size - pixels = sepia_image.load() - - for y in range(height): - for x in range(width): - r, g, b = pixels[x, y] - tr = int(0.393 * r + 0.769 * g + 0.189 * b) - tg = int(0.349 * r + 0.686 * g + 0.168 * b) - tb = int(0.272 * r + 0.534 * g + 0.131 * b) - pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) - - return sepia_image - - -def process_with_protocol(image_path: str, output_path: str, filter_obj: ImageFilter): - image = Image.open(image_path) - image = filter_obj.apply(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -def main() -> None: - input_image = "input.jpg" # Replace with your image path - - # Callable examples - process_with_callable(input_image, "output_callable_grayscale.jpg", apply_grayscale) - process_with_callable(input_image, "output_callable_invert.jpg", invert_filter) - - # ABC examples - process_with_abc(input_image, "output_abc_grayscale.jpg", GrayscaleFilter()) - process_with_abc(input_image, "output_abc_invert.jpg", InvertFilter()) - - # Protocol example - process_with_protocol(input_image, "output_protocol_sepia.jpg", SepiaFilter()) - - -if __name__ == "__main__": - main() From 8951f88c36cd171d5a50de97369473fef1284899 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 8 May 2025 16:10:13 +0200 Subject: [PATCH 09/44] Some cleanup. --- 2025/abstraction/abstraction_abc.py | 4 +++- 2025/abstraction/abstraction_protocol.py | 10 ++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/2025/abstraction/abstraction_abc.py b/2025/abstraction/abstraction_abc.py index 0d45d7f..7c63cd7 100644 --- a/2025/abstraction/abstraction_abc.py +++ b/2025/abstraction/abstraction_abc.py @@ -5,7 +5,9 @@ class FilterBase(ABC): - name: str + @property + @abstractmethod + def name(self) -> str: ... @abstractmethod def apply(self, image: Image.Image) -> Image.Image: ... diff --git a/2025/abstraction/abstraction_protocol.py b/2025/abstraction/abstraction_protocol.py index 849b2b7..5902778 100644 --- a/2025/abstraction/abstraction_protocol.py +++ b/2025/abstraction/abstraction_protocol.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from typing import Any, Protocol from PIL import Image @@ -11,13 +12,10 @@ def apply(self, image: Image.Image) -> Image.Image: ... def configure(self, config: dict[str, Any]) -> None: ... +@dataclass class SepiaFilter: - def __init__(self) -> None: - self._depth: int = 20 - - @property - def name(self) -> str: - return "Sepia" + name: str = "Sepia" + _depth: int = 20 def apply(self, image: Image.Image) -> Image.Image: print(f"Applying {self.name} filter with depth {self._depth}") From 72628b70a96ae22c2806be57321cef9276f46e69 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Mon, 12 May 2025 17:23:43 +0200 Subject: [PATCH 10/44] WiP on abstraction example --- 2025/abstraction/abstraction_abc.py | 76 ------------------ .../abstraction_abc/filters/base.py | 14 ++++ .../abstraction_abc/filters/grayscale.py | 18 +++++ .../abstraction_abc/filters/invert.py | 22 +++++ 2025/abstraction/abstraction_abc/main.py | 20 +++++ .../abstraction_abc/output_abc_grayscale.jpg | Bin 0 -> 26854 bytes .../abstraction_abc/output_abc_invert.jpg | Bin 0 -> 30626 bytes .../abstraction_abc/process_img.py | 9 +++ 2025/abstraction/abstraction_none/main.py | 18 +++++ 2025/abstraction/filters/grayscale.py | 6 ++ 2025/abstraction/filters/invert.py | 6 ++ 2025/abstraction/filters/sepia.py | 18 +++++ 12 files changed, 131 insertions(+), 76 deletions(-) delete mode 100644 2025/abstraction/abstraction_abc.py create mode 100644 2025/abstraction/abstraction_abc/filters/base.py create mode 100644 2025/abstraction/abstraction_abc/filters/grayscale.py create mode 100644 2025/abstraction/abstraction_abc/filters/invert.py create mode 100644 2025/abstraction/abstraction_abc/main.py create mode 100644 2025/abstraction/abstraction_abc/output_abc_grayscale.jpg create mode 100644 2025/abstraction/abstraction_abc/output_abc_invert.jpg create mode 100644 2025/abstraction/abstraction_abc/process_img.py create mode 100644 2025/abstraction/abstraction_none/main.py create mode 100644 2025/abstraction/filters/grayscale.py create mode 100644 2025/abstraction/filters/invert.py create mode 100644 2025/abstraction/filters/sepia.py diff --git a/2025/abstraction/abstraction_abc.py b/2025/abstraction/abstraction_abc.py deleted file mode 100644 index 7c63cd7..0000000 --- a/2025/abstraction/abstraction_abc.py +++ /dev/null @@ -1,76 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any - -from PIL import Image, ImageOps - - -class FilterBase(ABC): - @property - @abstractmethod - def name(self) -> str: ... - - @abstractmethod - def apply(self, image: Image.Image) -> Image.Image: ... - - @abstractmethod - def configure(self, config: dict[str, Any]) -> None: ... - - -class GrayscaleFilter(FilterBase): - def __init__(self) -> None: - self._intensity: float = 1.0 - - @property - def name(self) -> str: - return "Grayscale" - - def apply(self, image: Image.Image) -> Image.Image: - print(f"Applying {self.name} filter with intensity {self._intensity}") - return ImageOps.grayscale(image) - - def configure(self, config: dict[str, Any]) -> None: - self._intensity = config.get("intensity", self._intensity) - - -class InvertFilter(FilterBase): - def __init__(self) -> None: - self._enabled: bool = True - - @property - def name(self) -> str: - return "Invert" - - def apply(self, image: Image.Image) -> Image.Image: - if self._enabled: - print(f"Applying {self.name} filter") - return ImageOps.invert(image.convert("RGB")) - else: - print(f"{self.name} filter disabled, returning original image") - return image - - def configure(self, config: dict[str, Any]) -> None: - self._enabled = config.get("enabled", self._enabled) - - -def process_with_abc(image_path: str, output_path: str, filter_obj: FilterBase) -> None: - print(f"\nUsing filter: {filter_obj.name}") - image = Image.open(image_path) - image = filter_obj.apply(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -def main() -> None: - input_image: str = "input.jpg" - - grayscale = GrayscaleFilter() - grayscale.configure({"intensity": 0.8}) - process_with_abc(input_image, "output_abc_grayscale.jpg", grayscale) - - invert = InvertFilter() - invert.configure({"enabled": True}) - process_with_abc(input_image, "output_abc_invert.jpg", invert) - - -if __name__ == "__main__": - main() diff --git a/2025/abstraction/abstraction_abc/filters/base.py b/2025/abstraction/abstraction_abc/filters/base.py new file mode 100644 index 0000000..f5472d1 --- /dev/null +++ b/2025/abstraction/abstraction_abc/filters/base.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from PIL import Image +from typing import Any + +class FilterBase(ABC): + @property + @abstractmethod + def name(self) -> str: ... + + @abstractmethod + def apply(self, image: Image.Image) -> Image.Image: ... + + @abstractmethod + def configure(self, config: dict[str, Any]) -> None: ... \ No newline at end of file diff --git a/2025/abstraction/abstraction_abc/filters/grayscale.py b/2025/abstraction/abstraction_abc/filters/grayscale.py new file mode 100644 index 0000000..25d8503 --- /dev/null +++ b/2025/abstraction/abstraction_abc/filters/grayscale.py @@ -0,0 +1,18 @@ +from .base import FilterBase +from PIL import Image, ImageOps +from typing import Any + +class GrayscaleFilter(FilterBase): + def __init__(self) -> None: + self._intensity: float = 1.0 + + @property + def name(self) -> str: + return "Grayscale" + + def apply(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter with intensity {self._intensity}") + return ImageOps.grayscale(image) + + def configure(self, config: dict[str, Any]) -> None: + self._intensity = config.get("intensity", self._intensity) \ No newline at end of file diff --git a/2025/abstraction/abstraction_abc/filters/invert.py b/2025/abstraction/abstraction_abc/filters/invert.py new file mode 100644 index 0000000..3c65cfd --- /dev/null +++ b/2025/abstraction/abstraction_abc/filters/invert.py @@ -0,0 +1,22 @@ +from .base import FilterBase +from PIL import Image, ImageOps +from typing import Any + +class InvertFilter(FilterBase): + def __init__(self) -> None: + self._enabled: bool = True + + @property + def name(self) -> str: + return "Invert" + + def apply(self, image: Image.Image) -> Image.Image: + if self._enabled: + print(f"Applying {self.name} filter") + return ImageOps.invert(image.convert("RGB")) + else: + print(f"{self.name} filter disabled, returning original image") + return image + + def configure(self, config: dict[str, Any]) -> None: + self._enabled = config.get("enabled", self._enabled) \ No newline at end of file diff --git a/2025/abstraction/abstraction_abc/main.py b/2025/abstraction/abstraction_abc/main.py new file mode 100644 index 0000000..11051f4 --- /dev/null +++ b/2025/abstraction/abstraction_abc/main.py @@ -0,0 +1,20 @@ +from typing import Any +from process_img import process_with_abc +from filters.grayscale import GrayscaleFilter +from filters.invert import InvertFilter + + +def main() -> None: + input_image: str = "../input.jpg" + + grayscale = GrayscaleFilter() + grayscale.configure({"intensity": 0.8}) + process_with_abc(input_image, "output_abc_grayscale.jpg", grayscale) + + invert = InvertFilter() + invert.configure({"enabled": True}) + process_with_abc(input_image, "output_abc_invert.jpg", invert) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/abstraction_abc/output_abc_grayscale.jpg b/2025/abstraction/abstraction_abc/output_abc_grayscale.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a7c4c10efdea8fe310e6456f2c74c2fc0f291642 GIT binary patch literal 26854 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 S`cu>nwPAkkKNN%yO8?ocrJ#QR literal 0 HcmV?d00001 diff --git a/2025/abstraction/abstraction_abc/output_abc_invert.jpg b/2025/abstraction/abstraction_abc/output_abc_invert.jpg new file mode 100644 index 0000000000000000000000000000000000000000..61f726b701c688fe951dbc70ea8e7888fb586384 GIT binary patch literal 30626 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 None: + print(f"\nUsing filter: {filter_obj.name}") + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") \ No newline at end of file diff --git a/2025/abstraction/abstraction_none/main.py b/2025/abstraction/abstraction_none/main.py new file mode 100644 index 0000000..2645b5a --- /dev/null +++ b/2025/abstraction/abstraction_none/main.py @@ -0,0 +1,18 @@ +from PIL import Image +from filters.grayscale import apply_grayscale +from filters.invert import apply_invert +from filters.sepia import apply_sepia + + +def main() -> None: + image = Image.open("input.jpg") + image = apply_grayscale(image) + image = apply_invert(image) + image = apply_sepia(image) + + image.save("output.jpg") + print("Saved output.jpg") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/abstraction/filters/grayscale.py b/2025/abstraction/filters/grayscale.py new file mode 100644 index 0000000..bc3dcd6 --- /dev/null +++ b/2025/abstraction/filters/grayscale.py @@ -0,0 +1,6 @@ +from PIL import Image, ImageOps + + +def apply_grayscale(image: Image.Image) -> Image.Image: + print("Applying grayscale filter") + return ImageOps.grayscale(image) \ No newline at end of file diff --git a/2025/abstraction/filters/invert.py b/2025/abstraction/filters/invert.py new file mode 100644 index 0000000..da0240f --- /dev/null +++ b/2025/abstraction/filters/invert.py @@ -0,0 +1,6 @@ +from PIL import Image, ImageOps + + +def apply_invert(image: Image.Image) -> Image.Image: + print("Applying invert filter") + return ImageOps.invert(image.convert("RGB")) \ No newline at end of file diff --git a/2025/abstraction/filters/sepia.py b/2025/abstraction/filters/sepia.py new file mode 100644 index 0000000..d0c2bd3 --- /dev/null +++ b/2025/abstraction/filters/sepia.py @@ -0,0 +1,18 @@ +from PIL import Image + + +def apply_sepia(image: Image.Image) -> Image.Image: + print("Applying sepia filter") + sepia_image = image.convert("RGB") + width, height = sepia_image.size + pixels = sepia_image.load() + + for y in range(height): + for x in range(width): + r, g, b = pixels[x, y] + tr = int(0.393 * r + 0.769 * g + 0.189 * b) + tg = int(0.349 * r + 0.686 * g + 0.168 * b) + tb = int(0.272 * r + 0.534 * g + 0.131 * b) + pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) + + return sepia_image \ No newline at end of file From d416fe13f6a99603d79efae59e4d337b8ad784a4 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 14 May 2025 15:58:07 +0200 Subject: [PATCH 11/44] Removed output images. Cleaned up examples and organized them around the various types of abstraction. --- .../abstraction_abc/filters/base.py | 6 +- .../abstraction_abc/filters/grayscale.py | 14 +++- 2025/abstraction/abstraction_abc/main.py | 7 +- .../abstraction_abc/output_abc_grayscale.jpg | Bin 26854 -> 0 bytes .../abstraction_abc/output_abc_invert.jpg | Bin 30626 -> 0 bytes .../abstraction_abc/process_img.py | 7 +- 2025/abstraction/abstraction_callable.py | 75 ------------------ .../abstraction_callable/filters/grayscale.py | 9 +++ .../filters/invert.py | 0 .../filters/sepia.py | 0 2025/abstraction/abstraction_callable/main.py | 40 ++++++++++ .../abstraction_callable/process_img.py | 13 +++ .../abstraction_none/filters/grayscale.py | 22 +++++ .../abstraction_none/filters/invert.py | 11 +++ 2025/abstraction/abstraction_none/main.py | 22 ++--- .../abstraction_none/process_img.py | 20 +++++ 2025/abstraction/abstraction_protocol.py | 63 --------------- .../abstraction_protocol/filters/grayscale.py | 22 +++++ .../abstraction_protocol/filters/invert.py | 23 ++++++ 2025/abstraction/abstraction_protocol/main.py | 18 +++++ .../abstraction_protocol/process_img.py | 20 +++++ 2025/abstraction/filters/grayscale.py | 6 -- 22 files changed, 230 insertions(+), 168 deletions(-) delete mode 100644 2025/abstraction/abstraction_abc/output_abc_grayscale.jpg delete mode 100644 2025/abstraction/abstraction_abc/output_abc_invert.jpg delete mode 100644 2025/abstraction/abstraction_callable.py create mode 100644 2025/abstraction/abstraction_callable/filters/grayscale.py rename 2025/abstraction/{ => abstraction_callable}/filters/invert.py (100%) rename 2025/abstraction/{ => abstraction_callable}/filters/sepia.py (100%) create mode 100644 2025/abstraction/abstraction_callable/main.py create mode 100644 2025/abstraction/abstraction_callable/process_img.py create mode 100644 2025/abstraction/abstraction_none/filters/grayscale.py create mode 100644 2025/abstraction/abstraction_none/filters/invert.py create mode 100644 2025/abstraction/abstraction_none/process_img.py delete mode 100644 2025/abstraction/abstraction_protocol.py create mode 100644 2025/abstraction/abstraction_protocol/filters/grayscale.py create mode 100644 2025/abstraction/abstraction_protocol/filters/invert.py create mode 100644 2025/abstraction/abstraction_protocol/main.py create mode 100644 2025/abstraction/abstraction_protocol/process_img.py delete mode 100644 2025/abstraction/filters/grayscale.py diff --git a/2025/abstraction/abstraction_abc/filters/base.py b/2025/abstraction/abstraction_abc/filters/base.py index f5472d1..c1e9a96 100644 --- a/2025/abstraction/abstraction_abc/filters/base.py +++ b/2025/abstraction/abstraction_abc/filters/base.py @@ -1,7 +1,9 @@ from abc import ABC, abstractmethod -from PIL import Image from typing import Any +from PIL import Image + + class FilterBase(ABC): @property @abstractmethod @@ -11,4 +13,4 @@ def name(self) -> str: ... def apply(self, image: Image.Image) -> Image.Image: ... @abstractmethod - def configure(self, config: dict[str, Any]) -> None: ... \ No newline at end of file + def configure(self, config: dict[str, Any]) -> None: ... diff --git a/2025/abstraction/abstraction_abc/filters/grayscale.py b/2025/abstraction/abstraction_abc/filters/grayscale.py index 25d8503..6e7ecdd 100644 --- a/2025/abstraction/abstraction_abc/filters/grayscale.py +++ b/2025/abstraction/abstraction_abc/filters/grayscale.py @@ -1,7 +1,10 @@ -from .base import FilterBase -from PIL import Image, ImageOps from typing import Any +from PIL import Image, ImageOps + +from .base import FilterBase + + class GrayscaleFilter(FilterBase): def __init__(self) -> None: self._intensity: float = 1.0 @@ -12,7 +15,10 @@ def name(self) -> str: def apply(self, image: Image.Image) -> Image.Image: print(f"Applying {self.name} filter with intensity {self._intensity}") - return ImageOps.grayscale(image) + grayscale_image = ImageOps.grayscale(image) + if self._intensity < 1.0: + return Image.blend(image, grayscale_image, self._intensity) + return grayscale_image def configure(self, config: dict[str, Any]) -> None: - self._intensity = config.get("intensity", self._intensity) \ No newline at end of file + self._intensity = config.get("intensity", self._intensity) diff --git a/2025/abstraction/abstraction_abc/main.py b/2025/abstraction/abstraction_abc/main.py index 11051f4..ef18c42 100644 --- a/2025/abstraction/abstraction_abc/main.py +++ b/2025/abstraction/abstraction_abc/main.py @@ -1,7 +1,6 @@ -from typing import Any -from process_img import process_with_abc from filters.grayscale import GrayscaleFilter from filters.invert import InvertFilter +from process_img import process_img def main() -> None: @@ -9,11 +8,11 @@ def main() -> None: grayscale = GrayscaleFilter() grayscale.configure({"intensity": 0.8}) - process_with_abc(input_image, "output_abc_grayscale.jpg", grayscale) + process_img(input_image, "output_abc_grayscale.jpg", grayscale) invert = InvertFilter() invert.configure({"enabled": True}) - process_with_abc(input_image, "output_abc_invert.jpg", invert) + process_img(input_image, "output_abc_invert.jpg", invert) if __name__ == "__main__": diff --git a/2025/abstraction/abstraction_abc/output_abc_grayscale.jpg b/2025/abstraction/abstraction_abc/output_abc_grayscale.jpg deleted file mode 100644 index a7c4c10efdea8fe310e6456f2c74c2fc0f291642..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26854 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 S`cu>nwPAkkKNN%yO8?ocrJ#QR diff --git a/2025/abstraction/abstraction_abc/output_abc_invert.jpg b/2025/abstraction/abstraction_abc/output_abc_invert.jpg deleted file mode 100644 index 61f726b701c688fe951dbc70ea8e7888fb586384..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30626 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 None: +def process_img(image_path: str, output_path: str, filter_obj: FilterBase) -> None: print(f"\nUsing filter: {filter_obj.name}") image = Image.open(image_path) image = filter_obj.apply(image) image.save(output_path) - print(f"Saved processed image to {output_path}") \ No newline at end of file + print(f"Saved processed image to {output_path}") diff --git a/2025/abstraction/abstraction_callable.py b/2025/abstraction/abstraction_callable.py deleted file mode 100644 index c4d8e40..0000000 --- a/2025/abstraction/abstraction_callable.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Callable - -from PIL import Image, ImageOps - -type ImageFilterFunc = Callable[[Image.Image], Image.Image] - - -def make_grayscale_filter(intensity: float = 1.0) -> ImageFilterFunc: - def filter_func(image: Image.Image) -> Image.Image: - print(f"Applying grayscale filter with intensity {intensity}") - # Note: intensity isn't actually used by Pillow here, but it demonstrates config - return ImageOps.grayscale(image) - - return filter_func - - -def make_invert_filter(enabled: bool = True) -> ImageFilterFunc: - def filter_func(image: Image.Image) -> Image.Image: - if enabled: - print("Applying invert filter") - return ImageOps.invert(image.convert("RGB")) - else: - print("Invert filter disabled, returning original image") - return image - - return filter_func - - -def make_sepia_filter(depth: int = 20) -> ImageFilterFunc: - def filter_func(image: Image.Image) -> Image.Image: - print(f"Applying sepia filter with depth {depth}") - sepia_image = image.convert("RGB") - width, height = sepia_image.size - pixels = sepia_image.load() - - for y in range(height): - for x in range(width): - r, g, b = pixels[x, y] - tr = int(0.393 * r + 0.769 * g + 0.189 * b + depth) - tg = int(0.349 * r + 0.686 * g + 0.168 * b + depth) - tb = int(0.272 * r + 0.534 * g + 0.131 * b + depth) - pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) - return sepia_image - - return filter_func - - -def process_image( - image_path: str, output_path: str, filter_func: ImageFilterFunc, filter_name: str -) -> None: - print(f"\nUsing filter: {filter_name}") - image = Image.open(image_path) - image = filter_func(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -def main() -> None: - input_image: str = "input.jpg" - - # Create configured filters - grayscale_filter = make_grayscale_filter(intensity=0.8) - invert_filter = make_invert_filter(enabled=True) - sepia_filter = make_sepia_filter(depth=15) - - # Apply filters - process_image( - input_image, "output_callable_grayscale.jpg", grayscale_filter, "Grayscale" - ) - process_image(input_image, "output_callable_invert.jpg", invert_filter, "Invert") - process_image(input_image, "output_callable_sepia.jpg", sepia_filter, "Sepia") - - -if __name__ == "__main__": - main() diff --git a/2025/abstraction/abstraction_callable/filters/grayscale.py b/2025/abstraction/abstraction_callable/filters/grayscale.py new file mode 100644 index 0000000..bdc0bf9 --- /dev/null +++ b/2025/abstraction/abstraction_callable/filters/grayscale.py @@ -0,0 +1,9 @@ +from PIL import Image, ImageOps + + +def apply_grayscale(image: Image.Image, intensity: float) -> Image.Image: + print(f"Applying grayscale filter with intensity {intensity}") + grayscale_image = ImageOps.grayscale(image) + if intensity < 1.0: + return Image.blend(image, grayscale_image, intensity) + return grayscale_image diff --git a/2025/abstraction/filters/invert.py b/2025/abstraction/abstraction_callable/filters/invert.py similarity index 100% rename from 2025/abstraction/filters/invert.py rename to 2025/abstraction/abstraction_callable/filters/invert.py diff --git a/2025/abstraction/filters/sepia.py b/2025/abstraction/abstraction_callable/filters/sepia.py similarity index 100% rename from 2025/abstraction/filters/sepia.py rename to 2025/abstraction/abstraction_callable/filters/sepia.py diff --git a/2025/abstraction/abstraction_callable/main.py b/2025/abstraction/abstraction_callable/main.py new file mode 100644 index 0000000..b0b92af --- /dev/null +++ b/2025/abstraction/abstraction_callable/main.py @@ -0,0 +1,40 @@ +from functools import partial + +from filters.grayscale import apply_grayscale +from filters.invert import apply_invert +from PIL import Image, ImageOps +from process_img import process_img + + +def make_grayscale_filter(intensity: float = 1.0) -> ImageFilterFunc: + def filter_func(image: Image.Image) -> Image.Image: + print(f"Applying grayscale filter with intensity {intensity}") + # Note: intensity isn't actually used by Pillow here, but it demonstrates config + return ImageOps.grayscale(image) + + return filter_func + + +def make_invert_filter(enabled: bool = True) -> ImageFilterFunc: + def filter_func(image: Image.Image) -> Image.Image: + if enabled: + print("Applying invert filter") + return ImageOps.invert(image.convert("RGB")) + else: + print("Invert filter disabled, returning original image") + return image + + return filter_func + + +def main() -> None: + input_image: str = "../input.jpg" + + grayscale_fn = partial(apply_grayscale, intensity=0.8) + process_img(input_image, "output_abc_grayscale.jpg", grayscale_fn) + + process_img(input_image, "output_abc_invert.jpg", apply_invert) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/abstraction_callable/process_img.py b/2025/abstraction/abstraction_callable/process_img.py new file mode 100644 index 0000000..946fd09 --- /dev/null +++ b/2025/abstraction/abstraction_callable/process_img.py @@ -0,0 +1,13 @@ +from typing import Callable + +from PIL import Image + +type ProcessFn = Callable[[image.Image], image.Image] + + +def process_img(image_path: str, output_path: str, filter_fn: ProcessFn) -> None: + print(f"\nUsing filter: {filter_fn.__name__}") + image = Image.open(image_path) + image = filter_fn(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") diff --git a/2025/abstraction/abstraction_none/filters/grayscale.py b/2025/abstraction/abstraction_none/filters/grayscale.py new file mode 100644 index 0000000..50ac420 --- /dev/null +++ b/2025/abstraction/abstraction_none/filters/grayscale.py @@ -0,0 +1,22 @@ +from typing import Any + +from PIL import Image, ImageOps + + +class GrayscaleFilter: + def __init__(self) -> None: + self._intensity: float = 1.0 + + @property + def name(self) -> str: + return "Grayscale" + + def apply(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter with intensity {self._intensity}") + grayscale_image = ImageOps.grayscale(image) + if self._intensity < 1.0: + return Image.blend(image, grayscale_image, self._intensity) + return grayscale_image + + def configure(self, config: dict[str, Any]) -> None: + self._intensity = config.get("intensity", self._intensity) diff --git a/2025/abstraction/abstraction_none/filters/invert.py b/2025/abstraction/abstraction_none/filters/invert.py new file mode 100644 index 0000000..1f8f56c --- /dev/null +++ b/2025/abstraction/abstraction_none/filters/invert.py @@ -0,0 +1,11 @@ +from PIL import Image, ImageOps + + +class InvertFilter: + @property + def name(self) -> str: + return "Invert" + + def do_invert(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter") + return ImageOps.invert(image.convert("RGB")) diff --git a/2025/abstraction/abstraction_none/main.py b/2025/abstraction/abstraction_none/main.py index 2645b5a..15e5589 100644 --- a/2025/abstraction/abstraction_none/main.py +++ b/2025/abstraction/abstraction_none/main.py @@ -1,18 +1,18 @@ -from PIL import Image -from filters.grayscale import apply_grayscale -from filters.invert import apply_invert -from filters.sepia import apply_sepia +from filters.grayscale import GrayscaleFilter +from filters.invert import InvertFilter +from process_img import process_img def main() -> None: - image = Image.open("input.jpg") - image = apply_grayscale(image) - image = apply_invert(image) - image = apply_sepia(image) + input_image: str = "../input.jpg" - image.save("output.jpg") - print("Saved output.jpg") + grayscale = GrayscaleFilter() + grayscale.configure({"intensity": 0.8}) + process_img(input_image, "output_abc_grayscale.jpg", grayscale) + + invert = InvertFilter() + process_img(input_image, "output_abc_invert.jpg", invert) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/abstraction/abstraction_none/process_img.py b/2025/abstraction/abstraction_none/process_img.py new file mode 100644 index 0000000..66c8346 --- /dev/null +++ b/2025/abstraction/abstraction_none/process_img.py @@ -0,0 +1,20 @@ +from typing import Any + +from filters.grayscale import GrayscaleFilter +from filters.invert import InvertFilter +from PIL import Image + + +def process_img(image_path: str, output_path: str, filter_obj: Any) -> None: + print(f"\nUsing filter: {filter_obj.name}") + image = Image.open(image_path) + if isinstance(filter_obj, GrayscaleFilter): + filter_obj.configure({"intensity": 0.8}) + image = filter_obj.apply(image) + elif isinstance(filter_obj, InvertFilter): + image = filter_obj.do_invert(image) + else: + print("Unknown filter type. Skipping configuration.") + + image.save(output_path) + print(f"Saved processed image to {output_path}") diff --git a/2025/abstraction/abstraction_protocol.py b/2025/abstraction/abstraction_protocol.py deleted file mode 100644 index 5902778..0000000 --- a/2025/abstraction/abstraction_protocol.py +++ /dev/null @@ -1,63 +0,0 @@ -from dataclasses import dataclass -from typing import Any, Protocol - -from PIL import Image - - -class ImageFilter(Protocol): - name: str - - def apply(self, image: Image.Image) -> Image.Image: ... - - def configure(self, config: dict[str, Any]) -> None: ... - - -@dataclass -class SepiaFilter: - name: str = "Sepia" - _depth: int = 20 - - def apply(self, image: Image.Image) -> Image.Image: - print(f"Applying {self.name} filter with depth {self._depth}") - sepia_image = image.convert("RGB") - width, height = sepia_image.size - pixels = sepia_image.load() - - for y in range(height): - for x in range(width): - r, g, b = pixels[x, y] - tr = int(0.393 * r + 0.769 * g + 0.189 * b + self._depth) - tg = int(0.349 * r + 0.686 * g + 0.168 * b + self._depth) - tb = int(0.272 * r + 0.534 * g + 0.131 * b + self._depth) - pixels[x, y] = (min(255, tr), min(255, tg), min(255, tb)) - - return sepia_image - - def configure(self, config: dict[str, Any]) -> None: - self._depth = config.get("depth", self._depth) - - -def process_with_protocol( - image_path: str, output_path: str, filter_obj: ImageFilter -) -> None: - print(f"\nUsing filter: {filter_obj.name}") - image = Image.open(image_path) - image = filter_obj.apply(image) - image.save(output_path) - print(f"Saved processed image to {output_path}") - - -# ----------- Main function ----------- - - -def main() -> None: - input_image: str = "input.jpg" # Replace with a real image path - - # Protocol example - sepia = SepiaFilter() - sepia.configure({"depth": 15}) - process_with_protocol(input_image, "output_protocol_sepia.jpg", sepia) - - -if __name__ == "__main__": - main() diff --git a/2025/abstraction/abstraction_protocol/filters/grayscale.py b/2025/abstraction/abstraction_protocol/filters/grayscale.py new file mode 100644 index 0000000..50ac420 --- /dev/null +++ b/2025/abstraction/abstraction_protocol/filters/grayscale.py @@ -0,0 +1,22 @@ +from typing import Any + +from PIL import Image, ImageOps + + +class GrayscaleFilter: + def __init__(self) -> None: + self._intensity: float = 1.0 + + @property + def name(self) -> str: + return "Grayscale" + + def apply(self, image: Image.Image) -> Image.Image: + print(f"Applying {self.name} filter with intensity {self._intensity}") + grayscale_image = ImageOps.grayscale(image) + if self._intensity < 1.0: + return Image.blend(image, grayscale_image, self._intensity) + return grayscale_image + + def configure(self, config: dict[str, Any]) -> None: + self._intensity = config.get("intensity", self._intensity) diff --git a/2025/abstraction/abstraction_protocol/filters/invert.py b/2025/abstraction/abstraction_protocol/filters/invert.py new file mode 100644 index 0000000..856b0f6 --- /dev/null +++ b/2025/abstraction/abstraction_protocol/filters/invert.py @@ -0,0 +1,23 @@ +from typing import Any + +from PIL import Image, ImageOps + + +class InvertFilter: + def __init__(self) -> None: + self._enabled: bool = True + + @property + def name(self) -> str: + return "Invert" + + def apply(self, image: Image.Image) -> Image.Image: + if self._enabled: + print(f"Applying {self.name} filter") + return ImageOps.invert(image.convert("RGB")) + else: + print(f"{self.name} filter disabled, returning original image") + return image + + def configure(self, config: dict[str, Any]) -> None: + self._enabled = config.get("enabled", self._enabled) diff --git a/2025/abstraction/abstraction_protocol/main.py b/2025/abstraction/abstraction_protocol/main.py new file mode 100644 index 0000000..15e5589 --- /dev/null +++ b/2025/abstraction/abstraction_protocol/main.py @@ -0,0 +1,18 @@ +from filters.grayscale import GrayscaleFilter +from filters.invert import InvertFilter +from process_img import process_img + + +def main() -> None: + input_image: str = "../input.jpg" + + grayscale = GrayscaleFilter() + grayscale.configure({"intensity": 0.8}) + process_img(input_image, "output_abc_grayscale.jpg", grayscale) + + invert = InvertFilter() + process_img(input_image, "output_abc_invert.jpg", invert) + + +if __name__ == "__main__": + main() diff --git a/2025/abstraction/abstraction_protocol/process_img.py b/2025/abstraction/abstraction_protocol/process_img.py new file mode 100644 index 0000000..10c34f6 --- /dev/null +++ b/2025/abstraction/abstraction_protocol/process_img.py @@ -0,0 +1,20 @@ +from typing import Any, Protocol + +from PIL import Image + + +class FilterBase(Protocol): + @property + def name(self) -> str: ... + + def apply(self, image: Image.Image) -> Image.Image: ... + + def configure(self, config: dict[str, Any]) -> None: ... + + +def process_img(image_path: str, output_path: str, filter_obj: FilterBase) -> None: + print(f"\nUsing filter: {filter_obj.name}") + image = Image.open(image_path) + image = filter_obj.apply(image) + image.save(output_path) + print(f"Saved processed image to {output_path}") diff --git a/2025/abstraction/filters/grayscale.py b/2025/abstraction/filters/grayscale.py deleted file mode 100644 index bc3dcd6..0000000 --- a/2025/abstraction/filters/grayscale.py +++ /dev/null @@ -1,6 +0,0 @@ -from PIL import Image, ImageOps - - -def apply_grayscale(image: Image.Image) -> Image.Image: - print("Applying grayscale filter") - return ImageOps.grayscale(image) \ No newline at end of file From e94d027c161067ffbcbfe961c4ac8b2438a5cae7 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 14 May 2025 16:53:56 +0200 Subject: [PATCH 12/44] Worked on basic serverless example --- 2025/serverless/.gitignore | 2 + 2025/serverless/Pulumi.yaml | 6 + 2025/serverless/__main__.py | 58 +++ 2025/serverless/functions/channels.json | 20 ++ 2025/serverless/functions/main.py | 7 + 2025/serverless/functions/main_channels.py | 38 ++ 2025/serverless/pyproject.toml | 10 + 2025/serverless/requirements.txt | 6 + 2025/serverless/uv.lock | 398 +++++++++++++++++++++ 9 files changed, 545 insertions(+) create mode 100644 2025/serverless/.gitignore create mode 100644 2025/serverless/Pulumi.yaml create mode 100644 2025/serverless/__main__.py create mode 100644 2025/serverless/functions/channels.json create mode 100644 2025/serverless/functions/main.py create mode 100644 2025/serverless/functions/main_channels.py create mode 100644 2025/serverless/pyproject.toml create mode 100644 2025/serverless/requirements.txt create mode 100644 2025/serverless/uv.lock diff --git a/2025/serverless/.gitignore b/2025/serverless/.gitignore new file mode 100644 index 0000000..a3807e5 --- /dev/null +++ b/2025/serverless/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/2025/serverless/Pulumi.yaml b/2025/serverless/Pulumi.yaml new file mode 100644 index 0000000..d7da578 --- /dev/null +++ b/2025/serverless/Pulumi.yaml @@ -0,0 +1,6 @@ +name: channels +runtime: + name: python + options: + virtualenv: venv +description: A minimal Google Cloud Python Pulumi program diff --git a/2025/serverless/__main__.py b/2025/serverless/__main__.py new file mode 100644 index 0000000..b049b97 --- /dev/null +++ b/2025/serverless/__main__.py @@ -0,0 +1,58 @@ +import os +import time + +import pulumi +from pulumi_gcp import cloudfunctions, storage + +# Disable rule for that module-level exports be ALL_CAPS, for legibility. +# pylint: disable=C0103 + +# File path to where the Cloud Function's source code is located. +PATH_TO_SOURCE_CODE = "./functions" + +# We will store the source code to the Cloud Function in a Google Cloud Storage bucket. +bucket = storage.Bucket("cf_demo_bucket", location="US", force_destroy=True) + +# The Cloud Function source code itself needs to be zipped up into an +# archive, which we create using the pulumi.AssetArchive primitive. +assets = {} +for file in os.listdir(PATH_TO_SOURCE_CODE): + location = os.path.join(PATH_TO_SOURCE_CODE, file) + asset = pulumi.FileAsset(path=location) + assets[file] = asset + +archive = pulumi.AssetArchive(assets=assets) + +# Create the single Cloud Storage object, which contains all of the function's +# source code. ("main.py" and "requirements.txt".) +source_archive_object = storage.BucketObject( + "eta_demo_object", + name=f"main.py-{time.time()}", + bucket=bucket.name, + source=archive, +) + +# Create the Cloud Function, deploying the source we just uploaded to Google +# Cloud Storage. +fxn = cloudfunctions.Function( + "eta_demo_function", + entry_point="hello_name", + region="us-central1", + runtime="python310", + source_archive_bucket=bucket.name, + source_archive_object=source_archive_object.name, + trigger_http=True, +) + +invoker = cloudfunctions.FunctionIamMember( + "invoker", + project=fxn.project, + region=fxn.region, + cloud_function=fxn.name, + role="roles/cloudfunctions.invoker", + member="allUsers", +) + +# Export the DNS name of the bucket and the cloud function URL. +pulumi.export("bucket_name", bucket.url) +pulumi.export("fxn_url", fxn.https_trigger_url) diff --git a/2025/serverless/functions/channels.json b/2025/serverless/functions/channels.json new file mode 100644 index 0000000..e536a19 --- /dev/null +++ b/2025/serverless/functions/channels.json @@ -0,0 +1,20 @@ +[ + { + "id": "codestackr", + "name": "codeSTACKr", + "tags": ["web development", "typescript"], + "description": "My tutorials are generally about web development and include coding languages such as HTML, CSS, Sass, JavaScript, and TypeScript." + }, + { + "id": "jackherrington", + "name": "Jack Herrington", + "tags": ["frontend", "technology"], + "description": "Frontend videos from basic to very advanced; tutorials, technology deep dives. You'll love it!" + }, + { + "id": "arjancodes", + "name": "ArjanCodes", + "tags": ["software design", "python"], + "description": "ArjanCodes focuses on helping you become a better software developer." + } +] \ No newline at end of file diff --git a/2025/serverless/functions/main.py b/2025/serverless/functions/main.py new file mode 100644 index 0000000..c722d1d --- /dev/null +++ b/2025/serverless/functions/main.py @@ -0,0 +1,7 @@ +import flask +import functions_framework + + +@functions_framework.http +def hello(request: flask.Request) -> flask.Response: + return flask.Response("Hello, World!", status=200) diff --git a/2025/serverless/functions/main_channels.py b/2025/serverless/functions/main_channels.py new file mode 100644 index 0000000..3332ca0 --- /dev/null +++ b/2025/serverless/functions/main_channels.py @@ -0,0 +1,38 @@ +import json +from typing import Any + +import flask +import functions_framework + +# Define an internal Flask app +app = flask.Flask("internal") + + +channels: dict[str, Any] = {} + +with open("channels.json", encoding="utf8") as file: + channels_raw = json.load(file) + for channel_raw in channels_raw: + channels[channel_raw["id"]] = channel_raw + + +# Define the internal path, idiomatic Flask definition +@app.route("/channels/", methods=["GET", "POST"]) +def name(channel_id: str): + if channel_id not in channels: + return flask.Response("Channel not found", status=404) + return flask.Response(json.dumps(channels[channel_id]), status=200) + + +@functions_framework.http +def hello(request: flask.Request) -> flask.Response: + # Create a new app context for the internal app + ctx = app.test_request_context( + path=request.full_path, + method=request.method, + ) + ctx.request = request + ctx.push() + response = app.full_dispatch_request() + ctx.pop() + return response diff --git a/2025/serverless/pyproject.toml b/2025/serverless/pyproject.toml new file mode 100644 index 0000000..eda0a67 --- /dev/null +++ b/2025/serverless/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "serverless" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = [ + "flask>=3.1.1", + "functions-framework>=3.8.2", + "pulumi>=3.169.0", + "pulumi-gcp>=8.30.1", +] diff --git a/2025/serverless/requirements.txt b/2025/serverless/requirements.txt new file mode 100644 index 0000000..480f1dc --- /dev/null +++ b/2025/serverless/requirements.txt @@ -0,0 +1,6 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-gcp>=6.0.0,<7.0.0 +fastapi +pydantic +uvicorn +watchfiles \ No newline at end of file diff --git a/2025/serverless/uv.lock b/2025/serverless/uv.lock new file mode 100644 index 0000000..db27bd0 --- /dev/null +++ b/2025/serverless/uv.lock @@ -0,0 +1,398 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" + +[[package]] +name = "arpeggio" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/c4/516bb54456f85ad1947702ea4cef543a59de66d31a9887dbc3d9df36e3e1/Arpeggio-2.0.2.tar.gz", hash = "sha256:c790b2b06e226d2dd468e4fbfb5b7f506cec66416031fde1441cf1de2a0ba700", size = 766643, upload-time = "2023-07-09T12:30:04.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/4f/d28bf30a19d4649b40b501d531b44e73afada99044df100380fd9567e92f/Arpeggio-2.0.2-py2.py3-none-any.whl", hash = "sha256:f7c8ae4f4056a89e020c24c7202ac8df3e2bc84e416746f20b0da35bb1de0250", size = 55287, upload-time = "2023-07-09T12:30:01.87Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "click" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, +] + +[[package]] +name = "cloudevents" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/41/97a7448adf5888d394a22d491749fb55b1e06e95870bd9edc3d58889bb8a/cloudevents-1.11.0.tar.gz", hash = "sha256:5be990583e99f3b08af5a709460e20b25cb169270227957a20b47a6ec8635e66", size = 33670, upload-time = "2024-06-20T13:47:32.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/0e/268a75b712e4dd504cff19e4b987942cd93532d1680009d6492c9d41bdac/cloudevents-1.11.0-py3-none-any.whl", hash = "sha256:77edb4f2b01f405c44ea77120c3213418dbc63d8859f98e9e85de875502b8a76", size = 55088, upload-time = "2024-06-20T13:47:30.066Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268, upload-time = "2025-04-10T19:46:26.044Z" }, + { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077, upload-time = "2025-04-10T19:46:27.464Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127, upload-time = "2025-04-10T19:46:29.467Z" }, + { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249, upload-time = "2025-04-10T19:46:31.538Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e4/395c792b243f2367d84202dc33689aa3d910fb9826a7491ba20fc9e261f5/debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f", size = 2485676, upload-time = "2025-04-10T19:46:32.96Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514, upload-time = "2025-04-10T19:46:34.336Z" }, + { url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756, upload-time = "2025-04-10T19:46:36.199Z" }, + { url = "https://files.pythonhosted.org/packages/e0/62/a7b4a57013eac4ccaef6977966e6bec5c63906dd25a86e35f155952e29a1/debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e", size = 5297119, upload-time = "2025-04-10T19:46:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, +] + +[[package]] +name = "flask" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440, upload-time = "2025-05-13T15:01:17.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" }, +] + +[[package]] +name = "functions-framework" +version = "3.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudevents" }, + { name = "flask" }, + { name = "gunicorn", marker = "sys_platform != 'win32'" }, + { name = "watchdog" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/80/b19ccfd7148a487c4583d03a3b9f21680cc19beb2059ec838209caa1d7b2/functions_framework-3.8.2.tar.gz", hash = "sha256:109bcdca01244067052a605536b44d042903b3805d093cd32e343ba5affffc90", size = 44392, upload-time = "2024-11-13T21:41:17.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/79/7e2391feb0fbfa2d1466944c030070fa4c7f5cac74e26680c42f5b622c21/functions_framework-3.8.2-py3-none-any.whl", hash = "sha256:ecbe8e4566efca9ed1718f210ac92d47fc47ec3a448d2bca3b4bb5888bceca08", size = 35963, upload-time = "2024-11-13T21:41:15.535Z" }, +] + +[[package]] +name = "grpcio" +version = "1.66.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/d1/49a96df4eb1d805cf546247df40636515416d2d5c66665e5129c8b4162a8/grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231", size = 12489713, upload-time = "2024-09-28T12:44:01.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/5c/c4da36b7a77dbb15c4bc72228dff7161874752b2c6bddf7bb046d9da1b90/grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf", size = 5002933, upload-time = "2024-09-28T12:38:24.109Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d5/b631445dff250a5301f51ff56c5fc917c7f955cd02fa55379f158a89abeb/grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8", size = 10793953, upload-time = "2024-09-28T12:38:27.02Z" }, + { url = "https://files.pythonhosted.org/packages/c8/1c/2179ac112152e92c02990f98183edf645df14aa3c38b39f1a3a60358b6c6/grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6", size = 5499791, upload-time = "2024-09-28T12:38:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/0b/53/8d7ab865fbd983309c8242930f00b28a01047f70c2b2e4c79a5c92a46a08/grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7", size = 6109606, upload-time = "2024-09-28T12:38:33.566Z" }, + { url = "https://files.pythonhosted.org/packages/86/e9/3dfb5a3ff540636d46b8b723345e923e8c553d9b3f6a8d1b09b0d915eb46/grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd", size = 5762866, upload-time = "2024-09-28T12:38:36.023Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cb/c07493ad5dd73d51e4e15b0d483ff212dfec136ee1e4f3b49d115bdc7a13/grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee", size = 6446819, upload-time = "2024-09-28T12:38:38.69Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5f/142e19db367a34ea0ee8a8451e43215d0a1a5dbffcfdcae8801f22903301/grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c", size = 6040273, upload-time = "2024-09-28T12:38:41.348Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3b/12fcd752c55002e4b0e0a7bd5faec101bc0a4e3890be3f95a43353142481/grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453", size = 3537988, upload-time = "2024-09-28T12:38:44.544Z" }, + { url = "https://files.pythonhosted.org/packages/f1/70/76bfea3faa862bfceccba255792e780691ff25b8227180759c9d38769379/grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679", size = 4275553, upload-time = "2024-09-28T12:38:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/8708a8dfb3f1ac89926c27c5dd17412764157a2959dbc5a606eaf8ac71f6/grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d", size = 5004245, upload-time = "2024-09-28T12:38:50.596Z" }, + { url = "https://files.pythonhosted.org/packages/8b/37/0b57c3769efb3cc9ec97fcaa9f7243046660e7ed58c0faebc4ef315df92c/grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34", size = 10756749, upload-time = "2024-09-28T12:38:54.131Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5a/425e995724a19a1b110340ed653bc7c5de8019d9fc84b3798a0f79c3eb31/grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed", size = 5499666, upload-time = "2024-09-28T12:38:57.145Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e4/86a5c5ec40a6b683671a1d044ebca433812d99da8fcfc2889e9c43cecbd4/grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7", size = 6109578, upload-time = "2024-09-28T12:38:59.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/86/a86742f3deaa22385c3bff984c5947fc62d47d3fab26c508730037d027e5/grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46", size = 5763274, upload-time = "2024-09-28T12:39:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/c3/61/b9a2a4345dea0a354c4ed8ac7aacbdd0ff986acbc8f92680213cf3d2faa3/grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a", size = 6450416, upload-time = "2024-09-28T12:39:05.06Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/ad303ce75d8cd71d855a661519aa160ce42f27498f589f1ae6d9f8c5e8ac/grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b", size = 6040045, upload-time = "2024-09-28T12:39:08.214Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b3/8db1873e3240ef1672ba87b89e949ece367089e29e4d221377bfdd288bd3/grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75", size = 3537126, upload-time = "2024-09-28T12:39:10.655Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/133216989fe7e17caeafd7ff5b17cc82c4e722025d0b8d5d2290c11fe2e6/grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf", size = 4278018, upload-time = "2024-09-28T12:39:13.196Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "parver" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arpeggio" }, + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/e5/1c774688a90f0b76e872e30f6f1ba3f5e14056cd0d96a684047d4a986226/parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777", size = 26908, upload-time = "2023-10-03T21:06:54.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/4c/f98024021bef4d44dce3613feebd702c7ad8883f777ff8488384c59e9774/parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2", size = 15172, upload-time = "2023-10-03T21:06:52.796Z" }, +] + +[[package]] +name = "pip" +version = "25.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/de/241caa0ca606f2ec5fe0c1f4261b0465df78d786a38da693864a116c37f4/pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077", size = 1940155, upload-time = "2025-05-02T15:14:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af", size = 1825227, upload-time = "2025-05-02T15:13:59.102Z" }, +] + +[[package]] +name = "protobuf" +version = "4.25.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/63/84fdeac1f03864c2b8b9f0b7fe711c4af5f95759ee281d2026530086b2f5/protobuf-4.25.7.tar.gz", hash = "sha256:28f65ae8c14523cc2c76c1e91680958700d3eac69f45c96512c12c63d9a38807", size = 380612, upload-time = "2025-04-24T02:56:58.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/ed/9a58076cfb8edc237c92617f1d3744660e9b4457d54f3c2fdf1a4bbae5c7/protobuf-4.25.7-cp310-abi3-win32.whl", hash = "sha256:dc582cf1a73a6b40aa8e7704389b8d8352da616bc8ed5c6cc614bdd0b5ce3f7a", size = 392457, upload-time = "2025-04-24T02:56:40.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/b3/e00870528029fe252cf3bd6fa535821c276db3753b44a4691aee0d52ff9e/protobuf-4.25.7-cp310-abi3-win_amd64.whl", hash = "sha256:cd873dbddb28460d1706ff4da2e7fac175f62f2a0bebc7b33141f7523c5a2399", size = 413446, upload-time = "2025-04-24T02:56:44.199Z" }, + { url = "https://files.pythonhosted.org/packages/60/1d/f450a193f875a20099d4492d2c1cb23091d65d512956fb1e167ee61b4bf0/protobuf-4.25.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4c899f09b0502eb39174c717ccf005b844ea93e31137c167ddcacf3e09e49610", size = 394248, upload-time = "2025-04-24T02:56:45.75Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/ea88e9857484a0618c74121618b9e620fc50042de43cdabbebe1b93a83e0/protobuf-4.25.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:6d2f5dede3d112e573f0e5f9778c0c19d9f9e209727abecae1d39db789f522c6", size = 293717, upload-time = "2025-04-24T02:56:47.427Z" }, + { url = "https://files.pythonhosted.org/packages/a7/81/d0b68e9a9a76804113b6dedc6fffed868b97048bbe6f1bedc675bdb8523c/protobuf-4.25.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:d41fb7ae72a25fcb79b2d71e4247f0547a02e8185ed51587c22827a87e5736ed", size = 294636, upload-time = "2025-04-24T02:56:48.976Z" }, + { url = "https://files.pythonhosted.org/packages/17/d7/1e7c80cb2ea2880cfe38580dcfbb22b78b746640c9c13fc3337a6967dc4c/protobuf-4.25.7-py3-none-any.whl", hash = "sha256:e9d969f5154eaeab41404def5dcf04e62162178f4b9de98b2d3c1c70f5f84810", size = 156468, upload-time = "2025-04-24T02:56:56.957Z" }, +] + +[[package]] +name = "pulumi" +version = "3.169.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "debugpy" }, + { name = "dill" }, + { name = "grpcio" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "semver" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f7/fdf271f5c576f380745c20c47812e98956f0871d3569125c93476ab44418/pulumi-3.169.0-py3-none-any.whl", hash = "sha256:8288d5baf63d280793d118c2df5708ed24327e59f20ecf3c86fc0900da2e8c38", size = 337496, upload-time = "2025-05-08T11:53:42.854Z" }, +] + +[[package]] +name = "pulumi-gcp" +version = "8.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/37/5e45019b81317c98ebba738104777edc77a698708292213315af6bf8098d/pulumi_gcp-8.30.1.tar.gz", hash = "sha256:e32f21d47d35439140a47a467cbabee66790d57d8c7f900e51ee612129168035", size = 7586280, upload-time = "2025-05-13T12:51:37.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/fd/395e816595575576bf4dd29c44d03ce5402aebdb56ac6f0813f55b76e1df/pulumi_gcp-8.30.1-py3-none-any.whl", hash = "sha256:71687a8f16c0e05846d699956c7da79ed5b633afab3a65671de0163167a6c948", size = 9603797, upload-time = "2025-05-13T12:51:33.698Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "serverless" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, + { name = "functions-framework" }, + { name = "pulumi" }, + { name = "pulumi-gcp" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = ">=3.1.1" }, + { name = "functions-framework", specifier = ">=3.8.2" }, + { name = "pulumi", specifier = ">=3.169.0" }, + { name = "pulumi-gcp", specifier = ">=8.30.1" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] From e04372c52f3cb566410add2d2a8a8395d6e80453 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 15 May 2025 16:23:22 +0200 Subject: [PATCH 13/44] Added mcp example --- 2025/mcp-server/pyproject.toml | 9 + 2025/mcp-server/uv.lock | 421 ++++++++++++++++++++++++++++ 2025/mcp-server/video_repository.py | 9 + 2025/mcp-server/videos.py | 39 +++ 4 files changed, 478 insertions(+) create mode 100644 2025/mcp-server/pyproject.toml create mode 100644 2025/mcp-server/uv.lock create mode 100644 2025/mcp-server/video_repository.py create mode 100644 2025/mcp-server/videos.py diff --git a/2025/mcp-server/pyproject.toml b/2025/mcp-server/pyproject.toml new file mode 100644 index 0000000..ca38033 --- /dev/null +++ b/2025/mcp-server/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "mcp-example" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "httpx>=0.28.1", + "mcp[cli]>=1.8.1", + "youtube-search>=2.1.2", +] diff --git a/2025/mcp-server/uv.lock b/2025/mcp-server/uv.lock new file mode 100644 index 0000000..62b2243 --- /dev/null +++ b/2025/mcp-server/uv.lock @@ -0,0 +1,421 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mcp" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/13/16b712e8a3be6a736b411df2fc6b4e75eb1d3e99b1cd57a3a1decf17f612/mcp-1.8.1.tar.gz", hash = "sha256:ec0646271d93749f784d2316fb5fe6102fb0d1be788ec70a9e2517e8f2722c0e", size = 265605, upload-time = "2025-05-12T17:33:57.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/5d/91cf0d40e40ae9ecf8d4004e0f9611eea86085aa0b5505493e0ff53972da/mcp-1.8.1-py3-none-any.whl", hash = "sha256:948e03783859fa35abe05b9b6c0a1d5519be452fc079dc8d7f682549591c1770", size = 119761, upload-time = "2025-05-12T17:33:56.136Z" }, +] + +[package.optional-dependencies] +cli = [ + { name = "python-dotenv" }, + { name = "typer" }, +] + +[[package]] +name = "mcp-example" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "httpx" }, + { name = "mcp", extra = ["cli"] }, + { name = "youtube-search" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mcp", extras = ["cli"], specifier = ">=1.8.1" }, + { name = "youtube-search", specifier = ">=2.1.2" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/5f/28f45b1ff14bee871bacafd0a97213f7ec70e389939a80c60c0fb72a9fc9/sse_starlette-2.3.5.tar.gz", hash = "sha256:228357b6e42dcc73a427990e2b4a03c023e2495ecee82e14f07ba15077e334b2", size = 17511, upload-time = "2025-05-12T18:23:52.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/48/3e49cf0f64961656402c0023edbc51844fe17afe53ab50e958a6dbbbd499/sse_starlette-2.3.5-py3-none-any.whl", hash = "sha256:251708539a335570f10eaaa21d1848a10c42ee6dc3a9cf37ef42266cdb1c52a8", size = 10233, upload-time = "2025-05-12T18:23:50.722Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "typer" +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] + +[[package]] +name = "youtube-search" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/66/87d6d426e6e7cbd9b675d712f2538d55023cb9394411e54cdb60cdc9e002/youtube-search-2.1.2.tar.gz", hash = "sha256:5749a6d8076fda65557c91367f0fab0936290ecdea457c831c9f3f05918a3aa2", size = 3167, upload-time = "2022-10-05T01:47:44.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4e/548b906dd72bb51f700feb4f2200de3e0c64b34509d3173afb196ad7f1b9/youtube_search-2.1.2-py3-none-any.whl", hash = "sha256:f9cfaaf7c59806777eb29d3b8acee96b50e80f73a0621c2bb220d4e5beb09e4f", size = 3395, upload-time = "2022-10-05T01:47:42.63Z" }, +] diff --git a/2025/mcp-server/video_repository.py b/2025/mcp-server/video_repository.py new file mode 100644 index 0000000..21b225b --- /dev/null +++ b/2025/mcp-server/video_repository.py @@ -0,0 +1,9 @@ +from typing import Any + +from youtube_search import YoutubeSearch + + +def search_youtube(query: str, max_results: int = 10) -> dict[str, Any]: + """Search YouTube for a given query and return the results.""" + results = YoutubeSearch(query, max_results=max_results).to_dict() + return results diff --git a/2025/mcp-server/videos.py b/2025/mcp-server/videos.py new file mode 100644 index 0000000..f244e55 --- /dev/null +++ b/2025/mcp-server/videos.py @@ -0,0 +1,39 @@ +from mcp.server.fastmcp import FastMCP +from video_repository import search_youtube + +# Initialize FastMCP server +mcp = FastMCP("videos") + + +def format_video(video: dict[str, str]) -> str: + """Format a video feature into a readable string.""" + return f""" + Title: {video.get("title", "Unknown")} + Channel: {video.get("channel", "Unknown")} + Duration: {video.get("duration", "Unknown")} + Description: {video.get("description", "No description available")} + Views: {video.get("views", "Unknown")} + URL: https://www.youtube.com/watch?v={video.get("id", "Unknown")} + Published: {video.get("publish_time", "Unknown")} + """ + + +@mcp.tool() +async def get_videos(search: str, max_results: int) -> str: + """Get videos for a search query. + + Args: + search: Search query string + max_results: Maximum number of results to return + """ + results = search_youtube(search, max_results=max_results) + if not results: + return "No videos found." + + videos = [format_video(video) for video in results] + return "\n---\n".join(videos) + + +if __name__ == "__main__": + # Initialize and run the server + mcp.run(transport="stdio") From e89df1a32300c218a88f17a7314c78993d19083f Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 16 May 2025 16:34:48 +0200 Subject: [PATCH 14/44] Added API example + MCP server that integrates with API --- 2025/mcp-server/README.md | 3 + 2025/mcp-server/{videos.py => mcp_videos.py} | 2 +- 2025/mcp-server/mcp_videos_api.py | 49 ++++ 2025/mcp-server/pyproject.toml | 2 + 2025/mcp-server/uv.lock | 223 ++++++++++++++++++ 2025/mcp-server/video_api.py | 52 ++++ .../{video_repository.py => yt_helper.py} | 2 +- 7 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 2025/mcp-server/README.md rename 2025/mcp-server/{videos.py => mcp_videos.py} (96%) create mode 100644 2025/mcp-server/mcp_videos_api.py create mode 100644 2025/mcp-server/video_api.py rename 2025/mcp-server/{video_repository.py => yt_helper.py} (73%) diff --git a/2025/mcp-server/README.md b/2025/mcp-server/README.md new file mode 100644 index 0000000..fd34532 --- /dev/null +++ b/2025/mcp-server/README.md @@ -0,0 +1,3 @@ +# Sample CURL request to API + +curl -X GET "http://localhost:8000/videos?search=python%20design%20patterns&max_results=3" -H "accept: application/json" \ No newline at end of file diff --git a/2025/mcp-server/videos.py b/2025/mcp-server/mcp_videos.py similarity index 96% rename from 2025/mcp-server/videos.py rename to 2025/mcp-server/mcp_videos.py index f244e55..3a3c176 100644 --- a/2025/mcp-server/videos.py +++ b/2025/mcp-server/mcp_videos.py @@ -1,5 +1,5 @@ from mcp.server.fastmcp import FastMCP -from video_repository import search_youtube +from yt_helper import search_youtube # Initialize FastMCP server mcp = FastMCP("videos") diff --git a/2025/mcp-server/mcp_videos_api.py b/2025/mcp-server/mcp_videos_api.py new file mode 100644 index 0000000..9fa3542 --- /dev/null +++ b/2025/mcp-server/mcp_videos_api.py @@ -0,0 +1,49 @@ +import httpx +from mcp.server.fastmcp import FastMCP + +# Initialize FastMCP server +mcp = FastMCP("videos") + +API_URL = "http://localhost:8000/videos" # Adjust if hosted elsewhere + + +def format_video(video: dict[str, str]) -> str: + """Format a video feature into a readable string.""" + return f""" +Title: {video.get("title", "Unknown")} +Channel: {video.get("channel", "Unknown")} +Duration: {video.get("duration", "Unknown")} +Description: {video.get("description", "No description available")} +Views: {video.get("views", "Unknown")} +URL: {video.get("url", "Unknown")} +Published: {video.get("publish_time", "Unknown")} +""".strip() + + +@mcp.tool() +async def get_videos(search: str, max_results: int) -> str: + """Get videos for a search query via external FastAPI endpoint. + + Args: + search: Search query string + max_results: Maximum number of results to return + """ + async with httpx.AsyncClient() as client: + try: + response = await client.get( + API_URL, params={"search": search, "max_results": max_results} + ) + response.raise_for_status() + data = response.json() + videos = data.get("results", []) + except Exception as e: + return f"Error retrieving videos: {str(e)}" + + if not videos: + return "No videos found." + + return "\n---\n".join(format_video(video) for video in videos) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/2025/mcp-server/pyproject.toml b/2025/mcp-server/pyproject.toml index ca38033..7a88121 100644 --- a/2025/mcp-server/pyproject.toml +++ b/2025/mcp-server/pyproject.toml @@ -3,7 +3,9 @@ name = "mcp-example" version = "0.1.0" requires-python = ">=3.13" dependencies = [ + "fastapi[standard]>=0.115.12", "httpx>=0.28.1", "mcp[cli]>=1.8.1", + "uvicorn>=0.34.2", "youtube-search>=2.1.2", ] diff --git a/2025/mcp-server/uv.lock b/2025/mcp-server/uv.lock index 62b2243..c52c33b 100644 --- a/2025/mcp-server/uv.lock +++ b/2025/mcp-server/uv.lock @@ -76,6 +76,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753, upload-time = "2024-12-15T14:28:10.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705, upload-time = "2024-12-15T14:28:06.18Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "uvicorn", extra = ["standard"] }, +] + [[package]] name = "h11" version = "0.16.0" @@ -98,6 +163,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -131,6 +211,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -143,6 +235,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + [[package]] name = "mcp" version = "1.8.1" @@ -174,15 +294,19 @@ name = "mcp-example" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "fastapi", extra = ["standard"] }, { name = "httpx" }, { name = "mcp", extra = ["cli"] }, + { name = "uvicorn" }, { name = "youtube-search" }, ] [package.metadata] requires-dist = [ + { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "mcp", extras = ["cli"], specifier = ">=1.8.1" }, + { name = "uvicorn", specifier = ">=0.34.2" }, { name = "youtube-search", specifier = ">=2.1.2" }, ] @@ -279,6 +403,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + [[package]] name = "requests" version = "2.32.3" @@ -307,6 +448,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, ] +[[package]] +name = "rich-toolkit" +version = "0.14.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/31/b6d055f291a660a7bcaec4bcc9457b9fef8ecb6293e527b1eef1840aefd4/rich_toolkit-0.14.6.tar.gz", hash = "sha256:9dbd40e83414b84e828bf899115fff8877ce5951b73175f44db142902f07645d", size = 110805, upload-time = "2025-05-12T19:19:15.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/3c/7a824c0514e87c61000583ac22c8321da6dc8e58a93d5f56e583482a2ee0/rich_toolkit-0.14.6-py3-none-any.whl", hash = "sha256:764f3a5f9e4b539ce805596863299e8982599514906dc5e3ccc2d390ef74c301", size = 24815, upload-time = "2025-05-12T19:19:13.713Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -408,6 +563,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, ] +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/62/435766874b704f39b2fecd8395a29042db2b5ec4005bd34523415e9bd2e0/watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d", size = 401531, upload-time = "2025-04-08T10:35:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a6/e52a02c05411b9cb02823e6797ef9bbba0bfaf1bb627da1634d44d8af833/watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763", size = 392417, upload-time = "2025-04-08T10:35:37.048Z" }, + { url = "https://files.pythonhosted.org/packages/3f/53/c4af6819770455932144e0109d4854437769672d7ad897e76e8e1673435d/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40", size = 453423, upload-time = "2025-04-08T10:35:38.357Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d1/8e88df58bbbf819b8bc5cfbacd3c79e01b40261cad0fc84d1e1ebd778a07/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563", size = 458185, upload-time = "2025-04-08T10:35:39.708Z" }, + { url = "https://files.pythonhosted.org/packages/ff/70/fffaa11962dd5429e47e478a18736d4e42bec42404f5ee3b92ef1b87ad60/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04", size = 486696, upload-time = "2025-04-08T10:35:41.469Z" }, + { url = "https://files.pythonhosted.org/packages/39/db/723c0328e8b3692d53eb273797d9a08be6ffb1d16f1c0ba2bdbdc2a3852c/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f", size = 522327, upload-time = "2025-04-08T10:35:43.289Z" }, + { url = "https://files.pythonhosted.org/packages/cd/05/9fccc43c50c39a76b68343484b9da7b12d42d0859c37c61aec018c967a32/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a", size = 499741, upload-time = "2025-04-08T10:35:44.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/14/499e90c37fa518976782b10a18b18db9f55ea73ca14641615056f8194bb3/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827", size = 453995, upload-time = "2025-04-08T10:35:46.336Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/f75d6840059320df5adecd2c687fbc18960a7f97b55c300d20f207d48aef/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a", size = 629693, upload-time = "2025-04-08T10:35:48.161Z" }, + { url = "https://files.pythonhosted.org/packages/fc/17/180ca383f5061b61406477218c55d66ec118e6c0c51f02d8142895fcf0a9/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936", size = 624677, upload-time = "2025-04-08T10:35:49.65Z" }, + { url = "https://files.pythonhosted.org/packages/bf/15/714d6ef307f803f236d69ee9d421763707899d6298d9f3183e55e366d9af/watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc", size = 277804, upload-time = "2025-04-08T10:35:51.093Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload-time = "2025-04-08T10:35:52.458Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + [[package]] name = "youtube-search" version = "2.1.2" diff --git a/2025/mcp-server/video_api.py b/2025/mcp-server/video_api.py new file mode 100644 index 0000000..54bcdca --- /dev/null +++ b/2025/mcp-server/video_api.py @@ -0,0 +1,52 @@ +from typing import Optional + +import uvicorn +from fastapi import FastAPI, Query +from pydantic import BaseModel +from yt_helper import search_youtube + +app = FastAPI(title="YouTube Video Search API") + + +class Video(BaseModel): + title: str + channel: str + duration: str + description: Optional[str] + views: Optional[str] + url: str + publish_time: Optional[str] + + +@app.get("/videos", response_model=list[Video]) +async def get_videos( + search: str = Query(..., description="Search query"), + max_results: int = Query( + 5, ge=1, le=50, description="Max number of videos to return" + ), +): + """Search for YouTube videos.""" + results = search_youtube(search, max_results=max_results) + + formatted = [ + Video( + title=video.get("title", "Unknown"), + channel=video.get("channel", "Unknown"), + duration=video.get("duration", "Unknown"), + description=video.get("description", None), + views=video.get("views", None), + url=f"https://www.youtube.com/watch?v={video.get('id', '')}", + publish_time=video.get("publish_time", None), + ) + for video in results + ] + + return formatted + + +def main(): + uvicorn.run("video_api:app", host="0.0.0.0", port=8000, reload=True) + + +if __name__ == "__main__": + main() diff --git a/2025/mcp-server/video_repository.py b/2025/mcp-server/yt_helper.py similarity index 73% rename from 2025/mcp-server/video_repository.py rename to 2025/mcp-server/yt_helper.py index 21b225b..6f54cfd 100644 --- a/2025/mcp-server/video_repository.py +++ b/2025/mcp-server/yt_helper.py @@ -3,7 +3,7 @@ from youtube_search import YoutubeSearch -def search_youtube(query: str, max_results: int = 10) -> dict[str, Any]: +def search_youtube(query: str, max_results: int = 10) -> list[dict[str, Any]]: """Search YouTube for a given query and return the results.""" results = YoutubeSearch(query, max_results=max_results).to_dict() return results From deb5c5fde9b18955a82927c1627860cc0b0bae1e Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 20 May 2025 15:40:56 +0200 Subject: [PATCH 15/44] Changed example to use 2nd gen cloud run functions. Cleanup. --- 2025/serverless/.gitignore | 2 - 2025/serverless/Pulumi.testing.yaml | 3 + 2025/serverless/Pulumi.yaml | 7 +- 2025/serverless/__main__.py | 80 ++++++++----------- .../{functions => function}/channels.json | 0 .../{functions/main.py => function/hello.py} | 2 +- .../main_channels.py => function/main.py} | 2 +- 2025/serverless/pyproject.toml | 9 +-- 2025/serverless/requirements.txt | 6 -- 2025/serverless/uv.lock | 61 +++----------- 10 files changed, 58 insertions(+), 114 deletions(-) delete mode 100644 2025/serverless/.gitignore create mode 100644 2025/serverless/Pulumi.testing.yaml rename 2025/serverless/{functions => function}/channels.json (100%) rename 2025/serverless/{functions/main.py => function/hello.py} (66%) rename 2025/serverless/{functions/main_channels.py => function/main.py} (93%) delete mode 100644 2025/serverless/requirements.txt diff --git a/2025/serverless/.gitignore b/2025/serverless/.gitignore deleted file mode 100644 index a3807e5..0000000 --- a/2025/serverless/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -venv/ diff --git a/2025/serverless/Pulumi.testing.yaml b/2025/serverless/Pulumi.testing.yaml new file mode 100644 index 0000000..346cf12 --- /dev/null +++ b/2025/serverless/Pulumi.testing.yaml @@ -0,0 +1,3 @@ +config: + gcp:project: acpulumi-460409 + gcp:region: europe-west1 diff --git a/2025/serverless/Pulumi.yaml b/2025/serverless/Pulumi.yaml index d7da578..c84cacb 100644 --- a/2025/serverless/Pulumi.yaml +++ b/2025/serverless/Pulumi.yaml @@ -1,6 +1,7 @@ -name: channels +name: serverless-py runtime: name: python options: - virtualenv: venv -description: A minimal Google Cloud Python Pulumi program + toolchain: uv + virtualenv: .venv +description: Basic example of a serverless Python and Go GCP functions (in Python) \ No newline at end of file diff --git a/2025/serverless/__main__.py b/2025/serverless/__main__.py index b049b97..d72b9f9 100644 --- a/2025/serverless/__main__.py +++ b/2025/serverless/__main__.py @@ -1,58 +1,44 @@ -import os -import time +from pulumi import asset, export +from pulumi_gcp import cloudfunctionsv2, storage -import pulumi -from pulumi_gcp import cloudfunctions, storage +bucket = storage.Bucket("bucket", location="europe-west1") -# Disable rule for that module-level exports be ALL_CAPS, for legibility. -# pylint: disable=C0103 - -# File path to where the Cloud Function's source code is located. -PATH_TO_SOURCE_CODE = "./functions" - -# We will store the source code to the Cloud Function in a Google Cloud Storage bucket. -bucket = storage.Bucket("cf_demo_bucket", location="US", force_destroy=True) - -# The Cloud Function source code itself needs to be zipped up into an -# archive, which we create using the pulumi.AssetArchive primitive. -assets = {} -for file in os.listdir(PATH_TO_SOURCE_CODE): - location = os.path.join(PATH_TO_SOURCE_CODE, file) - asset = pulumi.FileAsset(path=location) - assets[file] = asset - -archive = pulumi.AssetArchive(assets=assets) - -# Create the single Cloud Storage object, which contains all of the function's -# source code. ("main.py" and "requirements.txt".) -source_archive_object = storage.BucketObject( - "eta_demo_object", - name=f"main.py-{time.time()}", +py_bucket_object = storage.BucketObject( + "python-zip", bucket=bucket.name, - source=archive, + source=asset.AssetArchive({".": asset.FileArchive("./function")}), ) -# Create the Cloud Function, deploying the source we just uploaded to Google -# Cloud Storage. -fxn = cloudfunctions.Function( - "eta_demo_function", - entry_point="hello_name", - region="us-central1", - runtime="python310", - source_archive_bucket=bucket.name, - source_archive_object=source_archive_object.name, - trigger_http=True, +py_function = cloudfunctionsv2.Function( + "python-func", + name="python-func", + location="europe-west1", + build_config={ + "runtime": "python313", + "entry_point": "channels_handler", + "source": { + "storage_source": { + "bucket": bucket.name, + "object": py_bucket_object.name, + }, + }, + }, + service_config={ + "max_instance_count": 1, + "available_memory": "256M", + "timeout_seconds": 60, + }, ) -invoker = cloudfunctions.FunctionIamMember( - "invoker", - project=fxn.project, - region=fxn.region, - cloud_function=fxn.name, + +py_invoker = cloudfunctionsv2.FunctionIamMember( + "py-invoker", + project=py_function.project, + location=py_function.location, + cloud_function=py_function.name, role="roles/cloudfunctions.invoker", + # role="roles/run.invoker", member="allUsers", ) -# Export the DNS name of the bucket and the cloud function URL. -pulumi.export("bucket_name", bucket.url) -pulumi.export("fxn_url", fxn.https_trigger_url) +export("python_endpoint", py_function.url) diff --git a/2025/serverless/functions/channels.json b/2025/serverless/function/channels.json similarity index 100% rename from 2025/serverless/functions/channels.json rename to 2025/serverless/function/channels.json diff --git a/2025/serverless/functions/main.py b/2025/serverless/function/hello.py similarity index 66% rename from 2025/serverless/functions/main.py rename to 2025/serverless/function/hello.py index c722d1d..cba3e5d 100644 --- a/2025/serverless/functions/main.py +++ b/2025/serverless/function/hello.py @@ -3,5 +3,5 @@ @functions_framework.http -def hello(request: flask.Request) -> flask.Response: +def hello_handler(request: flask.Request) -> flask.Response: return flask.Response("Hello, World!", status=200) diff --git a/2025/serverless/functions/main_channels.py b/2025/serverless/function/main.py similarity index 93% rename from 2025/serverless/functions/main_channels.py rename to 2025/serverless/function/main.py index 3332ca0..b1d882b 100644 --- a/2025/serverless/functions/main_channels.py +++ b/2025/serverless/function/main.py @@ -25,7 +25,7 @@ def name(channel_id: str): @functions_framework.http -def hello(request: flask.Request) -> flask.Response: +def channels_handler(request: flask.Request) -> flask.Response: # Create a new app context for the internal app ctx = app.test_request_context( path=request.full_path, diff --git a/2025/serverless/pyproject.toml b/2025/serverless/pyproject.toml index eda0a67..5920e6f 100644 --- a/2025/serverless/pyproject.toml +++ b/2025/serverless/pyproject.toml @@ -1,10 +1,9 @@ [project] name = "serverless" version = "0.1.0" -requires-python = ">=3.12" +requires-python = ">=3.13" dependencies = [ - "flask>=3.1.1", - "functions-framework>=3.8.2", - "pulumi>=3.169.0", - "pulumi-gcp>=8.30.1", + "functions-framework>=3.8.3", + "pulumi>=3.170.0", + "pulumi-gcp>=8.31.0", ] diff --git a/2025/serverless/requirements.txt b/2025/serverless/requirements.txt deleted file mode 100644 index 480f1dc..0000000 --- a/2025/serverless/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -pulumi>=3.0.0,<4.0.0 -pulumi-gcp>=6.0.0,<7.0.0 -fastapi -pydantic -uvicorn -watchfiles \ No newline at end of file diff --git a/2025/serverless/uv.lock b/2025/serverless/uv.lock index db27bd0..fad3ea1 100644 --- a/2025/serverless/uv.lock +++ b/2025/serverless/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.12" +requires-python = ">=3.13" [[package]] name = "arpeggio" @@ -68,10 +68,6 @@ version = "1.8.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268, upload-time = "2025-04-10T19:46:26.044Z" }, - { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077, upload-time = "2025-04-10T19:46:27.464Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127, upload-time = "2025-04-10T19:46:29.467Z" }, - { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249, upload-time = "2025-04-10T19:46:31.538Z" }, { url = "https://files.pythonhosted.org/packages/4d/e4/395c792b243f2367d84202dc33689aa3d910fb9826a7491ba20fc9e261f5/debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f", size = 2485676, upload-time = "2025-04-10T19:46:32.96Z" }, { url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514, upload-time = "2025-04-10T19:46:34.336Z" }, { url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756, upload-time = "2025-04-10T19:46:36.199Z" }, @@ -119,7 +115,7 @@ wheels = [ [[package]] name = "functions-framework" -version = "3.8.2" +version = "3.8.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -129,9 +125,9 @@ dependencies = [ { name = "watchdog" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/80/b19ccfd7148a487c4583d03a3b9f21680cc19beb2059ec838209caa1d7b2/functions_framework-3.8.2.tar.gz", hash = "sha256:109bcdca01244067052a605536b44d042903b3805d093cd32e343ba5affffc90", size = 44392, upload-time = "2024-11-13T21:41:17.467Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/1f/367a737a58b53f22113e7f443598a3907d67a5ddf1c45f9d35a6ba642bb0/functions_framework-3.8.3.tar.gz", hash = "sha256:95827698469e3979518d52e32def2f11230465877fef32afd49045013b2a469c", size = 43640, upload-time = "2025-05-16T18:27:34.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/79/7e2391feb0fbfa2d1466944c030070fa4c7f5cac74e26680c42f5b622c21/functions_framework-3.8.2-py3-none-any.whl", hash = "sha256:ecbe8e4566efca9ed1718f210ac92d47fc47ec3a448d2bca3b4bb5888bceca08", size = 35963, upload-time = "2024-11-13T21:41:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/26/52/b0b1fe1b964a9afe6b7da41c88434f09c16bf915cd48b9185e839f96fa6c/functions_framework-3.8.3-py3-none-any.whl", hash = "sha256:fd352272c02ee08b4a3445e234213e43fbdd350365d98e2cce13e1575490bffe", size = 36051, upload-time = "2025-05-16T18:27:33.037Z" }, ] [[package]] @@ -140,15 +136,6 @@ version = "1.66.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/71/d1/49a96df4eb1d805cf546247df40636515416d2d5c66665e5129c8b4162a8/grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231", size = 12489713, upload-time = "2024-09-28T12:44:01.429Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/5c/c4da36b7a77dbb15c4bc72228dff7161874752b2c6bddf7bb046d9da1b90/grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf", size = 5002933, upload-time = "2024-09-28T12:38:24.109Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d5/b631445dff250a5301f51ff56c5fc917c7f955cd02fa55379f158a89abeb/grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8", size = 10793953, upload-time = "2024-09-28T12:38:27.02Z" }, - { url = "https://files.pythonhosted.org/packages/c8/1c/2179ac112152e92c02990f98183edf645df14aa3c38b39f1a3a60358b6c6/grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6", size = 5499791, upload-time = "2024-09-28T12:38:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/0b/53/8d7ab865fbd983309c8242930f00b28a01047f70c2b2e4c79a5c92a46a08/grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7", size = 6109606, upload-time = "2024-09-28T12:38:33.566Z" }, - { url = "https://files.pythonhosted.org/packages/86/e9/3dfb5a3ff540636d46b8b723345e923e8c553d9b3f6a8d1b09b0d915eb46/grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd", size = 5762866, upload-time = "2024-09-28T12:38:36.023Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cb/c07493ad5dd73d51e4e15b0d483ff212dfec136ee1e4f3b49d115bdc7a13/grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee", size = 6446819, upload-time = "2024-09-28T12:38:38.69Z" }, - { url = "https://files.pythonhosted.org/packages/ff/5f/142e19db367a34ea0ee8a8451e43215d0a1a5dbffcfdcae8801f22903301/grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c", size = 6040273, upload-time = "2024-09-28T12:38:41.348Z" }, - { url = "https://files.pythonhosted.org/packages/5c/3b/12fcd752c55002e4b0e0a7bd5faec101bc0a4e3890be3f95a43353142481/grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453", size = 3537988, upload-time = "2024-09-28T12:38:44.544Z" }, - { url = "https://files.pythonhosted.org/packages/f1/70/76bfea3faa862bfceccba255792e780691ff25b8227180759c9d38769379/grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679", size = 4275553, upload-time = "2024-09-28T12:38:47.734Z" }, { url = "https://files.pythonhosted.org/packages/72/31/8708a8dfb3f1ac89926c27c5dd17412764157a2959dbc5a606eaf8ac71f6/grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d", size = 5004245, upload-time = "2024-09-28T12:38:50.596Z" }, { url = "https://files.pythonhosted.org/packages/8b/37/0b57c3769efb3cc9ec97fcaa9f7243046660e7ed58c0faebc4ef315df92c/grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34", size = 10756749, upload-time = "2024-09-28T12:38:54.131Z" }, { url = "https://files.pythonhosted.org/packages/bf/5a/425e995724a19a1b110340ed653bc7c5de8019d9fc84b3798a0f79c3eb31/grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed", size = 5499666, upload-time = "2024-09-28T12:38:57.145Z" }, @@ -199,16 +186,6 @@ version = "3.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, @@ -278,7 +255,7 @@ wheels = [ [[package]] name = "pulumi" -version = "3.169.0" +version = "3.170.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debugpy" }, @@ -290,21 +267,21 @@ dependencies = [ { name = "semver" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/f7/fdf271f5c576f380745c20c47812e98956f0871d3569125c93476ab44418/pulumi-3.169.0-py3-none-any.whl", hash = "sha256:8288d5baf63d280793d118c2df5708ed24327e59f20ecf3c86fc0900da2e8c38", size = 337496, upload-time = "2025-05-08T11:53:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/44/f0/b23f19dae871bb3d43bf3f29b11535d536d2ef9a4b7703e4d2467afc6cf9/pulumi-3.170.0-py3-none-any.whl", hash = "sha256:824b3c293c659be0cd6b975a719d921987a359f4c97639fc7ecd24a83e8cfd4d", size = 338393, upload-time = "2025-05-15T15:00:49.237Z" }, ] [[package]] name = "pulumi-gcp" -version = "8.30.1" +version = "8.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parver" }, { name = "pulumi" }, { name = "semver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/37/5e45019b81317c98ebba738104777edc77a698708292213315af6bf8098d/pulumi_gcp-8.30.1.tar.gz", hash = "sha256:e32f21d47d35439140a47a467cbabee66790d57d8c7f900e51ee612129168035", size = 7586280, upload-time = "2025-05-13T12:51:37.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/ed/70faa2aeb61ae4cd460100303253577df08aad376fc66684248d8bd08643/pulumi_gcp-8.31.0.tar.gz", hash = "sha256:c32791fb029591217d95460b33368648b12879058830a0611b9b4f102a40ff8d", size = 7639978, upload-time = "2025-05-15T14:48:23.677Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/fd/395e816595575576bf4dd29c44d03ce5402aebdb56ac6f0813f55b76e1df/pulumi_gcp-8.30.1-py3-none-any.whl", hash = "sha256:71687a8f16c0e05846d699956c7da79ed5b633afab3a65671de0163167a6c948", size = 9603797, upload-time = "2025-05-13T12:51:33.698Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/1d122e3b6fc6a533e07480ae7ab24bd3bfb9489b9d121bd03bbfc95bb1ea/pulumi_gcp-8.31.0-py3-none-any.whl", hash = "sha256:10e72436757491d7e00ca3ea58541cf67b5c502ba6ec30481b0244481385ab43", size = 9658676, upload-time = "2025-05-15T14:48:20.098Z" }, ] [[package]] @@ -313,15 +290,6 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, @@ -347,7 +315,6 @@ name = "serverless" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "flask" }, { name = "functions-framework" }, { name = "pulumi" }, { name = "pulumi-gcp" }, @@ -355,10 +322,9 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "flask", specifier = ">=3.1.1" }, - { name = "functions-framework", specifier = ">=3.8.2" }, - { name = "pulumi", specifier = ">=3.169.0" }, - { name = "pulumi-gcp", specifier = ">=8.30.1" }, + { name = "functions-framework", specifier = ">=3.8.3" }, + { name = "pulumi", specifier = ">=3.170.0" }, + { name = "pulumi-gcp", specifier = ">=8.31.0" }, ] [[package]] @@ -367,9 +333,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, From cabfff44ec50a0068b8438e1a2cee5f5c7ff00f6 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 20 May 2025 16:28:25 +0200 Subject: [PATCH 16/44] Updated example. --- 2025/mcp-server/mcp_videos.py | 8 +++++--- 2025/mcp-server/mcp_videos_api.py | 20 +++++++++++--------- 2025/mcp-server/video_api.py | 4 ++-- 2025/mcp-server/yt_helper.py | 5 +++++ 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/2025/mcp-server/mcp_videos.py b/2025/mcp-server/mcp_videos.py index 3a3c176..edcb289 100644 --- a/2025/mcp-server/mcp_videos.py +++ b/2025/mcp-server/mcp_videos.py @@ -1,11 +1,13 @@ +from typing import Any + from mcp.server.fastmcp import FastMCP -from yt_helper import search_youtube +from yt_helper import construct_video_url, search_youtube # Initialize FastMCP server mcp = FastMCP("videos") -def format_video(video: dict[str, str]) -> str: +def format_video(video: dict[str, Any]) -> str: """Format a video feature into a readable string.""" return f""" Title: {video.get("title", "Unknown")} @@ -13,7 +15,7 @@ def format_video(video: dict[str, str]) -> str: Duration: {video.get("duration", "Unknown")} Description: {video.get("description", "No description available")} Views: {video.get("views", "Unknown")} - URL: https://www.youtube.com/watch?v={video.get("id", "Unknown")} + URL: {construct_video_url(https://github.com/video.get(%22id%22,%20%22dQw4w9WgXcQ"))} Published: {video.get("publish_time", "Unknown")} """ diff --git a/2025/mcp-server/mcp_videos_api.py b/2025/mcp-server/mcp_videos_api.py index 9fa3542..9df3bf2 100644 --- a/2025/mcp-server/mcp_videos_api.py +++ b/2025/mcp-server/mcp_videos_api.py @@ -1,3 +1,5 @@ +from typing import Any + import httpx from mcp.server.fastmcp import FastMCP @@ -7,17 +9,17 @@ API_URL = "http://localhost:8000/videos" # Adjust if hosted elsewhere -def format_video(video: dict[str, str]) -> str: +def format_video(video: dict[str, Any]) -> str: """Format a video feature into a readable string.""" return f""" -Title: {video.get("title", "Unknown")} -Channel: {video.get("channel", "Unknown")} -Duration: {video.get("duration", "Unknown")} -Description: {video.get("description", "No description available")} -Views: {video.get("views", "Unknown")} -URL: {video.get("url", "Unknown")} -Published: {video.get("publish_time", "Unknown")} -""".strip() + Title: {video.get("title", "Unknown")} + Channel: {video.get("channel", "Unknown")} + Duration: {video.get("duration", "Unknown")} + Description: {video.get("description", "No description available")} + Views: {video.get("views", "Unknown")} + URL: {video.get("url", "Unknown")} + Published: {video.get("publish_time", "Unknown")} + """ @mcp.tool() diff --git a/2025/mcp-server/video_api.py b/2025/mcp-server/video_api.py index 54bcdca..a1893dd 100644 --- a/2025/mcp-server/video_api.py +++ b/2025/mcp-server/video_api.py @@ -3,7 +3,7 @@ import uvicorn from fastapi import FastAPI, Query from pydantic import BaseModel -from yt_helper import search_youtube +from yt_helper import construct_video_url, search_youtube app = FastAPI(title="YouTube Video Search API") @@ -35,7 +35,7 @@ async def get_videos( duration=video.get("duration", "Unknown"), description=video.get("description", None), views=video.get("views", None), - url=f"https://www.youtube.com/watch?v={video.get('id', '')}", + url=construct_video_url(https://github.com/video.get(%22id%22,%20%22dQw4w9WgXcQ")), publish_time=video.get("publish_time", None), ) for video in results diff --git a/2025/mcp-server/yt_helper.py b/2025/mcp-server/yt_helper.py index 6f54cfd..ef1f91b 100644 --- a/2025/mcp-server/yt_helper.py +++ b/2025/mcp-server/yt_helper.py @@ -7,3 +7,8 @@ def search_youtube(query: str, max_results: int = 10) -> list[dict[str, Any]]: """Search YouTube for a given query and return the results.""" results = YoutubeSearch(query, max_results=max_results).to_dict() return results + + +def construct_video_url(https://github.com/video_id:%20str) -> str: + """Construct a YouTube video URL from the video ID.""" + return f"https://www.youtube.com/watch?v={video_id}" From 5ffb60d401c9b7d381e08e460265898f23d086de Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 23 May 2025 13:21:13 +0200 Subject: [PATCH 17/44] Added dataclasses examples. --- 2025/dataclasses/books.db | Bin 0 -> 12288 bytes 2025/dataclasses/fastapi_example.py | 44 ++++ 2025/dataclasses/fastapi_model_example.py | 70 ++++++ 2025/dataclasses/fastapi_model_example_v2.py | 74 ++++++ 2025/dataclasses/fastapi_sqlalchemy.py | 105 ++++++++ 2025/dataclasses/pydantic_dc.py | 45 ++++ 2025/dataclasses/pyproject.toml | 10 + 2025/dataclasses/uv.lock | 241 +++++++++++++++++++ 8 files changed, 589 insertions(+) create mode 100644 2025/dataclasses/books.db create mode 100644 2025/dataclasses/fastapi_example.py create mode 100644 2025/dataclasses/fastapi_model_example.py create mode 100644 2025/dataclasses/fastapi_model_example_v2.py create mode 100644 2025/dataclasses/fastapi_sqlalchemy.py create mode 100644 2025/dataclasses/pydantic_dc.py create mode 100644 2025/dataclasses/pyproject.toml create mode 100644 2025/dataclasses/uv.lock diff --git a/2025/dataclasses/books.db b/2025/dataclasses/books.db new file mode 100644 index 0000000000000000000000000000000000000000..c8cde0fe64b4138036ce2e2d414ab656f20fe7c5 GIT binary patch literal 12288 zcmeI$ze~eF6bJCT)L#vhxJU+TpYrQg?03v&pkg9GlW5DyZ4Or)$ftH!YXi9go^qjx)RXO`QgQ@cxrz&BLGP}cOC&RKBpi-T$eXJCF0OqsIS>$l00bZa0SG_<0uX=z1Rwwb P2rRL{cBYp7>zBX}A(maa literal 0 HcmV?d00001 diff --git a/2025/dataclasses/fastapi_example.py b/2025/dataclasses/fastapi_example.py new file mode 100644 index 0000000..91e33b6 --- /dev/null +++ b/2025/dataclasses/fastapi_example.py @@ -0,0 +1,44 @@ +import secrets + +import uvicorn +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +app = FastAPI() + + +class Book(BaseModel): + title: str + author: str + pages: int = 0 + + +books_db: dict[str, Book] = {} + + +def generate_id() -> str: + # Generate a 24-character hex string (like MongoDB ObjectID) + return secrets.token_hex(12) + + +@app.post("/books/") +def create_book(book: Book): + book_id = generate_id() + books_db[book_id] = book + return {"id": book_id, "book": book} + + +@app.get("/books/{book_id}") +def get_book(book_id: str): + if book_id not in books_db: + raise HTTPException(status_code=404, detail="Book not found") + return {"id": book_id, "book": books_db[book_id]} + + +@app.get("/books/") +def list_books(): + return [{"id": book_id, "book": book} for book_id, book in books_db.items()] + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/2025/dataclasses/fastapi_model_example.py b/2025/dataclasses/fastapi_model_example.py new file mode 100644 index 0000000..a107266 --- /dev/null +++ b/2025/dataclasses/fastapi_model_example.py @@ -0,0 +1,70 @@ +import secrets +from dataclasses import dataclass, field + +import uvicorn +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +app = FastAPI() + +# --- DOMAIN MODEL --- + + +def generate_id() -> str: + return secrets.token_hex(12) + + +@dataclass +class Book: + title: str + author: str + pages: int = 0 + id: str = field(default_factory=generate_id) + + +# --- Pydantic MODELS for API --- + + +class BookCreate(BaseModel): + title: str + author: str + pages: int = 0 + + +class BookResponse(BaseModel): + id: str + title: str + author: str + pages: int + + +# --- FAKE DB --- + +books_db: dict[str, Book] = {} + + +# --- ROUTES --- + + +@app.post("/books/", response_model=BookResponse) +def create_book(book_data: BookCreate): + book = Book(**book_data.dict()) # ID is generated automatically + books_db[book.id] = book + return BookResponse(**book.__dict__) + + +@app.get("/books/{book_id}", response_model=BookResponse) +def get_book(book_id: str): + book = books_db.get(book_id) + if not book: + raise HTTPException(status_code=404, detail="Book not found") + return BookResponse(**book.__dict__) + + +@app.get("/books/", response_model=list[BookResponse]) +def list_books(): + return [BookResponse(**book.__dict__) for book in books_db.values()] + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/2025/dataclasses/fastapi_model_example_v2.py b/2025/dataclasses/fastapi_model_example_v2.py new file mode 100644 index 0000000..a029e62 --- /dev/null +++ b/2025/dataclasses/fastapi_model_example_v2.py @@ -0,0 +1,74 @@ +import secrets + +import uvicorn +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel, dataclasses + +app = FastAPI() + +# --- DOMAIN MODEL (Pydantic Dataclass) --- + + +def generate_id() -> str: + return secrets.token_hex(12) + + +@dataclasses.dataclass +class Book: + title: str + author: str + pages: int = 0 + id: str = dataclasses.Field(default_factory=generate_id) + + +# --- API MODELS (Pydantic BaseModels) --- + + +class BookCreate(BaseModel): + title: str + author: str + pages: int = 0 + + class Config: + from_attributes = True + + +class BookResponse(BaseModel): + id: str + title: str + author: str + pages: int + + class Config: + from_attributes = True + + +# --- FAKE IN-MEMORY DB --- + +books_db: dict[str, Book] = {} + +# --- ROUTES --- + + +@app.post("/books/", response_model=BookResponse) +def create_book(book_data: BookCreate): + book = Book(**book_data.model_dump()) + books_db[book.id] = book + return BookResponse.model_validate(book) + + +@app.get("/books/{book_id}", response_model=BookResponse) +def get_book(book_id: str): + book = books_db.get(book_id) + if not book: + raise HTTPException(status_code=404, detail="Book not found") + return BookResponse.model_validate(book) + + +@app.get("/books/", response_model=list[BookResponse]) +def list_books(): + return [BookResponse.model_validate(book) for book in books_db.values()] + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/2025/dataclasses/fastapi_sqlalchemy.py b/2025/dataclasses/fastapi_sqlalchemy.py new file mode 100644 index 0000000..0f00419 --- /dev/null +++ b/2025/dataclasses/fastapi_sqlalchemy.py @@ -0,0 +1,105 @@ +import secrets + +import uvicorn +from fastapi import Depends, FastAPI, HTTPException +from pydantic import BaseModel +from sqlalchemy import Integer, String, create_engine +from sqlalchemy.orm import ( + DeclarativeBase, + Mapped, + Session, + mapped_column, + sessionmaker, +) + +# --- Database setup --- + +DATABASE_URL = "sqlite:///./books.db" +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) + +SessionLocal = sessionmaker(bind=engine, autoflush=False) + + +# declarative base class +class Base(DeclarativeBase): + pass + + +# --- SQLAlchemy ORM model --- + + +class Book(Base): + __tablename__ = "books" + + id: Mapped[str] = mapped_column( + primary_key=True, default=lambda: secrets.token_hex(12) + ) + title: Mapped[str] = mapped_column(String, nullable=False) + author: Mapped[str] = mapped_column(String, nullable=False) + pages: Mapped[int] = mapped_column(Integer, default=0) + + +# --- Pydantic models --- + + +class BookCreate(BaseModel): + title: str + author: str + pages: int = 0 + + +class BookResponse(BaseModel): + id: str + title: str + author: str + pages: int + + class Config: + from_attributes = True # allows conversion from ORM objects + + +# --- Dependency to get a DB session --- + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +# --- FastAPI setup --- + +app = FastAPI() + +# Create tables +Base.metadata.create_all(bind=engine) + +# --- Routes --- + + +@app.post("/books/", response_model=BookResponse) +def create_book(book_data: BookCreate, db: Session = Depends(get_db)): + book = Book(**book_data.model_dump()) + db.add(book) + db.commit() + db.refresh(book) + return book + + +@app.get("/books/{book_id}", response_model=BookResponse) +def get_book(book_id: str, db: Session = Depends(get_db)): + book = db.get(Book, book_id) + if not book: + raise HTTPException(status_code=404, detail="Book not found") + return book + + +@app.get("/books/", response_model=list[BookResponse]) +def list_books(db: Session = Depends(get_db)): + return db.query(Book).all() + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/2025/dataclasses/pydantic_dc.py b/2025/dataclasses/pydantic_dc.py new file mode 100644 index 0000000..a12bfc1 --- /dev/null +++ b/2025/dataclasses/pydantic_dc.py @@ -0,0 +1,45 @@ +from pydantic import BaseModel, ValidationError, validator +from pydantic.dataclasses import dataclass + + +@dataclass +class Book: + title: str + pages: int + + @validator("pages") + def pages_must_be_positive(cls, v: int): + if v < 0: + raise ValueError("Pages must be a positive integer") + return v + + +class Author(BaseModel): + name: str + age: int + + @validator("age") + def age_must_be_positive(cls, v: int): + if v < 0: + raise ValueError("Age must be a positive integer") + return v + + +def main() -> None: + # Valid input + book = Book(title="1984", pages=328) # pages will be converted to int + + # print(book.model_dump()) # {'title': '1984', 'pages': 328} + + # Invalid input – will raise a ValidationError + try: + bad_book = Book(title="The Hobbit", pages="three hundred") + except ValidationError as e: + print(e) + + author = Author(name="J.R.R. Tolkien", age=81) + print(author.model_dump()) # {'name': 'J.R.R. Tolkien', 'age': 81} + + +if __name__ == "__main__": + main() diff --git a/2025/dataclasses/pyproject.toml b/2025/dataclasses/pyproject.toml new file mode 100644 index 0000000..4266fb1 --- /dev/null +++ b/2025/dataclasses/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "dataclasses_vs_pydantic" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "fastapi>=0.115.12", + "pydantic>=2.11.4", + "sqlalchemy>=2.0.41", + "uvicorn>=0.34.2", +] diff --git a/2025/dataclasses/uv.lock b/2025/dataclasses/uv.lock new file mode 100644 index 0000000..bf324b5 --- /dev/null +++ b/2025/dataclasses/uv.lock @@ -0,0 +1,241 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dataclasses-vs-pydantic" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi" }, + { name = "pydantic" }, + { name = "sqlalchemy" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.115.12" }, + { name = "pydantic", specifier = ">=2.11.4" }, + { name = "sqlalchemy", specifier = ">=2.0.41" }, + { name = "uvicorn", specifier = ">=0.34.2" }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/c1/a82edae11d46c0d83481aacaa1e578fea21d94a1ef400afd734d47ad95ad/greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485", size = 185797, upload-time = "2025-05-09T19:47:35.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/30/97b49779fff8601af20972a62cc4af0c497c1504dfbb3e93be218e093f21/greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59", size = 269150, upload-time = "2025-05-09T14:50:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/21/30/877245def4220f684bc2e01df1c2e782c164e84b32e07373992f14a2d107/greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf", size = 637381, upload-time = "2025-05-09T15:24:12.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/16/adf937908e1f913856b5371c1d8bdaef5f58f251d714085abeea73ecc471/greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325", size = 651427, upload-time = "2025-05-09T15:24:51.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/49/6d79f58fa695b618654adac64e56aff2eeb13344dc28259af8f505662bb1/greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5", size = 645795, upload-time = "2025-05-09T15:29:26.673Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e6/28ed5cb929c6b2f001e96b1d0698c622976cd8f1e41fe7ebc047fa7c6dd4/greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825", size = 648398, upload-time = "2025-05-09T14:53:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/b200194e25ae86bc57077f695b6cc47ee3118becf54130c5514456cf8dac/greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d", size = 606795, upload-time = "2025-05-09T14:53:47.039Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c8/ba1def67513a941154ed8f9477ae6e5a03f645be6b507d3930f72ed508d3/greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf", size = 1117976, upload-time = "2025-05-09T15:27:06.542Z" }, + { url = "https://files.pythonhosted.org/packages/c3/30/d0e88c1cfcc1b3331d63c2b54a0a3a4a950ef202fb8b92e772ca714a9221/greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708", size = 1145509, upload-time = "2025-05-09T14:54:02.223Z" }, + { url = "https://files.pythonhosted.org/packages/90/2e/59d6491834b6e289051b252cf4776d16da51c7c6ca6a87ff97e3a50aa0cd/greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421", size = 296023, upload-time = "2025-05-09T14:53:24.157Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/8a73aace5a5335a1cba56d0da71b7bd93e450f17d372c5b7c5fa547557e9/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418", size = 629911, upload-time = "2025-05-09T15:24:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/48/08/c8b8ebac4e0c95dcc68ec99198842e7db53eda4ab3fb0a4e785690883991/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4", size = 635251, upload-time = "2025-05-09T15:24:52.205Z" }, + { url = "https://files.pythonhosted.org/packages/37/26/7db30868f73e86b9125264d2959acabea132b444b88185ba5c462cb8e571/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763", size = 632620, upload-time = "2025-05-09T15:29:28.051Z" }, + { url = "https://files.pythonhosted.org/packages/10/ec/718a3bd56249e729016b0b69bee4adea0dfccf6ca43d147ef3b21edbca16/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b", size = 628851, upload-time = "2025-05-09T14:53:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1c79286a76bc62ccdc1387291464af16a4204ea717f24e77b0acd623b99/greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207", size = 593718, upload-time = "2025-05-09T14:53:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/cd/41/96ba2bf948f67b245784cd294b84e3d17933597dffd3acdb367a210d1949/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8", size = 1105752, upload-time = "2025-05-09T15:27:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/68/3b/3b97f9d33c1f2eb081759da62bd6162159db260f602f048bc2f36b4c453e/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51", size = 1125170, upload-time = "2025-05-09T14:54:04.082Z" }, + { url = "https://files.pythonhosted.org/packages/31/df/b7d17d66c8d0f578d2885a3d8f565e9e4725eacc9d3fdc946d0031c055c4/greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240", size = 269899, upload-time = "2025-05-09T14:54:01.581Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] From bcf4679afe8cd18eb88b35af46a32c43b0bd65cd Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 23 May 2025 15:07:57 +0200 Subject: [PATCH 18/44] Fixed serverless Pulumi example. --- 2025/serverless/__main__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/2025/serverless/__main__.py b/2025/serverless/__main__.py index d72b9f9..8e821a3 100644 --- a/2025/serverless/__main__.py +++ b/2025/serverless/__main__.py @@ -1,5 +1,5 @@ from pulumi import asset, export -from pulumi_gcp import cloudfunctionsv2, storage +from pulumi_gcp import cloudfunctionsv2, cloudrunv2, storage bucket = storage.Bucket("bucket", location="europe-west1") @@ -10,8 +10,8 @@ ) py_function = cloudfunctionsv2.Function( - "python-func", - name="python-func", + "channels-api", + name="channels-api", location="europe-west1", build_config={ "runtime": "python313", @@ -30,14 +30,13 @@ }, ) - -py_invoker = cloudfunctionsv2.FunctionIamMember( +py_invoker = cloudrunv2.ServiceIamMember( "py-invoker", project=py_function.project, location=py_function.location, - cloud_function=py_function.name, - role="roles/cloudfunctions.invoker", - # role="roles/run.invoker", + name=py_function.name, + # role="roles/cloudfunctions.invoker", + role="roles/run.invoker", member="allUsers", ) From 5650869eb9bb3c4935f1be54070e7be2eb0652c6 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Mon, 26 May 2025 14:54:42 +0200 Subject: [PATCH 19/44] Minor fixes. --- 2025/abstraction/abstraction_abc/filters/grayscale.py | 2 +- .../abstraction/abstraction_callable/filters/grayscale.py | 8 ++++---- 2025/abstraction/abstraction_callable/main.py | 8 ++++---- 2025/abstraction/abstraction_callable/process_img.py | 5 ++--- 2025/abstraction/abstraction_none/filters/grayscale.py | 2 +- .../abstraction/abstraction_protocol/filters/grayscale.py | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/2025/abstraction/abstraction_abc/filters/grayscale.py b/2025/abstraction/abstraction_abc/filters/grayscale.py index 6e7ecdd..01dc662 100644 --- a/2025/abstraction/abstraction_abc/filters/grayscale.py +++ b/2025/abstraction/abstraction_abc/filters/grayscale.py @@ -15,7 +15,7 @@ def name(self) -> str: def apply(self, image: Image.Image) -> Image.Image: print(f"Applying {self.name} filter with intensity {self._intensity}") - grayscale_image = ImageOps.grayscale(image) + grayscale_image = ImageOps.grayscale(image).convert("RGB") if self._intensity < 1.0: return Image.blend(image, grayscale_image, self._intensity) return grayscale_image diff --git a/2025/abstraction/abstraction_callable/filters/grayscale.py b/2025/abstraction/abstraction_callable/filters/grayscale.py index bdc0bf9..361c87e 100644 --- a/2025/abstraction/abstraction_callable/filters/grayscale.py +++ b/2025/abstraction/abstraction_callable/filters/grayscale.py @@ -3,7 +3,7 @@ def apply_grayscale(image: Image.Image, intensity: float) -> Image.Image: print(f"Applying grayscale filter with intensity {intensity}") - grayscale_image = ImageOps.grayscale(image) - if intensity < 1.0: - return Image.blend(image, grayscale_image, intensity) - return grayscale_image + grayscale_image = ImageOps.grayscale(image).convert("RGB") + if intensity < 1.0: + return Image.blend(image, grayscale_image, intensity) + return grayscale_image diff --git a/2025/abstraction/abstraction_callable/main.py b/2025/abstraction/abstraction_callable/main.py index b0b92af..d90dbee 100644 --- a/2025/abstraction/abstraction_callable/main.py +++ b/2025/abstraction/abstraction_callable/main.py @@ -3,10 +3,10 @@ from filters.grayscale import apply_grayscale from filters.invert import apply_invert from PIL import Image, ImageOps -from process_img import process_img +from process_img import ImageFilterFn, process_img -def make_grayscale_filter(intensity: float = 1.0) -> ImageFilterFunc: +def make_grayscale_filter(intensity: float = 1.0) -> ImageFilterFn: def filter_func(image: Image.Image) -> Image.Image: print(f"Applying grayscale filter with intensity {intensity}") # Note: intensity isn't actually used by Pillow here, but it demonstrates config @@ -15,7 +15,7 @@ def filter_func(image: Image.Image) -> Image.Image: return filter_func -def make_invert_filter(enabled: bool = True) -> ImageFilterFunc: +def make_invert_filter(enabled: bool = True) -> ImageFilterFn: def filter_func(image: Image.Image) -> Image.Image: if enabled: print("Applying invert filter") @@ -30,7 +30,7 @@ def filter_func(image: Image.Image) -> Image.Image: def main() -> None: input_image: str = "../input.jpg" - grayscale_fn = partial(apply_grayscale, intensity=0.8) + grayscale_fn = partial(apply_grayscale, intensity=0.6) process_img(input_image, "output_abc_grayscale.jpg", grayscale_fn) process_img(input_image, "output_abc_invert.jpg", apply_invert) diff --git a/2025/abstraction/abstraction_callable/process_img.py b/2025/abstraction/abstraction_callable/process_img.py index 946fd09..6ae7ec6 100644 --- a/2025/abstraction/abstraction_callable/process_img.py +++ b/2025/abstraction/abstraction_callable/process_img.py @@ -2,11 +2,10 @@ from PIL import Image -type ProcessFn = Callable[[image.Image], image.Image] +type ImageFilterFn = Callable[[Image], Image] -def process_img(image_path: str, output_path: str, filter_fn: ProcessFn) -> None: - print(f"\nUsing filter: {filter_fn.__name__}") +def process_img(image_path: str, output_path: str, filter_fn: ImageFilterFn) -> None: image = Image.open(image_path) image = filter_fn(image) image.save(output_path) diff --git a/2025/abstraction/abstraction_none/filters/grayscale.py b/2025/abstraction/abstraction_none/filters/grayscale.py index 50ac420..8946494 100644 --- a/2025/abstraction/abstraction_none/filters/grayscale.py +++ b/2025/abstraction/abstraction_none/filters/grayscale.py @@ -13,7 +13,7 @@ def name(self) -> str: def apply(self, image: Image.Image) -> Image.Image: print(f"Applying {self.name} filter with intensity {self._intensity}") - grayscale_image = ImageOps.grayscale(image) + grayscale_image = ImageOps.grayscale(image).convert("RGB") if self._intensity < 1.0: return Image.blend(image, grayscale_image, self._intensity) return grayscale_image diff --git a/2025/abstraction/abstraction_protocol/filters/grayscale.py b/2025/abstraction/abstraction_protocol/filters/grayscale.py index 50ac420..8946494 100644 --- a/2025/abstraction/abstraction_protocol/filters/grayscale.py +++ b/2025/abstraction/abstraction_protocol/filters/grayscale.py @@ -13,7 +13,7 @@ def name(self) -> str: def apply(self, image: Image.Image) -> Image.Image: print(f"Applying {self.name} filter with intensity {self._intensity}") - grayscale_image = ImageOps.grayscale(image) + grayscale_image = ImageOps.grayscale(image).convert("RGB") if self._intensity < 1.0: return Image.blend(image, grayscale_image, self._intensity) return grayscale_image From bec66bcaee1807a816c2219275cc411376c15ac1 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 27 May 2025 16:48:04 +0200 Subject: [PATCH 20/44] Created SDK design examples. --- 2025/sdk/pyproject.toml | 16 ++ 2025/sdk/sdk_v0_example.py | 13 ++ 2025/sdk/sdk_v1/__init__.py | 0 2025/sdk/sdk_v1/client.py | 25 +++ 2025/sdk/sdk_v1/user.py | 14 ++ 2025/sdk/sdk_v1_example.py | 16 ++ 2025/sdk/sdk_v2/__init__.py | 0 2025/sdk/sdk_v2/client.py | 37 +++++ 2025/sdk/sdk_v2/user.py | 14 ++ 2025/sdk/sdk_v2_example.py | 16 ++ 2025/sdk/sdk_v3/__init__.py | 0 2025/sdk/sdk_v3/base.py | 39 +++++ 2025/sdk/sdk_v3/client.py | 37 +++++ 2025/sdk/sdk_v3/user.py | 12 ++ 2025/sdk/sdk_v3_example.py | 16 ++ 2025/sdk/test_api.py | 89 +++++++++++ 2025/sdk/uv.lock | 309 ++++++++++++++++++++++++++++++++++++ 17 files changed, 653 insertions(+) create mode 100644 2025/sdk/pyproject.toml create mode 100644 2025/sdk/sdk_v0_example.py create mode 100644 2025/sdk/sdk_v1/__init__.py create mode 100644 2025/sdk/sdk_v1/client.py create mode 100644 2025/sdk/sdk_v1/user.py create mode 100644 2025/sdk/sdk_v1_example.py create mode 100644 2025/sdk/sdk_v2/__init__.py create mode 100644 2025/sdk/sdk_v2/client.py create mode 100644 2025/sdk/sdk_v2/user.py create mode 100644 2025/sdk/sdk_v2_example.py create mode 100644 2025/sdk/sdk_v3/__init__.py create mode 100644 2025/sdk/sdk_v3/base.py create mode 100644 2025/sdk/sdk_v3/client.py create mode 100644 2025/sdk/sdk_v3/user.py create mode 100644 2025/sdk/sdk_v3_example.py create mode 100644 2025/sdk/test_api.py create mode 100644 2025/sdk/uv.lock diff --git a/2025/sdk/pyproject.toml b/2025/sdk/pyproject.toml new file mode 100644 index 0000000..1071910 --- /dev/null +++ b/2025/sdk/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "sdk" +version = "0.1.0" +description = "SDK design" +requires-python = ">=3.13" +dependencies = [ + "fastapi>=0.115.12", + "httpx>=0.28.1", + "pydantic[email]>=2.11.5", + "uvicorn>=0.34.2", +] + +[dependency-groups] +dev = [ + "pytest>=8.3.5", +] diff --git a/2025/sdk/sdk_v0_example.py b/2025/sdk/sdk_v0_example.py new file mode 100644 index 0000000..9f7e326 --- /dev/null +++ b/2025/sdk/sdk_v0_example.py @@ -0,0 +1,13 @@ +import httpx + + +def main() -> None: + # Initialize the API client with your API + headers = {"Authorization": "Bearer secret123"} + response = httpx.get("http://localhost:8000/users", headers=headers) + users = response.json() + print(users) + + +if __name__ == "__main__": + main() diff --git a/2025/sdk/sdk_v1/__init__.py b/2025/sdk/sdk_v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/sdk/sdk_v1/client.py b/2025/sdk/sdk_v1/client.py new file mode 100644 index 0000000..0e65ea9 --- /dev/null +++ b/2025/sdk/sdk_v1/client.py @@ -0,0 +1,25 @@ +import httpx + + +class APIHttpClient: + def __init__(self, token: str, base_url: str = "http://localhost:8000/"): + self.client = httpx.Client( + base_url=base_url, headers={"Authorization": f"Bearer {token}"} + ) + + def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response: + response = self.client.request(method, endpoint, **kwargs) + response.raise_for_status() + return response + + def get(self, endpoint: str, params: dict = None) -> httpx.Response: + return self.request("GET", endpoint, params=params) + + def post(self, endpoint: str, json: dict = None) -> httpx.Response: + return self.request("POST", endpoint, json=json) + + def put(self, endpoint: str, json: dict = None) -> httpx.Response: + return self.request("PUT", endpoint, json=json) + + def delete(self, endpoint: str) -> httpx.Response: + return self.request("DELETE", endpoint) diff --git a/2025/sdk/sdk_v1/user.py b/2025/sdk/sdk_v1/user.py new file mode 100644 index 0000000..d309d7a --- /dev/null +++ b/2025/sdk/sdk_v1/user.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel, EmailStr + +from .client import APIHttpClient + + +class User(BaseModel): + id: str + name: str + email: EmailStr + + @classmethod + def find(cls, client: APIHttpClient) -> list["User"]: + response = client.get("/users") + return [cls(**data) for data in response.json()] diff --git a/2025/sdk/sdk_v1_example.py b/2025/sdk/sdk_v1_example.py new file mode 100644 index 0000000..8a55dfe --- /dev/null +++ b/2025/sdk/sdk_v1_example.py @@ -0,0 +1,16 @@ +from sdk_v1.client import APIHttpClient +from sdk_v1.user import User + + +def main(): + # Initialize the API client with your API + client = APIHttpClient(token="secret123") + + # Fetch users from the API + users = User.find(client) + for user in users: + print(user) + + +if __name__ == "__main__": + main() diff --git a/2025/sdk/sdk_v2/__init__.py b/2025/sdk/sdk_v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/sdk/sdk_v2/client.py b/2025/sdk/sdk_v2/client.py new file mode 100644 index 0000000..35f7cb8 --- /dev/null +++ b/2025/sdk/sdk_v2/client.py @@ -0,0 +1,37 @@ +import httpx + +_client = None +_token = None +_base_url = "http://localhost:8000/" + + +def set_credentials(token: str): + global _client, _token, _base_url + _token = token + _client = httpx.Client( + base_url=_base_url, headers={"Authorization": f"Bearer {_token}"} + ) + + +def request(method: str, endpoint: str, **kwargs) -> httpx.Response: + if _client is None: + raise RuntimeError("Credentials not set. Call set_credentials() first.") + response = _client.request(method, endpoint, **kwargs) + response.raise_for_status() + return response + + +def get(endpoint: str, params: dict = None) -> httpx.Response: + return request("GET", endpoint, params=params) + + +def post(endpoint: str, json: dict = None) -> httpx.Response: + return request("POST", endpoint, json=json) + + +def put(endpoint: str, json: dict = None) -> httpx.Response: + return request("PUT", endpoint, json=json) + + +def delete(endpoint: str) -> httpx.Response: + return request("DELETE", endpoint) diff --git a/2025/sdk/sdk_v2/user.py b/2025/sdk/sdk_v2/user.py new file mode 100644 index 0000000..d37cf07 --- /dev/null +++ b/2025/sdk/sdk_v2/user.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel, EmailStr + +from sdk_v2 import client + + +class User(BaseModel): + id: str + name: str + email: EmailStr + + @classmethod + def find(cls) -> list["User"]: + response = client.get("/users") + return [cls(**data) for data in response.json()] diff --git a/2025/sdk/sdk_v2_example.py b/2025/sdk/sdk_v2_example.py new file mode 100644 index 0000000..27ee9f4 --- /dev/null +++ b/2025/sdk/sdk_v2_example.py @@ -0,0 +1,16 @@ +from sdk_v2.client import set_credentials +from sdk_v2.user import User + + +def main(): + # Initialize the API client with your API + set_credentials(token="secret123") + + # Fetch users from the API + users = User.find() + for user in users: + print(user) + + +if __name__ == "__main__": + main() diff --git a/2025/sdk/sdk_v3/__init__.py b/2025/sdk/sdk_v3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/sdk/sdk_v3/base.py b/2025/sdk/sdk_v3/base.py new file mode 100644 index 0000000..38313ff --- /dev/null +++ b/2025/sdk/sdk_v3/base.py @@ -0,0 +1,39 @@ +from typing import ClassVar, Type + +from pydantic import BaseModel + +from sdk_v3 import client + + +class BaseAPIModel[T](BaseModel): + id: str | None = None + _resource_path: ClassVar[str] = "" + + def save(self) -> None: + data = self.dict(exclude_unset=True) + if self.id: + response = client.put(f"/{self._resource_path}/{self.id}", json=data) + else: + response = client.post(f"/{self._resource_path}", json=data) + response.raise_for_status() + self.id = response.json()["id"] + + def delete(self) -> None: + if not self.id: + raise ValueError("Cannot delete unsaved resource.") + response = client.delete(f"/{self._resource_path}/{self.id}") + response.raise_for_status() + + @classmethod + def load(cls: Type[T], resource_id: str) -> T: + response = client.get(f"/{cls._resource_path}/{resource_id}") + if response.status_code == 404: + raise ValueError(f"{cls.__name__} not found.") + response.raise_for_status() + return cls(**response.json()) + + @classmethod + def find(cls: Type[T]) -> list[T]: + response = client.get(f"/{cls._resource_path}") + response.raise_for_status() + return [cls(**item) for item in response.json()] diff --git a/2025/sdk/sdk_v3/client.py b/2025/sdk/sdk_v3/client.py new file mode 100644 index 0000000..35f7cb8 --- /dev/null +++ b/2025/sdk/sdk_v3/client.py @@ -0,0 +1,37 @@ +import httpx + +_client = None +_token = None +_base_url = "http://localhost:8000/" + + +def set_credentials(token: str): + global _client, _token, _base_url + _token = token + _client = httpx.Client( + base_url=_base_url, headers={"Authorization": f"Bearer {_token}"} + ) + + +def request(method: str, endpoint: str, **kwargs) -> httpx.Response: + if _client is None: + raise RuntimeError("Credentials not set. Call set_credentials() first.") + response = _client.request(method, endpoint, **kwargs) + response.raise_for_status() + return response + + +def get(endpoint: str, params: dict = None) -> httpx.Response: + return request("GET", endpoint, params=params) + + +def post(endpoint: str, json: dict = None) -> httpx.Response: + return request("POST", endpoint, json=json) + + +def put(endpoint: str, json: dict = None) -> httpx.Response: + return request("PUT", endpoint, json=json) + + +def delete(endpoint: str) -> httpx.Response: + return request("DELETE", endpoint) diff --git a/2025/sdk/sdk_v3/user.py b/2025/sdk/sdk_v3/user.py new file mode 100644 index 0000000..75923f2 --- /dev/null +++ b/2025/sdk/sdk_v3/user.py @@ -0,0 +1,12 @@ +from typing import ClassVar + +from pydantic import EmailStr + +from .base import BaseAPIModel + + +class User(BaseAPIModel["User"]): + name: str + email: EmailStr + + _resource_path: ClassVar[str] = "users" diff --git a/2025/sdk/sdk_v3_example.py b/2025/sdk/sdk_v3_example.py new file mode 100644 index 0000000..908f048 --- /dev/null +++ b/2025/sdk/sdk_v3_example.py @@ -0,0 +1,16 @@ +from sdk_v3.client import set_credentials +from sdk_v3.user import User + + +def main(): + # Initialize the API client with your API + set_credentials(token="secret123") + + # Fetch users from the API + users = User.find() + for user in users: + print(user) + + +if __name__ == "__main__": + main() diff --git a/2025/sdk/test_api.py b/2025/sdk/test_api.py new file mode 100644 index 0000000..e3b8fc5 --- /dev/null +++ b/2025/sdk/test_api.py @@ -0,0 +1,89 @@ +from typing import Optional +from uuid import uuid4 + +import uvicorn +from fastapi import Depends, FastAPI, Header, HTTPException, status +from pydantic import BaseModel, EmailStr + +app = FastAPI() + +# Simulated database +fake_users_db = {} + +# API key setup +API_KEY = "secret123" # You can change this to anything you like + + +def get_api_key(authorization: Optional[str] = Header(None)): + if not authorization or not authorization.startswith("Bearer "): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or missing Authorization header", + ) + + token = authorization.split(" ")[1] + if token != API_KEY: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Invalid API key" + ) + + return token + + +# Pydantic models +class UserCreate(BaseModel): + name: str + email: EmailStr + + +class User(UserCreate): + id: str + + +# CRUD Endpoints +@app.post("/users", response_model=User) +def create_user(user: UserCreate, api_key: str = Depends(get_api_key)): + user_id = str(uuid4()) + new_user = User(id=user_id, **user.dict()) + fake_users_db[user_id] = new_user + return new_user + + +@app.get("/users", response_model=list[User]) +def get_users(api_key: str = Depends(get_api_key)): + return list(fake_users_db.values()) + + +@app.get("/users/{user_id}", response_model=User) +def get_user(user_id: str, api_key: str = Depends(get_api_key)): + user = fake_users_db.get(user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + return user + + +@app.put("/users/{user_id}", response_model=User) +def update_user( + user_id: str, user_update: UserCreate, api_key: str = Depends(get_api_key) +): + if user_id not in fake_users_db: + raise HTTPException(status_code=404, detail="User not found") + updated_user = User(id=user_id, **user_update.dict()) + fake_users_db[user_id] = updated_user + return updated_user + + +@app.delete("/users/{user_id}") +def delete_user(user_id: str, api_key: str = Depends(get_api_key)): + if user_id not in fake_users_db: + raise HTTPException(status_code=404, detail="User not found") + del fake_users_db[user_id] + return {"detail": "User deleted"} + + +def main(): + uvicorn.run("test_api:app", host="0.0.0.0", port=8000, reload=True) + + +if __name__ == "__main__": + main() diff --git a/2025/sdk/uv.lock b/2025/sdk/uv.lock new file mode 100644 index 0000000..3053da9 --- /dev/null +++ b/2025/sdk/uv.lock @@ -0,0 +1,309 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "sdk" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi" }, + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "uvicorn" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.115.12" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "pydantic", extras = ["email"], specifier = ">=2.11.5" }, + { name = "uvicorn", specifier = ">=0.34.2" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.3.5" }] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] From 722b42ed77a952d9f088fe48cd17e1ad903cf9d1 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 27 May 2025 17:31:10 +0200 Subject: [PATCH 21/44] Minor update to sdk example --- 2025/sdk/sdk_v3_example.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/2025/sdk/sdk_v3_example.py b/2025/sdk/sdk_v3_example.py index 908f048..ae99196 100644 --- a/2025/sdk/sdk_v3_example.py +++ b/2025/sdk/sdk_v3_example.py @@ -6,10 +6,17 @@ def main(): # Initialize the API client with your API set_credentials(token="secret123") - # Fetch users from the API + # Create and save + u = User(name="Alice", email="alice@example.com") + u.save() + + # Change and save + u.name = "Alice Smith" + u.save() + + # Find all users users = User.find() - for user in users: - print(user) + print(users) if __name__ == "__main__": From 3fc72051f3fb63d2f5d1a7a0edc69db90d9f1227 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 17 Jun 2025 13:31:25 +0200 Subject: [PATCH 22/44] Builder pattern example. --- 2025/builder/htmlbuilder.py | 45 +++++++++++++++++++++++++++++++++++++ 2025/builder/main.py | 28 +++++++++++++++++++++++ 2025/builder/pyproject.toml | 6 +++++ 2025/builder/uv.lock | 8 +++++++ 2025/builder/viewer.py | 32 ++++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 2025/builder/htmlbuilder.py create mode 100644 2025/builder/main.py create mode 100644 2025/builder/pyproject.toml create mode 100644 2025/builder/uv.lock create mode 100644 2025/builder/viewer.py diff --git a/2025/builder/htmlbuilder.py b/2025/builder/htmlbuilder.py new file mode 100644 index 0000000..2e20f00 --- /dev/null +++ b/2025/builder/htmlbuilder.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from typing import Self + + +@dataclass +class HTMLPage: + title: str + body_elements: list[str] + + def render(self) -> str: + body = "\n".join(self.body_elements) + return f""" + + {self.title} + + {body} + + """ + + +class HTMLBuilder: + def __init__(self) -> None: + self._title: str = "Untitled" + self._body: list[str] = [] + + def set_title(self, title: str) -> Self: + self._title = title + return self + + def add_header(self, text: str, level: int = 1) -> Self: + self._body.append(f"{text}") + return self + + def add_paragraph(self, text: str) -> Self: + self._body.append(f"

{text}

") + return self + + def add_button(self, label: str, onclick: str = "#") -> Self: + self._body.append( + f"" + ) + return self + + def build(self) -> HTMLPage: + return HTMLPage(self._title, self._body) diff --git a/2025/builder/main.py b/2025/builder/main.py new file mode 100644 index 0000000..89e85b9 --- /dev/null +++ b/2025/builder/main.py @@ -0,0 +1,28 @@ +from htmlbuilder import HTMLBuilder +from viewer import HTMLViewer + + +def main() -> None: + # --- Build UI Page --- + builder = HTMLBuilder() + page = ( + builder.set_title("Builder Pattern UI") + .add_header("Hello from Python!", level=1) + .add_paragraph("This page was generated using the Builder Pattern.") + .add_button("Visit ArjanCodes", onclick="https://arjan.codes/") + .build() + ) + + file_path = "page.html" + with open(file_path, "w") as f: + f.write(page.render()) + + print("HTML page written to 'page.html'") + + # --- Start Viewer --- + viewer = HTMLViewer(filename=file_path) + viewer.start() + + +if __name__ == "__main__": + main() diff --git a/2025/builder/pyproject.toml b/2025/builder/pyproject.toml new file mode 100644 index 0000000..d0f71cd --- /dev/null +++ b/2025/builder/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "builder" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ +] diff --git a/2025/builder/uv.lock b/2025/builder/uv.lock new file mode 100644 index 0000000..e7762de --- /dev/null +++ b/2025/builder/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "builder" +version = "0.1.0" +source = { virtual = "." } diff --git a/2025/builder/viewer.py b/2025/builder/viewer.py new file mode 100644 index 0000000..45ba5c9 --- /dev/null +++ b/2025/builder/viewer.py @@ -0,0 +1,32 @@ +import http.server +import os +import socketserver +import webbrowser + + +class HTMLViewer: + def __init__(self, filename: str, port: int = 8000) -> None: + self.filename = filename + self.port = port + self._server: socketserver.TCPServer | None = None + + def _open_browser(self) -> None: + url = f"http://localhost:{self.port}/{self.filename}" + webbrowser.open(url) + + def _serve(self) -> None: + handler = http.server.SimpleHTTPRequestHandler + with socketserver.TCPServer(("", self.port), handler) as httpd: + self._server = httpd + self._open_browser() + print(f"Serving '{self.filename}' at http://localhost:{self.port}") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nKeyboard interrupt received. Shutting down server...") + httpd.shutdown() + httpd.server_close() + + def start(self) -> None: + os.chdir(os.path.dirname(os.path.abspath(self.filename))) + self._serve() From 9b8ff80698e70a5ade04929e41e1142a223956e6 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 18 Jun 2025 09:54:57 +0200 Subject: [PATCH 23/44] Updated website. Made dataclass immutable. Added metadata to builder and page. --- 2025/builder/htmlbuilder.py | 19 ++++++++++++++++--- 2025/builder/main.py | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/2025/builder/htmlbuilder.py b/2025/builder/htmlbuilder.py index 2e20f00..ad3d64f 100644 --- a/2025/builder/htmlbuilder.py +++ b/2025/builder/htmlbuilder.py @@ -2,16 +2,24 @@ from typing import Self -@dataclass +@dataclass(frozen=True) class HTMLPage: title: str + metadata: dict[str, str] body_elements: list[str] def render(self) -> str: body = "\n".join(self.body_elements) + meta_tags = "\n".join( + f'' + for name, value in self.metadata.items() + ) return f""" - {self.title} + + {self.title} + {meta_tags} + {body} @@ -22,6 +30,7 @@ class HTMLBuilder: def __init__(self) -> None: self._title: str = "Untitled" self._body: list[str] = [] + self._metadata: dict[str, str] = {} def set_title(self, title: str) -> Self: self._title = title @@ -41,5 +50,9 @@ def add_button(self, label: str, onclick: str = "#") -> Self: ) return self + def add_metadata(self, name: str, content: str) -> Self: + self._metadata[name] = content + return self + def build(self) -> HTMLPage: - return HTMLPage(self._title, self._body) + return HTMLPage(self._title, self._metadata, self._body) diff --git a/2025/builder/main.py b/2025/builder/main.py index 89e85b9..e3c88b9 100644 --- a/2025/builder/main.py +++ b/2025/builder/main.py @@ -9,10 +9,11 @@ def main() -> None: builder.set_title("Builder Pattern UI") .add_header("Hello from Python!", level=1) .add_paragraph("This page was generated using the Builder Pattern.") - .add_button("Visit ArjanCodes", onclick="https://arjan.codes/") + .add_button("Visit ArjanCodes", onclick="https://www.arjancodes.com/") .build() ) + # --- Write to HTML File --- file_path = "page.html" with open(file_path, "w") as f: f.write(page.render()) From b6c46148919896e183e1264acede486c18e8436d Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 18 Jun 2025 12:28:40 +0200 Subject: [PATCH 24/44] Added other library examples. --- 2025/builder/matplotlib_example.py | 16 ++ 2025/builder/pandas_example.py | 11 + 2025/builder/pyproject.toml | 3 + 2025/builder/uv.lock | 320 +++++++++++++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 2025/builder/matplotlib_example.py create mode 100644 2025/builder/pandas_example.py diff --git a/2025/builder/matplotlib_example.py b/2025/builder/matplotlib_example.py new file mode 100644 index 0000000..7920079 --- /dev/null +++ b/2025/builder/matplotlib_example.py @@ -0,0 +1,16 @@ +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() + +fruits = ["apple", "blueberry", "cherry", "orange"] +counts = [40, 100, 30, 55] +bar_labels = ["red", "blue", "_red", "orange"] +bar_colors = ["tab:red", "tab:blue", "tab:red", "tab:orange"] + +ax.bar(fruits, counts, label=bar_labels, color=bar_colors) + +ax.set_ylabel("fruit supply") +ax.set_title("Fruit supply by kind and color") +ax.legend(title="Fruit color") + +plt.show() diff --git a/2025/builder/pandas_example.py b/2025/builder/pandas_example.py new file mode 100644 index 0000000..cdea250 --- /dev/null +++ b/2025/builder/pandas_example.py @@ -0,0 +1,11 @@ +import pandas as pd + +df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) +styled = ( + df.style.set_caption("Styled DataFrame") + .highlight_max(axis=0) + .format("{:.2f}") + .background_gradient(cmap="viridis") +) + +print(styled.to_html()) diff --git a/2025/builder/pyproject.toml b/2025/builder/pyproject.toml index d0f71cd..cf6e474 100644 --- a/2025/builder/pyproject.toml +++ b/2025/builder/pyproject.toml @@ -3,4 +3,7 @@ name = "builder" version = "0.1.0" requires-python = ">=3.13" dependencies = [ + "jinja2>=3.1.6", + "matplotlib>=3.10.3", + "pandas>=2.3.0", ] diff --git a/2025/builder/uv.lock b/2025/builder/uv.lock index e7762de..d2431f4 100644 --- a/2025/builder/uv.lock +++ b/2025/builder/uv.lock @@ -6,3 +6,323 @@ requires-python = ">=3.13" name = "builder" version = "0.1.0" source = { virtual = "." } +dependencies = [ + { name = "jinja2" }, + { name = "matplotlib" }, + { name = "pandas" }, +] + +[package.metadata] +requires-dist = [ + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "pandas", specifier = ">=2.3.0" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "fonttools" +version = "4.58.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026, upload-time = "2025-06-13T17:25:15.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197, upload-time = "2025-06-13T17:24:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272, upload-time = "2025-06-13T17:24:43.428Z" }, + { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184, upload-time = "2025-06-13T17:24:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445, upload-time = "2025-06-13T17:24:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800, upload-time = "2025-06-13T17:24:49.766Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259, upload-time = "2025-06-13T17:24:51.754Z" }, + { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824, upload-time = "2025-06-13T17:24:54.324Z" }, + { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382, upload-time = "2025-06-13T17:24:56.291Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/db/8e12381333aea300890829a0a36bfa738cac95475d88982d538725143fd9/numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6", size = 20382813, upload-time = "2025-06-07T14:54:32.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633, upload-time = "2025-06-07T14:44:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683, upload-time = "2025-06-07T14:44:28.847Z" }, + { url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683, upload-time = "2025-06-07T14:44:38.417Z" }, + { url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253, upload-time = "2025-06-07T14:44:49.359Z" }, + { url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658, upload-time = "2025-06-07T14:45:10.156Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765, upload-time = "2025-06-07T14:45:35.076Z" }, + { url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335, upload-time = "2025-06-07T14:45:58.797Z" }, + { url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608, upload-time = "2025-06-07T14:46:25.687Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005, upload-time = "2025-06-07T14:50:13.138Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093, upload-time = "2025-06-07T14:50:31.82Z" }, + { url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689, upload-time = "2025-06-07T14:50:47.888Z" }, + { url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612, upload-time = "2025-06-07T14:46:56.077Z" }, + { url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953, upload-time = "2025-06-07T14:47:18.053Z" }, + { url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806, upload-time = "2025-06-07T14:47:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169, upload-time = "2025-06-07T14:47:38.057Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701, upload-time = "2025-06-07T14:47:59.113Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983, upload-time = "2025-06-07T14:48:24.196Z" }, + { url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435, upload-time = "2025-06-07T14:48:47.712Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798, upload-time = "2025-06-07T14:49:14.866Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632, upload-time = "2025-06-07T14:49:25.67Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491, upload-time = "2025-06-07T14:49:44.898Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490, upload-time = "2025-06-05T03:27:54.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913, upload-time = "2025-06-05T03:27:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249, upload-time = "2025-06-05T16:50:20.17Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359, upload-time = "2025-06-05T03:27:06.431Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789, upload-time = "2025-06-05T03:27:09.875Z" }, + { url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734, upload-time = "2025-06-06T00:00:22.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381, upload-time = "2025-06-05T03:27:15.641Z" }, + { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135, upload-time = "2025-06-05T03:27:24.131Z" }, + { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356, upload-time = "2025-06-05T03:27:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674, upload-time = "2025-06-05T03:27:39.448Z" }, + { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876, upload-time = "2025-06-05T03:27:43.652Z" }, + { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182, upload-time = "2025-06-05T03:27:47.652Z" }, + { url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686, upload-time = "2025-06-06T00:00:26.142Z" }, + { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847, upload-time = "2025-06-05T03:27:51.465Z" }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] From 7da2620f05a963288726e7b4b9652ac81bb22fc4 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 24 Jun 2025 16:48:18 +0200 Subject: [PATCH 25/44] Added library examples. --- 2025/libraries/fastapi_page_example.py | 37 + 2025/libraries/flet_example.py | 8 + 2025/libraries/httpx_example.py | 6 + 2025/libraries/hypothesis_example.py | 7 + 2025/libraries/nicegui_example.py | 5 + 2025/libraries/pydantic_ai_example.py | 12 + 2025/libraries/pyproject.toml | 21 + 2025/libraries/pyset_example.py | 14 + 2025/libraries/rich_example.py | 3 + 2025/libraries/settings.env | 2 + 2025/libraries/tabulate_example.py | 4 + 2025/libraries/textual_example.py | 10 + 2025/libraries/toolz_example.py | 4 + 2025/libraries/uv.lock | 2252 ++++++++++++++++++++++++ 14 files changed, 2385 insertions(+) create mode 100644 2025/libraries/fastapi_page_example.py create mode 100644 2025/libraries/flet_example.py create mode 100644 2025/libraries/httpx_example.py create mode 100644 2025/libraries/hypothesis_example.py create mode 100644 2025/libraries/nicegui_example.py create mode 100644 2025/libraries/pydantic_ai_example.py create mode 100644 2025/libraries/pyproject.toml create mode 100644 2025/libraries/pyset_example.py create mode 100644 2025/libraries/rich_example.py create mode 100644 2025/libraries/settings.env create mode 100644 2025/libraries/tabulate_example.py create mode 100644 2025/libraries/textual_example.py create mode 100644 2025/libraries/toolz_example.py create mode 100644 2025/libraries/uv.lock diff --git a/2025/libraries/fastapi_page_example.py b/2025/libraries/fastapi_page_example.py new file mode 100644 index 0000000..5765a13 --- /dev/null +++ b/2025/libraries/fastapi_page_example.py @@ -0,0 +1,37 @@ +import uvicorn +from fastapi import FastAPI +from fastapi_pagination import Page, add_pagination, paginate +from pydantic import BaseModel + +app = FastAPI() + + +# --- Define your Pydantic model --- +class Item(BaseModel): + id: int + name: str + + +# --- Fake database --- +items_db: list[Item] = [ + Item(id=i, name=f"Item {i}") for i in range(1, 101) +] # 100 items + + +# --- Paginated endpoint --- +@app.get("/items", response_model=Page[Item]) +def get_items(): + return paginate(items_db) + + +# --- Activate pagination --- +add_pagination(app) + + +def main() -> None: + uvicorn.run("fastapi_page_example:app", host="127.0.0.1", port=8000, reload=True) + + +# --- Run server directly --- +if __name__ == "__main__": + main() diff --git a/2025/libraries/flet_example.py b/2025/libraries/flet_example.py new file mode 100644 index 0000000..04c414c --- /dev/null +++ b/2025/libraries/flet_example.py @@ -0,0 +1,8 @@ +import flet as ft + + +def main(page: ft.Page): + page.add(ft.Text("Hello, Flet!")) + + +ft.app(target=main) diff --git a/2025/libraries/httpx_example.py b/2025/libraries/httpx_example.py new file mode 100644 index 0000000..789b3c1 --- /dev/null +++ b/2025/libraries/httpx_example.py @@ -0,0 +1,6 @@ +import httpx + +response = httpx.get( + "https://business.arjancodes.com/api/v0/courses/66951fb842a33dd06c85e343" +) +print(response.json()) diff --git a/2025/libraries/hypothesis_example.py b/2025/libraries/hypothesis_example.py new file mode 100644 index 0000000..bcccb39 --- /dev/null +++ b/2025/libraries/hypothesis_example.py @@ -0,0 +1,7 @@ +from hypothesis import given +from hypothesis.strategies import integers, lists + + +@given(lists(integers())) +def test_sort_idempotent(xs: list[int]) -> None: + assert sorted(sorted(xs)) == sorted(xs) diff --git a/2025/libraries/nicegui_example.py b/2025/libraries/nicegui_example.py new file mode 100644 index 0000000..b03f15e --- /dev/null +++ b/2025/libraries/nicegui_example.py @@ -0,0 +1,5 @@ +from nicegui import ui + +ui.label("Hello from NiceGUI!") +ui.button("Click me", on_click=lambda: ui.notify("Clicked!")) +ui.run() diff --git a/2025/libraries/pydantic_ai_example.py b/2025/libraries/pydantic_ai_example.py new file mode 100644 index 0000000..a759392 --- /dev/null +++ b/2025/libraries/pydantic_ai_example.py @@ -0,0 +1,12 @@ +from pydantic_ai import Agent + +agent = Agent( + "google-gla:gemini-1.5-flash", + system_prompt="Be concise, reply with one sentence.", +) + +result = agent.run_sync('Where does "hello world" come from?') +print(result.output) +""" +The first known use of "hello, world" was in a 1974 textbook about the C programming language. +""" diff --git a/2025/libraries/pyproject.toml b/2025/libraries/pyproject.toml new file mode 100644 index 0000000..7d6a45b --- /dev/null +++ b/2025/libraries/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "libraries" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "fastapi>=0.115.13", + "fastapi-pagination>=0.13.2", + "flet[all]>=0.28.3", + "httpx>=0.28.1", + "hypothesis>=6.135.14", + "nicegui>=2.20.0", + "pydantic-ai>=0.3.2", + "pydantic-settings>=2.10.0", + "pytest>=8.4.1", + "reflex>=0.7.14", + "rich>=14.0.0", + "tabulate>=0.9.0", + "textual>=3.5.0", + "toolz>=1.0.0", + "uvicorn>=0.34.3", +] diff --git a/2025/libraries/pyset_example.py b/2025/libraries/pyset_example.py new file mode 100644 index 0000000..f73ee68 --- /dev/null +++ b/2025/libraries/pyset_example.py @@ -0,0 +1,14 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + database_url: str + debug: bool = False + + class Config: + env_file = "settings.env" + + +settings = Settings() +print(f"Database URL: {settings.database_url}") +print(f"Debug mode: {settings.debug}") diff --git a/2025/libraries/rich_example.py b/2025/libraries/rich_example.py new file mode 100644 index 0000000..25c45c8 --- /dev/null +++ b/2025/libraries/rich_example.py @@ -0,0 +1,3 @@ +from rich import print + +print("[bold magenta]Hello, World![/bold magenta]") diff --git a/2025/libraries/settings.env b/2025/libraries/settings.env new file mode 100644 index 0000000..c2d733c --- /dev/null +++ b/2025/libraries/settings.env @@ -0,0 +1,2 @@ +DATABASE_URL=postgresql://user:pass@localhost:5432/mydb +DEBUG=true \ No newline at end of file diff --git a/2025/libraries/tabulate_example.py b/2025/libraries/tabulate_example.py new file mode 100644 index 0000000..acf8eb1 --- /dev/null +++ b/2025/libraries/tabulate_example.py @@ -0,0 +1,4 @@ +from tabulate import tabulate + +table = [["Alice", 24], ["Bob", 19]] +print(tabulate(table, headers=["Name", "Age"])) diff --git a/2025/libraries/textual_example.py b/2025/libraries/textual_example.py new file mode 100644 index 0000000..09f2148 --- /dev/null +++ b/2025/libraries/textual_example.py @@ -0,0 +1,10 @@ +from textual.app import App +from textual.widgets import Static + + +class MyApp(App): + def compose(self): + yield Static("Hello from Textual!") + + +MyApp().run() diff --git a/2025/libraries/toolz_example.py b/2025/libraries/toolz_example.py new file mode 100644 index 0000000..47328dd --- /dev/null +++ b/2025/libraries/toolz_example.py @@ -0,0 +1,4 @@ +from toolz import compose + +strip_upper = compose(str.strip, str.upper) +print(strip_upper(" hello ")) # -> "HELLO" diff --git a/2025/libraries/uv.lock b/2025/libraries/uv.lock new file mode 100644 index 0000000..5cd90cb --- /dev/null +++ b/2025/libraries/uv.lock @@ -0,0 +1,2252 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910, upload-time = "2025-06-14T15:14:30.604Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566, upload-time = "2025-06-14T15:14:32.275Z" }, + { url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856, upload-time = "2025-06-14T15:14:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683, upload-time = "2025-06-14T15:14:36.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946, upload-time = "2025-06-14T15:14:38Z" }, + { url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017, upload-time = "2025-06-14T15:14:39.951Z" }, + { url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390, upload-time = "2025-06-14T15:14:42.151Z" }, + { url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719, upload-time = "2025-06-14T15:14:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424, upload-time = "2025-06-14T15:14:45.945Z" }, + { url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447, upload-time = "2025-06-14T15:14:47.911Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110, upload-time = "2025-06-14T15:14:50.334Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706, upload-time = "2025-06-14T15:14:52.378Z" }, + { url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839, upload-time = "2025-06-14T15:14:54.617Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311, upload-time = "2025-06-14T15:14:56.597Z" }, + { url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202, upload-time = "2025-06-14T15:14:58.598Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794, upload-time = "2025-06-14T15:15:00.939Z" }, + { url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735, upload-time = "2025-06-14T15:15:02.858Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, +] + +[[package]] +name = "alembic" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/35/116797ff14635e496bbda0c168987f5326a6555b09312e9b817e360d1f56/alembic-1.16.2.tar.gz", hash = "sha256:e53c38ff88dadb92eb22f8b150708367db731d58ad7e9d417c9168ab516cbed8", size = 1963563, upload-time = "2025-06-16T18:05:08.566Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/e2/88e425adac5ad887a087c38d04fe2030010572a3e0e627f8a6e8c33eeda8/alembic-1.16.2-py3-none-any.whl", hash = "sha256:5f42e9bd0afdbd1d5e3ad856c01754530367debdebf21ed6894e34af52b3bb03", size = 242717, upload-time = "2025-06-16T18:05:10.27Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.55.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/19/e2e09bc7fc0c4562ae865b3e5d487931c254c517e1c739b0c8aef2cf3186/anthropic-0.55.0.tar.gz", hash = "sha256:61826efa1bda0e4c7dc6f6a0d82b7d99b3fda970cd048d40ef5fca08a5eabd33", size = 408192, upload-time = "2025-06-23T18:52:26.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/8f/ba982f539db40f49a610f61562e9b54fb9c85e7b9ede9a46ff6f9e79042f/anthropic-0.55.0-py3-none-any.whl", hash = "sha256:3518433fc0372a13f2b793b4cabecc7734ec9176e063a0f28dac19aa17c57f94", size = 289318, upload-time = "2025-06-23T18:52:24.478Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "bidict" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, +] + +[[package]] +name = "binaryornot" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/fe/7ebfec74d49f97fc55cd38240c7a7d08134002b1e14be8c3897c0dd5e49b/binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", size = 371054, upload-time = "2017-08-03T15:55:25.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/7e/f7b6f453e6481d1e233540262ccbfcf89adcd43606f44a028d7f5fae5eb2/binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4", size = 9006, upload-time = "2017-08-03T15:55:31.23Z" }, +] + +[[package]] +name = "boto3" +version = "1.38.42" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/d3/43ea4ee898b3d0b3865b8529d9699d46876687c14b470081e2d7692531a5/boto3-1.38.42.tar.gz", hash = "sha256:2cb783c668ae4f2a86b6497b47251b9baf9a16db8fff863b57eae683276b9e1f", size = 111838, upload-time = "2025-06-23T19:27:04.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/a9/13160d0e6b44c7fa9d6805a8cc83303af32455140b1b7decd8ff23fbbfee/boto3-1.38.42-py3-none-any.whl", hash = "sha256:a9b4c7021bf5adee985523fc87db27a7200de161c094cb8f709b93a81797dc8a", size = 139923, upload-time = "2025-06-23T19:27:03.145Z" }, +] + +[[package]] +name = "botocore" +version = "1.38.42" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/4d/4b543efe1ce84791adc7fdd33e397d29c958467ba04f06f0c23c90343e69/botocore-1.38.42.tar.gz", hash = "sha256:3a14188e48f6e26be561164373d34150fa9cb39f7ad32cc745dcd3ab05f43683", size = 14039614, upload-time = "2025-06-23T19:26:55.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/7e/250b9c8651c85becb3f6646101299e5519a209671f8bd9f0d4c16a12c629/botocore-1.38.42-py3-none-any.whl", hash = "sha256:fbbeac30c045b5c19f1c3bb063ea2b6315ce2d6fcb3d898e87d1c1846297961c", size = 13701936, upload-time = "2025-06-23T19:26:50.418Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cohere" +version = "5.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "tokenizers" }, + { name = "types-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/33/69c7d1b25a20eafef4197a1444c7f87d5241e936194e54876ea8996157e6/cohere-5.15.0.tar.gz", hash = "sha256:e802d4718ddb0bb655654382ebbce002756a3800faac30296cde7f1bdc6ff2cc", size = 135021, upload-time = "2025-04-15T13:39:51.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/87/94694db7fe6df979fbc03286eaabdfa98f1c8fa532960e5afdf965e10960/cohere-5.15.0-py3-none-any.whl", hash = "sha256:22ff867c2a6f2fc2b585360c6072f584f11f275ef6d9242bac24e0fa2df1dfb5", size = 259522, upload-time = "2025-04-15T13:39:49.498Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cookiecutter" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, + { name = "binaryornot" }, + { name = "click" }, + { name = "jinja2" }, + { name = "python-slugify" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/17/9f2cd228eb949a91915acd38d3eecdc9d8893dde353b603f0db7e9f6be55/cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c", size = 158767, upload-time = "2024-02-21T18:02:41.949Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/d9/0137658a353168ffa9d0fc14b812d3834772040858ddd1cb6eeaf09f7a44/cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d", size = 39177, upload-time = "2024-02-21T18:02:39.569Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + +[[package]] +name = "fasta2a" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/21/c79bd6082ce107275449d180d49af9248068bb2f10375666f963a418e20c/fasta2a-0.3.2.tar.gz", hash = "sha256:cfb8f6d4a7e72f4c23f57c08476563889efbd64218cdb0dbb051faeca53f5989", size = 12292, upload-time = "2025-06-21T05:25:08.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/e8/b7f49806f697f8f2d2556ae6e6eb7573055c06e9e6e87dfbc618e36fcd61/fasta2a-0.3.2-py3-none-any.whl", hash = "sha256:da5b442d2559b2f4bb44807c997139ba15e22ba74f5790181f568be9a75d833b", size = 15328, upload-time = "2025-06-21T05:24:58.737Z" }, +] + +[[package]] +name = "fastapi" +version = "0.115.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307", size = 295680, upload-time = "2025-06-17T11:49:45.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865", size = 95315, upload-time = "2025-06-17T11:49:44.106Z" }, +] + +[[package]] +name = "fastapi-pagination" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/25/fc3c9ed9d99df279acdbf57da1a3a91ffceae9c087882cbe8a9cb1229b1e/fastapi_pagination-0.13.2.tar.gz", hash = "sha256:5e76f129aef706601b86114428ca3ff68715bfa3929bf4df8c7ed27561d7f661", size = 550389, upload-time = "2025-06-07T09:30:44.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/cb/cf2f10d4620b31a77705226c7292f39b4a191cef3485ea42561fc2e157d9/fastapi_pagination-0.13.2-py3-none-any.whl", hash = "sha256:d2ec66ffda5cd9c1d665521f3916b16ebbb15d5010a945449292540ef70c4d9a", size = 50404, upload-time = "2025-06-07T09:30:42.218Z" }, +] + +[[package]] +name = "fastavro" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/8f/32664a3245247b13702d13d2657ea534daf64e58a3f72a3a2d10598d6916/fastavro-1.11.1.tar.gz", hash = "sha256:bf6acde5ee633a29fb8dfd6dfea13b164722bc3adc05a0e055df080549c1c2f8", size = 1016250, upload-time = "2025-05-18T04:54:31.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/08/8e25b9e87a98f8c96b25e64565fa1a1208c0095bb6a84a5c8a4b925688a5/fastavro-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f963b8ddaf179660e814ab420850c1b4ea33e2ad2de8011549d958b21f77f20a", size = 931520, upload-time = "2025-05-18T04:55:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/02/ee/7cf5561ef94781ed6942cee6b394a5e698080f4247f00f158ee396ec244d/fastavro-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0253e5b6a3c9b62fae9fc3abd8184c5b64a833322b6af7d666d3db266ad879b5", size = 3195989, upload-time = "2025-05-18T04:55:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/f02f097d79f090e5c5aca8a743010c4e833a257c0efdeb289c68294f7928/fastavro-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca637b150e1f4c0e8e564fad40a16bd922bcb7ffd1a6e4836e6084f2c4f4e8db", size = 3239755, upload-time = "2025-05-18T04:55:16.463Z" }, + { url = "https://files.pythonhosted.org/packages/09/4c/46626b4ee4eb8eb5aa7835973c6ba8890cf082ef2daface6071e788d2992/fastavro-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76af1709031621828ca6ce7f027f7711fa33ac23e8269e7a5733996ff8d318da", size = 3243788, upload-time = "2025-05-18T04:55:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6f/8ed42524e9e8dc0554f0f211dd1c6c7a9dde83b95388ddcf7c137e70796f/fastavro-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8224e6d8d9864d4e55dafbe88920d6a1b8c19cc3006acfac6aa4f494a6af3450", size = 3378330, upload-time = "2025-05-18T04:55:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/b8/51/38cbe243d5facccab40fc43a4c17db264c261be955ce003803d25f0da2c3/fastavro-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:cde7ed91b52ff21f0f9f157329760ba7251508ca3e9618af3ffdac986d9faaa2", size = 443115, upload-time = "2025-05-18T04:55:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/d0/57/0d31ed1a49c65ad9f0f0128d9a928972878017781f9d4336f5f60982334c/fastavro-1.11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e5ed1325c1c414dd954e7a2c5074daefe1eceb672b8c727aa030ba327aa00693", size = 1021401, upload-time = "2025-05-18T04:55:23.431Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/a3f1a75fbfc16b3eff65dc0efcdb92364967923194312b3f8c8fc2cb95be/fastavro-1.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd3c95baeec37188899824faf44a5ee94dfc4d8667b05b2f867070c7eb174c4", size = 3384349, upload-time = "2025-05-18T04:55:25.575Z" }, + { url = "https://files.pythonhosted.org/packages/be/84/02bceb7518867df84027232a75225db758b9b45f12017c9743f45b73101e/fastavro-1.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e0babcd81acceb4c60110af9efa25d890dbb68f7de880f806dadeb1e70fe413", size = 3240658, upload-time = "2025-05-18T04:55:27.633Z" }, + { url = "https://files.pythonhosted.org/packages/f2/17/508c846c644d39bc432b027112068b8e96e7560468304d4c0757539dd73a/fastavro-1.11.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c0cb8063c7208b53b6867983dc6ae7cc80b91116b51d435d2610a5db2fc52f", size = 3372809, upload-time = "2025-05-18T04:55:30.063Z" }, + { url = "https://files.pythonhosted.org/packages/fe/84/9c2917a70ed570ddbfd1d32ac23200c1d011e36c332e59950d2f6d204941/fastavro-1.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1bc2824e9969c04ab6263d269a1e0e5d40b9bd16ade6b70c29d6ffbc4f3cc102", size = 3387171, upload-time = "2025-05-18T04:55:32.531Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "flet" +version = "0.28.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", marker = "platform_system != 'Pyodide'" }, + { name = "oauthlib", marker = "platform_system != 'Pyodide'" }, + { name = "repath" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/d0/9ba4ee34972e9e0cf54b1f7d17c695491632421f81301993f2aec8d12105/flet-0.28.3-py3-none-any.whl", hash = "sha256:649bfc4af7933956ecf44963df6c0d997bff9ceeaf89d3c86d96803840cab83e", size = 463000, upload-time = "2025-05-20T19:44:58.651Z" }, +] + +[package.optional-dependencies] +all = [ + { name = "flet-cli" }, + { name = "flet-desktop", marker = "sys_platform == 'darwin' or sys_platform == 'win32'" }, + { name = "flet-desktop-light", marker = "sys_platform == 'linux'" }, + { name = "flet-web" }, +] + +[[package]] +name = "flet-cli" +version = "0.28.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cookiecutter" }, + { name = "flet" }, + { name = "packaging" }, + { name = "qrcode" }, + { name = "toml" }, + { name = "watchdog" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/33/9398add46c07a8247a28dc05fd96e26d12d07c7153126ce67ed42a6439bb/flet_cli-0.28.3-py3-none-any.whl", hash = "sha256:2759e4526472a32a584836cf926a9c3ba8fe6e363b4305454a759668d4fcad70", size = 44149, upload-time = "2025-05-20T19:44:57.336Z" }, +] + +[[package]] +name = "flet-desktop" +version = "0.28.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/0e/f797d3052de953fa42b804b6ff170ad5c7708fc2959ffcce9d2a49a4b56c/flet_desktop-0.28.3.tar.gz", hash = "sha256:3e5db7b152de8cd3935e98eb39c162bf5c66f595d30c006f28d48d60e77a463d", size = 39876159, upload-time = "2025-05-20T19:39:28.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/e6/280350788df36041b825a1e2ceeb2a60672f208164f3f2ff75c2cc16f1c8/flet_desktop-0.28.3-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:6763b8e14863b0ee93e310720efa02202acae6d6ce8ff783663380f350f4f382", size = 47048073, upload-time = "2025-05-20T19:42:36.252Z" }, + { url = "https://files.pythonhosted.org/packages/34/e0/7a1486d8f71bca34ae928f5f8833d19ec0bda5ad5975e8db9e0543ae577b/flet_desktop-0.28.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:89797a387e743808733f308c7faa1158ab768966180c0fc4207f3e95d3b25db3", size = 47048072, upload-time = "2025-05-20T19:42:41.787Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5f/85e74518b6ef0cebc6f7b1dfc63b523df821602745cf3a31a9607be18cf5/flet_desktop-0.28.3-py3-none-win32.whl", hash = "sha256:dc24f57ba725b974b4795b46e35f2b5348c4843f5117e9fc18b25c4abfa5caf4", size = 40265313, upload-time = "2025-05-20T19:39:19.761Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/c953ee675f2751f14329e23dbf538bb703c64ca1ad1c88e244ca17fe459e/flet_desktop-0.28.3-py3-none-win_amd64.whl", hash = "sha256:35db313302fd4c376ba9be4d43f953a5c67d1ba99180dc6afee702699ed14749", size = 40265319, upload-time = "2025-05-20T19:39:24.527Z" }, +] + +[[package]] +name = "flet-desktop-light" +version = "0.28.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flet" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/a9/3eb542246b49c40d39ba667c53f1f1f0c40f87b6b96d5542f29e2ae98eb3/flet_desktop_light-0.28.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:af6e53224bedd5c287c54ec3021ef3879abbba1265261a01e80badc3fbaddb86", size = 14978766, upload-time = "2025-05-20T19:31:54.681Z" }, + { url = "https://files.pythonhosted.org/packages/c6/06/a21078e117408519e4ea27f18d3826cf067d069d92e684a683261da1ab54/flet_desktop_light-0.28.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f3dc473aae8d6cf4270e59bbb51e819068c343a49a8836cced2d17fb0359a7f5", size = 15579831, upload-time = "2025-05-20T19:32:11.698Z" }, + { url = "https://files.pythonhosted.org/packages/c0/83/195152cc264b37426c8595418d52d51a30ef55d55db9e1c3c22818c01e47/flet_desktop_light-0.28.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d45647d68ce0aaf5418c938e8e02f404bbb8814ece504f7242730520c3697c47", size = 14978756, upload-time = "2025-05-20T19:31:57.01Z" }, + { url = "https://files.pythonhosted.org/packages/07/03/6b44479989b55994c14a88c41b1bdc0635ee19509453d301b7caaff8e1af/flet_desktop_light-0.28.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3090ca7c07392c76bd47ef29f79744c7d114edda466be5f675abc5f65f1f5be7", size = 15579821, upload-time = "2025-05-20T19:32:13.836Z" }, +] + +[[package]] +name = "flet-web" +version = "0.28.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "flet" }, + { name = "uvicorn", extra = ["standard"] }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/8a/01f73ae7123090b2974f0c5ba70d7d9fc463f47f6e12daeca59fdc40e1a9/flet_web-0.28.3-py3-none-any.whl", hash = "sha256:919c13f374e7cee539d29a8ccd6decb739528ef257c88e60af4df1ebab045bfb", size = 3137744, upload-time = "2025-05-20T19:27:32.443Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-genai" +version = "1.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/8a/4a628e2e918f8c4a48ea778b68395b97b05b6873c6e528e78ccfb02a2c8d/google_genai-1.21.1.tar.gz", hash = "sha256:5412fde7f0b39574a4670a9a25e398824a12b3cddd632fdff66d1b9bcfdbfcb4", size = 205636, upload-time = "2025-06-19T14:09:20.466Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/5c/659c2b992d631a873ae8fed612ce92af423fdc5f7d541dec7ce8f4b1789e/google_genai-1.21.1-py3-none-any.whl", hash = "sha256:fa6fa5311f9a757ce65cd528a938a0f309bb3032516015bf5b3022e63b2fc46b", size = 206388, upload-time = "2025-06-19T14:09:19.016Z" }, +] + +[[package]] +name = "granian" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/83/3c58fbccda2c70c19a09c26c5559314ac71f258e42d9e57cb88e7e36afa5/granian-2.3.4.tar.gz", hash = "sha256:ed04cb7f9befb1bcb8f97b7fee6d19eb4375346eeddd43c77193c35e9cf66b9a", size = 101156, upload-time = "2025-06-14T13:10:36.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/d5/e04a128a098584008690705f0365290bb8b6d8271d671380e3a185fb0a06/granian-2.3.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f3a7488836f064bb3dedabd09c223bf7408e9fd5bf3a77587c73b51d6ce1dbce", size = 3039582, upload-time = "2025-06-14T13:09:17.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/1f/72f9bbb4e8f1b83d26cfbcfdb4f53711b1a9630b93692e8a0e7fa98588e3/granian-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6ba37638c0bfa1b68c852738290ffac99f657ffbc2d6cdcc832d311e189d1947", size = 2721739, upload-time = "2025-06-14T13:09:19.822Z" }, + { url = "https://files.pythonhosted.org/packages/e6/40/c8d79fe2a3900895e595581a9b8be7872e1798efc180f426faf99b73282d/granian-2.3.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29d6d5d407dbd5fcbe58a412619ca060fa3de26e6f37ef48f0ebef2eb8bbec57", size = 3345502, upload-time = "2025-06-14T13:09:21.236Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2b/1dc6537e5ae95fbd553d771590f46bcedb8df3422d7eb034298af6c14d7a/granian-2.3.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90d2c23a8df642af2bac8e40d17b09550d67498b2a8ea6a8ff8844edba9c14e8", size = 2999116, upload-time = "2025-06-14T13:09:22.914Z" }, + { url = "https://files.pythonhosted.org/packages/67/d6/da281d75f7b897182021ce2586ce9f4bcb8e63def0ce905cc8da49122d5a/granian-2.3.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b94e70080877f368578bc8040936c605eebee204905cd64af0497a4c34307209", size = 3229521, upload-time = "2025-06-14T13:09:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d4/af75b73ee146d4bc673303cb3f9af6f19d66738d0082ec041d9b8b465451/granian-2.3.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a1a393fbb8a3245c0b26ae2cf37649d0f1438d61ea780445885212b878ba4e5d", size = 3136218, upload-time = "2025-06-14T13:09:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/e2089e4d19e674bbe591b69013379004e93d0d05160a9c186726b478fdcb/granian-2.3.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:b390427ac03fd09e6fdf5d290f47480b6e1471e2abf3889d278f119cbea97256", size = 3150105, upload-time = "2025-06-14T13:09:27.087Z" }, + { url = "https://files.pythonhosted.org/packages/11/26/917d9ee53ee74471b494ba69f8bb160cbf99200ccf642f67942123033983/granian-2.3.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6135af0ed734b718f1d7ac2540a088971b06ef65db9f58d44c1ce7dd5df354ac", size = 3467642, upload-time = "2025-06-14T13:09:28.397Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/50c4911db8cc6f426aa266d0d114741a31a09ad37d10e832d57638f9c088/granian-2.3.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:bac27d8b9bbecd5c2027a23cef6135d83ec05f6d0061b40dfdd9587a3f761579", size = 3267179, upload-time = "2025-06-14T13:09:30.059Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7c/83e02966e35614a3dbf190307ebec2f45a5f61be2492dd57ae3c56578eb8/granian-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:baca4b613eb9545bc309af213afe1940e4253b22445da667c47f841bb7baa666", size = 2788646, upload-time = "2025-06-14T13:09:31.398Z" }, + { url = "https://files.pythonhosted.org/packages/ba/56/6b67b36b9b1b067dc4f956c88a633b821668007b1da393e366e68bb4748f/granian-2.3.4-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:2224b46ad524d92ab8df5e301c489f2839312b8b487b3dcf8c032ef7f4a9b402", size = 2981091, upload-time = "2025-06-14T13:09:32.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/48/2833a6d0d32eb9f2f1f801ac27ebc4033cffac2789234d27c9fd0ae41e88/granian-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:670ce012d4f6fc045698bdf55b8d7ca262876d4c2fb894aceb5e015b1c488c25", size = 2691018, upload-time = "2025-06-14T13:09:34.513Z" }, + { url = "https://files.pythonhosted.org/packages/76/10/197dd8815eef3249fcc7e9ab67d1cf2312acebcfbdbe0374f0a70d7961d1/granian-2.3.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fedf6fe1c2a4cf2141860d45ff82861bf257c9d71f39e225f1989091d9656c40", size = 3082566, upload-time = "2025-06-14T13:09:36.147Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fd/526a113b20f7d952b2dafe57be0cb10ff3573f00f1ce76c4e97449f0089e/granian-2.3.4-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:41b020b24ac3888c477a2baf488e39cc64cc1235f6053d4d788542d56acbbb6c", size = 3011576, upload-time = "2025-06-14T13:09:38.277Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/d899269dc1b9daa6707eb18935ef6401e995597bdf0ef04a806fe9f307e9/granian-2.3.4-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:1fb9132a3535f3d399ed89621580b6eaed6c9df5c3f673e0a6205c987abb26d0", size = 3141072, upload-time = "2025-06-14T13:09:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/d5/95/f9c90d9e61853c31d18f1055e2f7378ed536960aa18c61f352833cd22c9c/granian-2.3.4-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:c223467643ca1e6436804cc84ec828c6f08c5c24beee6c861f76c9d65b5086c1", size = 3460907, upload-time = "2025-06-14T13:09:41.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/28/4caddadbf6778fc305e6706120a3cc78738ba421c6ae8ed45ac6ff677332/granian-2.3.4-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:4b2cef0a948a1278a6c7e5ae1414dbb852f893d56e9574da52e5f89c55fa9c3f", size = 3259581, upload-time = "2025-06-14T13:09:42.646Z" }, + { url = "https://files.pythonhosted.org/packages/00/89/19744b3ca30de038d928019f266812c69f4fb7fb827500d6c03301e6c38c/granian-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:161d7abdc3097669136be543245d687fa23e1c69936b1844f8c5334aadc060ef", size = 2801933, upload-time = "2025-06-14T13:09:44.376Z" }, +] + +[package.optional-dependencies] +reload = [ + { name = "watchfiles" }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, + { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, + { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, + { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" }, + { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137, upload-time = "2025-04-23T11:29:09.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303, upload-time = "2025-04-23T11:29:07.145Z" }, +] + +[[package]] +name = "groq" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/7d/bb053ba75357bf5e8c33def63fb31c8b0bb86dce07759a0cd8e3232d2df9/groq-0.28.0.tar.gz", hash = "sha256:65e1cab9184cbb32380d62eca50d6162269c7ec0c77e4cc868069cfe93450f9f", size = 131730, upload-time = "2025-06-12T16:22:49.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/24/20fc18d1b3e0883aeb24286ca8f26dc1970561b07d9c4412c84561bdf307/groq-0.28.0-py3-none-any.whl", hash = "sha256:c6f86638371c2cba2ca337232e76c8d412e75965ed7e3058d30c9aa5dfe84303", size = 130217, upload-time = "2025-06-12T16:22:47.97Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, + { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/8a/1362d565fefabaa4185cf3ae842a98dbc5b35146f5694f7080f043a6952f/huggingface_hub-0.33.0.tar.gz", hash = "sha256:aa31f70d29439d00ff7a33837c03f1f9dd83971ce4e29ad664d63ffb17d3bb97", size = 426179, upload-time = "2025-06-11T17:08:07.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/fb/53587a89fbc00799e4179796f51b3ad713c5de6bb680b2becb6d37c94649/huggingface_hub-0.33.0-py3-none-any.whl", hash = "sha256:e8668875b40c68f9929150d99727d39e5ebb8a05a98e4191b908dc7ded9074b3", size = 514799, upload-time = "2025-06-11T17:08:05.757Z" }, +] + +[[package]] +name = "hypothesis" +version = "6.135.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/a5/d4f74ba61bbe5dd001c998ae8b85f9bfdc6cd29e6c5693d1116847b64251/hypothesis-6.135.14.tar.gz", hash = "sha256:2666df50b3cc40ea08b161a5389d6a1cd5aa3cab0dd8fde0ae339389714a4f67", size = 452884, upload-time = "2025-06-20T19:16:38.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/cf/491a487229b04a2ad56175c74700cfb79635dfce2d942becc6ab10c0ceb9/hypothesis-6.135.14-py3-none-any.whl", hash = "sha256:0dd5b8095e36bd288367c631f864a16c30500b01b17943dcea681233f7421860", size = 519115, upload-time = "2025-06-20T19:16:34.539Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, + { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, + { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, + { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, + { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" }, + { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" }, + { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" }, + { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" }, + { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "libraries" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi" }, + { name = "fastapi-pagination" }, + { name = "flet", extra = ["all"] }, + { name = "httpx" }, + { name = "hypothesis" }, + { name = "nicegui" }, + { name = "pydantic-ai" }, + { name = "pydantic-settings" }, + { name = "pytest" }, + { name = "reflex" }, + { name = "rich" }, + { name = "tabulate" }, + { name = "textual" }, + { name = "toolz" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.115.13" }, + { name = "fastapi-pagination", specifier = ">=0.13.2" }, + { name = "flet", extras = ["all"], specifier = ">=0.28.3" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "hypothesis", specifier = ">=6.135.14" }, + { name = "nicegui", specifier = ">=2.20.0" }, + { name = "pydantic-ai", specifier = ">=0.3.2" }, + { name = "pydantic-settings", specifier = ">=2.10.0" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "reflex", specifier = ">=0.7.14" }, + { name = "rich", specifier = ">=14.0.0" }, + { name = "tabulate", specifier = ">=0.9.0" }, + { name = "textual", specifier = ">=3.5.0" }, + { name = "toolz", specifier = ">=1.0.0" }, + { name = "uvicorn", specifier = ">=0.34.3" }, +] + +[[package]] +name = "linkify-it-py" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, +] + +[[package]] +name = "logfire-api" +version = "3.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/3a/d44e4a8e7906821a444fdfd64428a858b26fe222d1c4ed74dcd4d25556f2/logfire_api-3.21.1.tar.gz", hash = "sha256:3af7818c1d831da027667d2eeff8f8993d793eb5063e03d817b8cda90ddca1a8", size = 49362, upload-time = "2025-06-18T12:57:42.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/fe/36c8b8b66834d568d28a87de1cab4cb163f1358ac58dd8a0145db12f04e2/logfire_api-3.21.1-py3-none-any.whl", hash = "sha256:c85888e8f4df806b389c9f851ee5db044e2451dd8813ba0dd6a6c2279a8b8edb", size = 82482, upload-time = "2025-06-18T12:57:39.473Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[package.optional-dependencies] +linkify = [ + { name = "linkify-it-py" }, +] +plugins = [ + { name = "mdit-py-plugins" }, +] + +[[package]] +name = "markdown2" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/52/d7dcc6284d59edb8301b8400435fbb4926a9b0f13a12b5cbaf3a4a54bb7b/markdown2-2.5.3.tar.gz", hash = "sha256:4d502953a4633408b0ab3ec503c5d6984d1b14307e32b325ec7d16ea57524895", size = 141676, upload-time = "2025-01-24T21:13:55.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/37/0a13c83ccf5365b8e08ea572dfbc04b8cb87cadd359b2451a567f5248878/markdown2-2.5.3-py3-none-any.whl", hash = "sha256:a8ebb7e84b8519c37bf7382b3db600f1798a22c245bfd754a1f87ca8d7ea63b3", size = 48550, upload-time = "2025-01-24T21:13:49.937Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mcp" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294, upload-time = "2025-06-12T08:20:30.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload-time = "2025-06-12T08:20:28.551Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542, upload-time = "2024-09-09T20:27:49.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316, upload-time = "2024-09-09T20:27:48.397Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mistralai" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/38/6bc1ee13d73f2ca80e8dd172aee408d5699fd89eb00b5a0f3b0a96632a40/mistralai-1.8.2.tar.gz", hash = "sha256:3a2fdf35498dd71cca3ee065adf8d75331f3bc6bbfbc7ffdd20dc82ae01d9d6d", size = 176102, upload-time = "2025-06-10T17:31:11.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/85/088e4ec778879d8d3d4aa83549444c39f639d060422a6ef725029e8cfc9d/mistralai-1.8.2-py3-none-any.whl", hash = "sha256:d7f2c3c9d02475c1f1911cff2458bd01e91bbe8e15bfb57cb7ac397a9440ef8e", size = 374066, upload-time = "2025-06-10T17:31:10.461Z" }, +] + +[[package]] +name = "multidict" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512, upload-time = "2025-06-17T14:15:56.556Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/c9/092c4e9402b6d16de761cff88cb842a5c8cc50ccecaf9c4481ba53264b9e/multidict-6.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:53d92df1752df67a928fa7f884aa51edae6f1cf00eeb38cbcf318cf841c17456", size = 73486, upload-time = "2025-06-17T14:14:37.238Z" }, + { url = "https://files.pythonhosted.org/packages/08/f9/6f7ddb8213f5fdf4db48d1d640b78e8aef89b63a5de8a2313286db709250/multidict-6.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:680210de2c38eef17ce46b8df8bf2c1ece489261a14a6e43c997d49843a27c99", size = 43745, upload-time = "2025-06-17T14:14:38.32Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a7/b9be0163bfeee3bb08a77a1705e24eb7e651d594ea554107fac8a1ca6a4d/multidict-6.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e279259bcb936732bfa1a8eec82b5d2352b3df69d2fa90d25808cfc403cee90a", size = 42135, upload-time = "2025-06-17T14:14:39.897Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/93c8203f943a417bda3c573a34d5db0cf733afdfffb0ca78545c7716dbd8/multidict-6.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1c185fc1069781e3fc8b622c4331fb3b433979850392daa5efbb97f7f9959bb", size = 238585, upload-time = "2025-06-17T14:14:41.332Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fe/2582b56a1807604774f566eeef183b0d6b148f4b89d1612cd077567b2e1e/multidict-6.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6bb5f65ff91daf19ce97f48f63585e51595539a8a523258b34f7cef2ec7e0617", size = 236174, upload-time = "2025-06-17T14:14:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c4/d8b66d42d385bd4f974cbd1eaa8b265e6b8d297249009f312081d5ded5c7/multidict-6.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8646b4259450c59b9286db280dd57745897897284f6308edbdf437166d93855", size = 250145, upload-time = "2025-06-17T14:14:43.944Z" }, + { url = "https://files.pythonhosted.org/packages/bc/64/62feda5093ee852426aae3df86fab079f8bf1cdbe403e1078c94672ad3ec/multidict-6.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d245973d4ecc04eea0a8e5ebec7882cf515480036e1b48e65dffcfbdf86d00be", size = 243470, upload-time = "2025-06-17T14:14:45.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dc/9f6fa6e854625cf289c0e9f4464b40212a01f76b2f3edfe89b6779b4fb93/multidict-6.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a133e7ddc9bc7fb053733d0ff697ce78c7bf39b5aec4ac12857b6116324c8d75", size = 236968, upload-time = "2025-06-17T14:14:46.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/ae/4b81c6e3745faee81a156f3f87402315bdccf04236f75c03e37be19c94ff/multidict-6.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80d696fa38d738fcebfd53eec4d2e3aeb86a67679fd5e53c325756682f152826", size = 236575, upload-time = "2025-06-17T14:14:47.929Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fa/4089d7642ea344226e1bfab60dd588761d4791754f8072e911836a39bedf/multidict-6.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:20d30c9410ac3908abbaa52ee5967a754c62142043cf2ba091e39681bd51d21a", size = 247632, upload-time = "2025-06-17T14:14:49.525Z" }, + { url = "https://files.pythonhosted.org/packages/16/ee/a353dac797de0f28fb7f078cc181c5f2eefe8dd16aa11a7100cbdc234037/multidict-6.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c65068cc026f217e815fa519d8e959a7188e94ec163ffa029c94ca3ef9d4a73", size = 243520, upload-time = "2025-06-17T14:14:50.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/560deb3d2d95822d6eb1bcb1f1cb728f8f0197ec25be7c936d5d6a5d133c/multidict-6.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e355ac668a8c3e49c2ca8daa4c92f0ad5b705d26da3d5af6f7d971e46c096da7", size = 248551, upload-time = "2025-06-17T14:14:52.229Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/ddf277e67c78205f6695f2a7639be459bca9cc353b962fd8085a492a262f/multidict-6.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08db204213d0375a91a381cae0677ab95dd8c67a465eb370549daf6dbbf8ba10", size = 258362, upload-time = "2025-06-17T14:14:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/02/fc/d64ee1df9b87c5210f2d4c419cab07f28589c81b4e5711eda05a122d0614/multidict-6.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ffa58e3e215af8f6536dc837a990e456129857bb6fd546b3991be470abd9597a", size = 253862, upload-time = "2025-06-17T14:14:55.323Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/a2743c00d9e25f4826d3a77cc13d4746398872cf21c843eef96bb9945665/multidict-6.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e86eb90015c6f21658dbd257bb8e6aa18bdb365b92dd1fba27ec04e58cdc31b", size = 247391, upload-time = "2025-06-17T14:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/9b/03/7773518db74c442904dbd349074f1e7f2a854cee4d9529fc59e623d3949e/multidict-6.5.0-cp313-cp313-win32.whl", hash = "sha256:f34a90fbd9959d0f857323bd3c52b3e6011ed48f78d7d7b9e04980b8a41da3af", size = 41115, upload-time = "2025-06-17T14:14:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9a/6fc51b1dc11a7baa944bc101a92167d8b0f5929d376a8c65168fc0d35917/multidict-6.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fcb2aa79ac6aef8d5b709bbfc2fdb1d75210ba43038d70fbb595b35af470ce06", size = 44768, upload-time = "2025-06-17T14:15:00.427Z" }, + { url = "https://files.pythonhosted.org/packages/82/2d/0d010be24b663b3c16e3d3307bbba2de5ae8eec496f6027d5c0515b371a8/multidict-6.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:6dcee5e7e92060b4bb9bb6f01efcbb78c13d0e17d9bc6eec71660dd71dc7b0c2", size = 41770, upload-time = "2025-06-17T14:15:01.854Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/a71711a5f32f84b7b036e82182e3250b949a0ce70d51a2c6a4079e665449/multidict-6.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cbbc88abea2388fde41dd574159dec2cda005cb61aa84950828610cb5010f21a", size = 80450, upload-time = "2025-06-17T14:15:02.968Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a2/953a9eede63a98fcec2c1a2c1a0d88de120056219931013b871884f51b43/multidict-6.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70b599f70ae6536e5976364d3c3cf36f40334708bd6cebdd1e2438395d5e7676", size = 46971, upload-time = "2025-06-17T14:15:04.149Z" }, + { url = "https://files.pythonhosted.org/packages/44/61/60250212953459edda2c729e1d85130912f23c67bd4f585546fe4bdb1578/multidict-6.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:828bab777aa8d29d59700018178061854e3a47727e0611cb9bec579d3882de3b", size = 45548, upload-time = "2025-06-17T14:15:05.666Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/e78ee82e96c495bc2582b303f68bed176b481c8d81a441fec07404fce2ca/multidict-6.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9695fc1462f17b131c111cf0856a22ff154b0480f86f539d24b2778571ff94d", size = 238545, upload-time = "2025-06-17T14:15:06.88Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0f/6132ca06670c8d7b374c3a4fd1ba896fc37fbb66b0de903f61db7d1020ec/multidict-6.5.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b5ac6ebaf5d9814b15f399337ebc6d3a7f4ce9331edd404e76c49a01620b68d", size = 229931, upload-time = "2025-06-17T14:15:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/d9957c506e6df6b3e7a194f0eea62955c12875e454b978f18262a65d017b/multidict-6.5.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84a51e3baa77ded07be4766a9e41d977987b97e49884d4c94f6d30ab6acaee14", size = 248181, upload-time = "2025-06-17T14:15:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/43/3f/7d5490579640db5999a948e2c41d4a0efd91a75989bda3e0a03a79c92be2/multidict-6.5.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8de67f79314d24179e9b1869ed15e88d6ba5452a73fc9891ac142e0ee018b5d6", size = 241846, upload-time = "2025-06-17T14:15:11.596Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/252b1ce949ece52bba4c0de7aa2e3a3d5964e800bce71fb778c2e6c66f7c/multidict-6.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17f78a52c214481d30550ec18208e287dfc4736f0c0148208334b105fd9e0887", size = 232893, upload-time = "2025-06-17T14:15:12.946Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/0070bfd48c16afc26e056f2acce49e853c0d604a69c7124bc0bbdb1bcc0a/multidict-6.5.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2966d0099cb2e2039f9b0e73e7fd5eb9c85805681aa2a7f867f9d95b35356921", size = 228567, upload-time = "2025-06-17T14:15:14.267Z" }, + { url = "https://files.pythonhosted.org/packages/2a/31/90551c75322113ebf5fd9c5422e8641d6952f6edaf6b6c07fdc49b1bebdd/multidict-6.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:86fb42ed5ed1971c642cc52acc82491af97567534a8e381a8d50c02169c4e684", size = 246188, upload-time = "2025-06-17T14:15:15.985Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e2/aa4b02a55e7767ff292871023817fe4db83668d514dab7ccbce25eaf7659/multidict-6.5.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4e990cbcb6382f9eae4ec720bcac6a1351509e6fc4a5bb70e4984b27973934e6", size = 235178, upload-time = "2025-06-17T14:15:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/f67e726717c4b138b166be1700e2b56e06fbbcb84643d15f9a9d7335ff41/multidict-6.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d99a59d64bb1f7f2117bec837d9e534c5aeb5dcedf4c2b16b9753ed28fdc20a3", size = 243422, upload-time = "2025-06-17T14:15:18.939Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/15fa318285e26a50aa3fa979bbcffb90f9b4d5ec58882d0590eda067d0da/multidict-6.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e8ef15cc97c9890212e1caf90f0d63f6560e1e101cf83aeaf63a57556689fb34", size = 254898, upload-time = "2025-06-17T14:15:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3d/d6c6d1c2e9b61ca80313912d30bb90d4179335405e421ef0a164eac2c0f9/multidict-6.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:b8a09aec921b34bd8b9f842f0bcfd76c6a8c033dc5773511e15f2d517e7e1068", size = 247129, upload-time = "2025-06-17T14:15:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/29/15/1568258cf0090bfa78d44be66247cfdb16e27dfd935c8136a1e8632d3057/multidict-6.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff07b504c23b67f2044533244c230808a1258b3493aaf3ea2a0785f70b7be461", size = 243841, upload-time = "2025-06-17T14:15:23.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/57/64af5dbcfd61427056e840c8e520b502879d480f9632fbe210929fd87393/multidict-6.5.0-cp313-cp313t-win32.whl", hash = "sha256:9232a117341e7e979d210e41c04e18f1dc3a1d251268df6c818f5334301274e1", size = 46761, upload-time = "2025-06-17T14:15:24.733Z" }, + { url = "https://files.pythonhosted.org/packages/26/a8/cac7f7d61e188ff44f28e46cb98f9cc21762e671c96e031f06c84a60556e/multidict-6.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:44cb5c53fb2d4cbcee70a768d796052b75d89b827643788a75ea68189f0980a1", size = 52112, upload-time = "2025-06-17T14:15:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/51/9f/076533feb1b5488d22936da98b9c217205cfbf9f56f7174e8c5c86d86fe6/multidict-6.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:51d33fafa82640c0217391d4ce895d32b7e84a832b8aee0dcc1b04d8981ec7f4", size = 44358, upload-time = "2025-06-17T14:15:27.117Z" }, + { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181, upload-time = "2025-06-17T14:15:55.156Z" }, +] + +[[package]] +name = "nicegui" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "certifi" }, + { name = "docutils" }, + { name = "fastapi" }, + { name = "h11" }, + { name = "httpx" }, + { name = "ifaddr" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markdown2" }, + { name = "orjson", marker = "platform_machine != 'i386' and platform_machine != 'i686'" }, + { name = "pygments" }, + { name = "python-engineio" }, + { name = "python-multipart" }, + { name = "python-socketio", extra = ["asyncio-client"] }, + { name = "requests" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "vbuild" }, + { name = "watchfiles" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/ec/e2bf3f4858def66e990aa616d88a63567198b13532f0771d2b7ed5117ddd/nicegui-2.20.0.tar.gz", hash = "sha256:b238df9c3e9f915d6f74b005e5a66f40f0a7a0598bf9f8fcc5b03326b1b704f7", size = 13097951, upload-time = "2025-06-13T14:05:18.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/df/24977fe7ee9c2205e99e6d4a74220d278b8169bc8cfe441ad90fb88773aa/nicegui-2.20.0-py3-none-any.whl", hash = "sha256:db550537e2664e916080bc381bf29d0659570e42cdceb224b94a418e4db85c8c", size = 13479430, upload-time = "2025-06-13T14:05:15.173Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "openai" +version = "1.91.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/a22f2973b729eff3f1f429017bdf717930c5de0fbf9e14017bae330e4e7a/openai-1.91.0.tar.gz", hash = "sha256:d6b07730d2f7c6745d0991997c16f85cddfc90ddcde8d569c862c30716b9fc90", size = 472529, upload-time = "2025-06-23T18:27:10.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/d2/f99bdd6fc737d6b3cf0df895508d621fc9a386b375a1230ee81d46c5436e/openai-1.91.0-py3-none-any.whl", hash = "sha256:207f87aa3bc49365e014fac2f7e291b99929f4fe126c4654143440e0ad446a5f", size = 735837, upload-time = "2025-06-23T18:27:08.913Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" }, +] + +[[package]] +name = "orjson" +version = "3.10.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087, upload-time = "2025-04-29T23:29:19.083Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273, upload-time = "2025-04-29T23:29:20.602Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779, upload-time = "2025-04-29T23:29:22.062Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811, upload-time = "2025-04-29T23:29:23.602Z" }, + { url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018, upload-time = "2025-04-29T23:29:25.094Z" }, + { url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368, upload-time = "2025-04-29T23:29:26.609Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840, upload-time = "2025-04-29T23:29:28.153Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135, upload-time = "2025-04-29T23:29:29.726Z" }, + { url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810, upload-time = "2025-04-29T23:29:31.269Z" }, + { url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491, upload-time = "2025-04-29T23:29:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277, upload-time = "2025-04-29T23:29:34.946Z" }, + { url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367, upload-time = "2025-04-29T23:29:36.52Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687, upload-time = "2025-04-29T23:29:38.292Z" }, + { url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794, upload-time = "2025-04-29T23:29:40.349Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "pscript" +version = "0.7.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/68/f918702e270eddc5f7c54108f6a2f2afc2d299985820dbb0db9beb77d66d/pscript-0.7.7.tar.gz", hash = "sha256:8632f7a4483f235514aadee110edee82eb6d67336bf68744a7b18d76e50442f8", size = 176138, upload-time = "2022-01-10T10:55:02.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/bc/980e2ebd442d2a8f1d22780f73db76f2a1df3bf79b3fb501b054b4b4dd03/pscript-0.7.7-py3-none-any.whl", hash = "sha256:b0fdac0df0393a4d7497153fea6a82e6429f32327c4c0a4817f1cd68adc08083", size = 126689, upload-time = "2022-01-10T10:55:00.793Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-ai" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["a2a", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "mcp", "mistral", "openai", "vertexai"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/4b/6e2025e48e19be64439fca67a915b225fad0d8dd5938834cff2277972d76/pydantic_ai-0.3.2.tar.gz", hash = "sha256:7ce4afcc025afbc166631ccb2b221bc633249fea0e048091ef41db28243f3467", size = 40676919, upload-time = "2025-06-21T05:25:09.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/5a/2f111433977b2a8b6c157aae0ed797618e3a8eafdf5be5813311ef7cb816/pydantic_ai-0.3.2-py3-none-any.whl", hash = "sha256:7d7b0695e5ba185bc4b6252f9eef724ddb89172565323b758f2a8faaa64ef513", size = 10124, upload-time = "2025-06-21T05:24:59.872Z" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/96/aa71914c14cb09801e6637b63e3bfaefb1b10e512a9f49d0cd1dd6f67a21/pydantic_ai_slim-0.3.2.tar.gz", hash = "sha256:90f1e6d95d0bbffbca118619b3b3e0f16c5c2c281e4c8c2ec66467b8e8615621", size = 151673, upload-time = "2025-06-21T05:25:13.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/df/d9adb57ffc13e25c40c1b450814950d315dfb3b6c3af150373a4c14a12be/pydantic_ai_slim-0.3.2-py3-none-any.whl", hash = "sha256:c409f00de1921cb610cab46f07a7b55b0632be7b8b87e3609573b47c07cb5ef1", size = 202200, upload-time = "2025-06-21T05:25:03.306Z" }, +] + +[package.optional-dependencies] +a2a = [ + { name = "fasta2a" }, +] +anthropic = [ + { name = "anthropic" }, +] +bedrock = [ + { name = "boto3" }, +] +cli = [ + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "rich" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-evals" }, +] +google = [ + { name = "google-genai" }, +] +groq = [ + { name = "groq" }, +] +mcp = [ + { name = "mcp" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-evals" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-ai-slim" }, + { name = "pyyaml" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/a9/3ea4eb5572f690bc422cc96a25b84729c86ed38bfa59317bf801c089f441/pydantic_evals-0.3.2.tar.gz", hash = "sha256:9034e2b51425ea125ebff347542362d70d92c8be73a4af58282fc5b58f09f6b0", size = 42914, upload-time = "2025-06-21T05:25:15.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/38/18b16b55b16c25986bee6f86a635fb7260f5c490ddfdd8888838b227cf92/pydantic_evals-0.3.2-py3-none-any.whl", hash = "sha256:d7c5b133ce8cb3dd56c748d62b1618ba743b91459c2bf64e835d650cd0752a0b", size = 51633, upload-time = "2025-06-21T05:25:04.632Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/e5/b114a97f3cbbbe15d193329a83d5297cf911f1c62f38398bc31b7218a806/pydantic_graph-0.3.2.tar.gz", hash = "sha256:874b06d6484499e391a2f799bb3b5399420e5d786087012a8716a398bfc3aeec", size = 21858, upload-time = "2025-06-21T05:25:16.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2c/8c2396eafac80da93c84e724ca277d2f8bb6b8c32f57ad2b1caa85546eba/pydantic_graph-0.3.2-py3-none-any.whl", hash = "sha256:efab29d7f201ad7a199acd94bb4d8accd70cc756e4030c069ac0d1048cb543a2", size = 27483, upload-time = "2025-06-21T05:25:05.765Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/ef/3d61472b7801c896f9efd9bb8750977d9577098b05224c5c41820690155e/pydantic_settings-2.10.0.tar.gz", hash = "sha256:7a12e0767ba283954f3fd3fefdd0df3af21b28aa849c40c35811d52d682fa876", size = 172625, upload-time = "2025-06-21T13:56:55.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/9e/fce9331fecf1d2761ff0516c5dceab8a5fd415e82943e727dc4c5fa84a90/pydantic_settings-2.10.0-py3-none-any.whl", hash = "sha256:33781dfa1c7405d5ed2b6f150830a93bb58462a847357bd8f162f8bacb77c027", size = 45232, upload-time = "2025-06-21T13:56:53.682Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pypng" +version = "0.20220715.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/cd/112f092ec27cca83e0516de0a3368dbd9128c187fb6b52aaaa7cde39c96d/pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1", size = 128992, upload-time = "2022-07-15T14:11:05.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/b9/3766cc361d93edb2ce81e2e1f87dd98f314d7d513877a342d31b30741680/pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", size = 58057, upload-time = "2022-07-15T14:11:03.713Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-engineio" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "simple-websocket" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677, upload-time = "2025-06-04T19:22:18.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536, upload-time = "2025-06-04T19:22:16.916Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "python-socketio" +version = "5.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "python-engineio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125, upload-time = "2025-04-12T15:46:59.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" }, +] + +[package.optional-dependencies] +asyncio-client = [ + { name = "aiohttp" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "qrcode" +version = "7.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "pypng" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/35/ad6d4c5a547fe9a5baf85a9edbafff93fc6394b014fab30595877305fa59/qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845", size = 535974, upload-time = "2023-02-05T22:11:46.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/79/aaf0c1c7214f2632badb2771d770b1500d3d7cbdf2590ae62e721ec50584/qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a", size = 46197, upload-time = "2023-02-05T22:11:43.4Z" }, +] + +[[package]] +name = "redis" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/9a/0551e01ba52b944f97480721656578c8a7c46b51b99d66814f85fe3a4f3e/redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977", size = 4639129, upload-time = "2025-05-28T05:01:18.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659, upload-time = "2025-05-28T05:01:16.955Z" }, +] + +[[package]] +name = "reflex" +version = "0.7.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "click" }, + { name = "fastapi" }, + { name = "granian", extra = ["reload"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "python-multipart" }, + { name = "python-socketio" }, + { name = "redis" }, + { name = "reflex-hosting-cli" }, + { name = "rich" }, + { name = "sqlmodel" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/11/52994ec428691991959d486fa48df57fe6a571a5d8d98ebd590377bf7f07/reflex-0.7.14.tar.gz", hash = "sha256:9a36eee47e60c64630cdc341565416ee838a53ee3c60d281eea0b7887b67f1e3", size = 576039, upload-time = "2025-06-03T02:01:54.855Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/e2/9dc1156cbfc1b7cde889ab09f89b0ae0938e694c32c4c553c52f3ad0796b/reflex-0.7.14-py3-none-any.whl", hash = "sha256:7b1e0e37a88e9129aa896c5a47c212a29c620fa2e82148813cd335c172974948", size = 873575, upload-time = "2025-06-03T02:01:52.853Z" }, +] + +[[package]] +name = "reflex-hosting-cli" +version = "0.1.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/81/305748b296140110d5cdd6441eaddc6058dc8906d4a8b13f9fa97d02bac0/reflex_hosting_cli-0.1.50.tar.gz", hash = "sha256:d594d3734f4fff4ad33618aab0d0c1d9130b7148cab75ac85adb9f6a49055a48", size = 32280, upload-time = "2025-06-07T22:46:46.327Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/b9/e1802ceffc9dc026cad2c8de180789b72f5f3aa7be7d4da33645cbc2a75e/reflex_hosting_cli-0.1.50-py3-none-any.whl", hash = "sha256:5285f9eaf80858e6c5dca6cd705c0f5924a243697f58814938a73f1083cab143", size = 41226, upload-time = "2025-06-07T22:46:44.951Z" }, +] + +[[package]] +name = "repath" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/e1/824989291d0f01886074fdf9504ba54598f5665bc4dd373b589b87e76608/repath-0.9.0.tar.gz", hash = "sha256:8292139bac6a0e43fd9d70605d4e8daeb25d46672e484ed31a24c7ce0aef0fb7", size = 5492, upload-time = "2019-10-08T00:25:22.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/ed/92e9b8a3ffc562f21df14ef2538f54e911df29730e1f0d79130a4edc86e7/repath-0.9.0-py3-none-any.whl", hash = "sha256:ee079d6c91faeb843274d22d8f786094ee01316ecfe293a1eb6546312bb6a318", size = 4738, upload-time = "2019-10-08T00:25:20.842Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, +] + +[[package]] +name = "simple-websocket" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780, upload-time = "2025-03-07T05:43:32.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622, upload-time = "2025-03-07T05:43:30.37Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/f4/989bc70cb8091eda43a9034ef969b25145291f3601703b82766e5172dfed/sse_starlette-2.3.6.tar.gz", hash = "sha256:0382336f7d4ec30160cf9ca0518962905e1b69b72d6c1c995131e0a703b436e3", size = 18284, upload-time = "2025-05-30T13:34:12.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/05/78850ac6e79af5b9508f8841b0f26aa9fd329a1ba00bf65453c2d312bcc8/sse_starlette-2.3.6-py3-none-any.whl", hash = "sha256:d49a8285b182f6e2228e2609c350398b2ca2c36216c2675d875f81e93548f760", size = 10606, upload-time = "2025-05-30T13:34:11.703Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "textual" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", extra = ["linkify", "plugins"] }, + { name = "platformdirs" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/63/16cdf4b9efb47366940d8315118c5c6dd6309f5eb2c159d7195b60e2e221/textual-3.5.0.tar.gz", hash = "sha256:c4a440338694672acf271c74904f1cf1e4a64c6761c056b02a561774b81a04f4", size = 1590084, upload-time = "2025-06-20T14:46:58.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/36/2597036cb80e40f71555bf59741471f7bd76ebed112f10ae0549650a12bf/textual-3.5.0-py3-none-any.whl", hash = "sha256:7c960efb70391b754e66201776793de2b26d699d51fb91f5f78401d13cec79a1", size = 688740, upload-time = "2025-06-20T14:46:56.484Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1791eb329c07122a75b01035b1a3aa22ad139f3ce0ece1b059b506d9d9de/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12", size = 3019202, upload-time = "2025-06-24T10:24:31.791Z" }, + { url = "https://files.pythonhosted.org/packages/05/15/fd2d8104faa9f86ac68748e6f7ece0b5eb7983c7efc3a2c197cb98c99030/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91", size = 2934539, upload-time = "2025-06-24T10:24:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2e/53e8fd053e1f3ffbe579ca5f9546f35ac67cf0039ed357ad7ec57f5f5af0/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb", size = 3248665, upload-time = "2025-06-24T10:24:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/79713359f4037aa8f4d1f06ffca35312ac83629da062670e8830917e2153/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab", size = 3451305, upload-time = "2025-06-24T10:24:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/38/5f/959f3a8756fc9396aeb704292777b84f02a5c6f25c3fc3ba7530db5feb2c/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae", size = 3214757, upload-time = "2025-06-24T10:24:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/c5/74/f41a432a0733f61f3d21b288de6dfa78f7acff309c6f0f323b2833e9189f/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020", size = 3121887, upload-time = "2025-06-24T10:24:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6a/bc220a11a17e5d07b0dfb3b5c628621d4dcc084bccd27cfaead659963016/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19", size = 9091965, upload-time = "2025-06-24T10:24:44.431Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/ac386d79c4ef20dc6f39c4706640c24823dca7ebb6f703bfe6b5f0292d88/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d", size = 9053372, upload-time = "2025-06-24T10:24:46.455Z" }, + { url = "https://files.pythonhosted.org/packages/63/7b/5440bf203b2a5358f074408f7f9c42884849cd9972879e10ee6b7a8c3b3d/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365", size = 9298632, upload-time = "2025-06-24T10:24:48.446Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d2/faa1acac3f96a7427866e94ed4289949b2524f0c1878512516567d80563c/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958", size = 9470074, upload-time = "2025-06-24T10:24:50.378Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/896e1ef0707212745ae9f37e84c7d50269411aef2e9ccd0de63623feecdf/tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962", size = 2330115, upload-time = "2025-06-24T10:24:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload-time = "2025-06-24T10:24:53.71Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "toolz" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790, upload-time = "2024-10-04T16:17:04.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383, upload-time = "2024-10-04T16:17:01.533Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943, upload-time = "2025-05-16T03:06:58.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356, upload-time = "2025-05-16T03:06:57.249Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uc-micro-py" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, +] + +[[package]] +name = "vbuild" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pscript" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/be/f0c6204a36440bbcc086bfa25964d009b7391c5a3c74d6e73188efd47adb/vbuild-0.8.2.tar.gz", hash = "sha256:270cd9078349d907dfae6c0e6364a5a5e74cb86183bb5093613f12a18b435fa9", size = 8937, upload-time = "2023-08-03T09:26:36.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/3d/7b22abbdb059d551507275a2815bc2b1974e3b9f6a13781c1eac9e858965/vbuild-0.8.2-py2.py3-none-any.whl", hash = "sha256:d76bcc976a1c53b6a5776ac947606f9e7786c25df33a587ebe33ed09dd8a1076", size = 9371, upload-time = "2023-08-03T09:26:35.023Z" }, +] + +[[package]] +name = "watchdog" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/38/764baaa25eb5e35c9a043d4c4588f9836edfe52a708950f4b6d5f714fd42/watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270", size = 126587, upload-time = "2024-08-11T07:38:01.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/63/eb8994a182672c042d85a33507475c50c2ee930577524dd97aea05251527/watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b", size = 100343, upload-time = "2024-08-11T07:37:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/ce/82/027c0c65c2245769580605bcd20a1dc7dfd6c6683c8c4e2ef43920e38d27/watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d", size = 92313, upload-time = "2024-08-11T07:37:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/2a/89/ad4715cbbd3440cb0d336b78970aba243a33a24b1a79d66f8d16b4590d6a/watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7", size = 92919, upload-time = "2024-08-11T07:37:24.715Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b1/25acf6767af6f7e44e0086309825bd8c098e301eed5868dc5350642124b9/watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83", size = 82947, upload-time = "2024-08-11T07:37:45.388Z" }, + { url = "https://files.pythonhosted.org/packages/e8/90/aebac95d6f954bd4901f5d46dcd83d68e682bfd21798fd125a95ae1c9dbf/watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c", size = 82942, upload-time = "2024-08-11T07:37:46.722Z" }, + { url = "https://files.pythonhosted.org/packages/15/3a/a4bd8f3b9381824995787488b9282aff1ed4667e1110f31a87b871ea851c/watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a", size = 82947, upload-time = "2024-08-11T07:37:48.941Z" }, + { url = "https://files.pythonhosted.org/packages/09/cc/238998fc08e292a4a18a852ed8274159019ee7a66be14441325bcd811dfd/watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73", size = 82946, upload-time = "2024-08-11T07:37:50.279Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/d4b915160c9d677174aa5fae4537ae1f5acb23b3745ab0873071ef671f0a/watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc", size = 82947, upload-time = "2024-08-11T07:37:51.55Z" }, + { url = "https://files.pythonhosted.org/packages/db/02/56ebe2cf33b352fe3309588eb03f020d4d1c061563d9858a9216ba004259/watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757", size = 82944, upload-time = "2024-08-11T07:37:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/01/d2/c8931ff840a7e5bd5dcb93f2bb2a1fd18faf8312e9f7f53ff1cf76ecc8ed/watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8", size = 82947, upload-time = "2024-08-11T07:37:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d8/cdb0c21a4a988669d7c210c75c6a2c9a0e16a3b08d9f7e633df0d9a16ad8/watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19", size = 82935, upload-time = "2024-08-11T07:37:56.668Z" }, + { url = "https://files.pythonhosted.org/packages/99/2e/b69dfaae7a83ea64ce36538cc103a3065e12c447963797793d5c0a1d5130/watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b", size = 82934, upload-time = "2024-08-11T07:37:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0b/43b96a9ecdd65ff5545b1b13b687ca486da5c6249475b1a45f24d63a1858/watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c", size = 82933, upload-time = "2024-08-11T07:37:59.573Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From 2dc0f28746d375622ea9f8d996a980a511cc2914 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 25 Jun 2025 16:56:16 +0200 Subject: [PATCH 26/44] Fixed type issue in base API model. --- 2025/sdk/sdk_v3/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2025/sdk/sdk_v3/base.py b/2025/sdk/sdk_v3/base.py index 38313ff..64bda52 100644 --- a/2025/sdk/sdk_v3/base.py +++ b/2025/sdk/sdk_v3/base.py @@ -5,7 +5,7 @@ from sdk_v3 import client -class BaseAPIModel[T](BaseModel): +class BaseAPIModel[T: BaseAPIModel](BaseModel): id: str | None = None _resource_path: ClassVar[str] = "" From dc07821ed22d3954a477f624657c22180534beb4 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 8 Jul 2025 15:14:42 +0200 Subject: [PATCH 27/44] Added weather example --- 2025/testtips/pyproject.toml | 12 +++ 2025/testtips/uv.lock | 177 +++++++++++++++++++++++++++++++++++ 2025/testtips/weather.py | 32 +++++++ 3 files changed, 221 insertions(+) create mode 100644 2025/testtips/pyproject.toml create mode 100644 2025/testtips/uv.lock create mode 100644 2025/testtips/weather.py diff --git a/2025/testtips/pyproject.toml b/2025/testtips/pyproject.toml new file mode 100644 index 0000000..7a3d9a7 --- /dev/null +++ b/2025/testtips/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "testtips" +version = "0.0.1" +authors = [{name = "ArjanCodes"}] +license = {text = "MIT"} +requires-python = ">=3.12" +dependencies = [ + "httpx>=0.28.1", + "pytest>=8.4.1", + "python-dotenv>=1.1.1", +] + diff --git a/2025/testtips/uv.lock b/2025/testtips/uv.lock new file mode 100644 index 0000000..c24767b --- /dev/null +++ b/2025/testtips/uv.lock @@ -0,0 +1,177 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "testtips" +version = "0.0.1" +source = { virtual = "." } +dependencies = [ + { name = "httpx" }, + { name = "pytest" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.28.1" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] diff --git a/2025/testtips/weather.py b/2025/testtips/weather.py new file mode 100644 index 0000000..5ad9916 --- /dev/null +++ b/2025/testtips/weather.py @@ -0,0 +1,32 @@ +# weather_service.py +import os + +import httpx +from dotenv import load_dotenv + +load_dotenv() + + +class WeatherService: + def __init__(self, api_key: str): + self.api_key = api_key + + def get_temperature(self, city: str) -> float: + response = httpx.get( + "https://api.weatherapi.com/v1/current.json", + params={"key": self.api_key, "q": city}, + ) + response.raise_for_status() + data = response.json() + return data["current"]["temp_c"] + + +def main(): + api_key = os.getenv("WEATHER_API_KEY") + weather_service = WeatherService(api_key) + temperature = weather_service.get_temperature("Amsterdam") + print(f"The current temperature in Amsterdam is {temperature}°C.") + + +if __name__ == "__main__": + main() From ec0fa27e2f8f02f0c76ef95108b2562c8e673239 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Fri, 11 Jul 2025 16:52:30 +0200 Subject: [PATCH 28/44] Added testing code examples. --- 2025/testtips/pyproject.toml | 4 + 2025/testtips/tests/test_tips.py | 73 ++++++++++++++++++ 2025/testtips/tests/test_weather.py | 78 ++++++++++++++++++++ 2025/testtips/tests/test_weather_fixture.py | 50 +++++++++++++ 2025/testtips/tests/test_weather_mock_mp.py | 41 ++++++++++ 2025/testtips/tests/test_weather_refactor.py | 12 +++ 2025/testtips/uv.lock | 33 +++++++++ 2025/testtips/weather_refactor.py | 34 +++++++++ 8 files changed, 325 insertions(+) create mode 100644 2025/testtips/tests/test_tips.py create mode 100644 2025/testtips/tests/test_weather.py create mode 100644 2025/testtips/tests/test_weather_fixture.py create mode 100644 2025/testtips/tests/test_weather_mock_mp.py create mode 100644 2025/testtips/tests/test_weather_refactor.py create mode 100644 2025/testtips/weather_refactor.py diff --git a/2025/testtips/pyproject.toml b/2025/testtips/pyproject.toml index 7a3d9a7..9720917 100644 --- a/2025/testtips/pyproject.toml +++ b/2025/testtips/pyproject.toml @@ -6,7 +6,11 @@ license = {text = "MIT"} requires-python = ">=3.12" dependencies = [ "httpx>=0.28.1", + "hypothesis>=6.135.26", "pytest>=8.4.1", "python-dotenv>=1.1.1", ] +[tool.pytest.ini_options] +pythonpath = "." + diff --git a/2025/testtips/tests/test_tips.py b/2025/testtips/tests/test_tips.py new file mode 100644 index 0000000..2d93863 --- /dev/null +++ b/2025/testtips/tests/test_tips.py @@ -0,0 +1,73 @@ +# tests/test_weather.py + +import sys + +import pytest +from weather import WeatherService + +# from hypothesis import given +# from hypothesis.strategies import floats + + +# ✅ Parametrization +@pytest.mark.parametrize( + "city,expected_temp", [("London", 15), ("Berlin", 20), ("Paris", 17)] +) +def test_get_temperature_multiple_cities(monkeypatch, city, expected_temp): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": expected_temp}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + temp = service.get_temperature(city) + + assert temp == expected_temp + + +# ✅ pytest.raises +def test_get_temperature_raises_http_error(monkeypatch): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + raise Exception("API error") + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + + with pytest.raises(Exception): + service.get_temperature("Tokyo") + + +# ✅ pytest.mark.skip +@pytest.mark.skip(reason="Skipping this test for demonstration purposes.") +def test_skipped_example(): + assert False + + +# ✅ pytest.mark.skipif +@pytest.mark.skipif(sys.platform == "win32", reason="Does not run on Windows") +def test_only_runs_on_non_windows(): + assert True + + +# ✅ pytest.mark.xfail +@pytest.mark.xfail(reason="Known bug: API sometimes returns wrong temperature") +def test_expected_failure_example(): + assert 2 + 2 == 5 + + +# ✅ Hypothesis property-based test +# @given(floats(min_value=-50, max_value=50)) +# def test_temperature_range_property(temp): +# assert -50 <= temp <= 50 diff --git a/2025/testtips/tests/test_weather.py b/2025/testtips/tests/test_weather.py new file mode 100644 index 0000000..542a624 --- /dev/null +++ b/2025/testtips/tests/test_weather.py @@ -0,0 +1,78 @@ +import httpx +import pytest +from weather import WeatherService + + +# Tip 1: Single assert per test +def test_get_temperature_returns_expected_value(monkeypatch): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": 22}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Amsterdam") + + assert temp == 22 + + +# Tip 2: Clear and descriptive names +def test_get_temperature_for_different_city(monkeypatch): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": 18}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Berlin") + + assert temp == 18 + + +def test_get_temperature_handles_api_error(monkeypatch): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + raise httpx.HTTPError("API error") + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + + with pytest.raises(httpx.HTTPError): + service.get_temperature("Paris") + + +def test_get_temperature_returns_float(monkeypatch): + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": 19.5}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Rome") + + assert isinstance(temp, float) diff --git a/2025/testtips/tests/test_weather_fixture.py b/2025/testtips/tests/test_weather_fixture.py new file mode 100644 index 0000000..5d88dd2 --- /dev/null +++ b/2025/testtips/tests/test_weather_fixture.py @@ -0,0 +1,50 @@ +import pytest +from weather import WeatherService + + +# This fixture creates a WeatherService instance you can reuse +@pytest.fixture +def weather_service(): + return WeatherService(api_key="fake-key") + + +def test_get_temperature_returns_expected_value(weather_service, monkeypatch): + """ + Test that get_temperature returns the correct temperature. + """ + + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": 20}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + temp = weather_service.get_temperature("Amsterdam") + assert temp == 20 + + +def test_get_temperature_returns_float(weather_service, monkeypatch): + """ + Test that get_temperature returns a float value. + """ + + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): + pass + + def json(self): + return {"current": {"temp_c": 18.5}} + + return FakeResponse() + + monkeypatch.setattr("weather.httpx.get", fake_get) + + temp = weather_service.get_temperature("Berlin") + assert isinstance(temp, float) diff --git a/2025/testtips/tests/test_weather_mock_mp.py b/2025/testtips/tests/test_weather_mock_mp.py new file mode 100644 index 0000000..b28340a --- /dev/null +++ b/2025/testtips/tests/test_weather_mock_mp.py @@ -0,0 +1,41 @@ +from weather import WeatherService +from unittest.mock import patch, MagicMock + +# Section 4: Mocking with patch + MagicMock +def test_get_temperature_with_mocking(): + """ + Example of mocking httpx.get with a MagicMock. + """ + # Create a MagicMock response object + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"current": {"temp_c": 25}} + + # Patch httpx.get so it returns our mock_response + with patch("weather.httpx.get", return_value=mock_response) as mock_get: + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Paris") + + assert temp == 25 + mock_get.assert_called_once() + + +# Section 5: Monkey patching with monkeypatch fixture +def test_get_temperature_with_monkeypatch(monkeypatch): + """ + Example of monkeypatching httpx.get with a manual stub. + """ + + def fake_get(url, params): + class FakeResponse: + def raise_for_status(self): pass + def json(self): return {"current": {"temp_c": 19}} + return FakeResponse() + + # Monkeypatch httpx.get to use fake_get + monkeypatch.setattr("weather.httpx.get", fake_get) + + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Berlin") + + assert temp == 19 \ No newline at end of file diff --git a/2025/testtips/tests/test_weather_refactor.py b/2025/testtips/tests/test_weather_refactor.py new file mode 100644 index 0000000..26c362f --- /dev/null +++ b/2025/testtips/tests/test_weather_refactor.py @@ -0,0 +1,12 @@ +from weather_refactor import WeatherService + +def test_get_temperature_with_stub_client(): + class StubClient: + def get(self, url, params): + class Response: + def raise_for_status(self): pass + def json(self): return {"current": {"temp_c": 18}} + return Response() + + service = WeatherService(client=StubClient(), api_key="fake_key") + assert service.get_temperature("Oslo") == 18 \ No newline at end of file diff --git a/2025/testtips/uv.lock b/2025/testtips/uv.lock index c24767b..c6502d8 100644 --- a/2025/testtips/uv.lock +++ b/2025/testtips/uv.lock @@ -16,6 +16,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + [[package]] name = "certifi" version = "2025.6.15" @@ -71,6 +80,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "hypothesis" +version = "6.135.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/83/15c4e30561a0d8c8d076c88cb159187823d877118f34c851ada3b9b02a7b/hypothesis-6.135.26.tar.gz", hash = "sha256:73af0e46cd5039c6806f514fed6a3c185d91ef88b5a1577477099ddbd1a2e300", size = 454523, upload-time = "2025-07-05T04:59:45.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/78/db4fdc464219455f8dde90074660c3faf8429101b2d1299cac7d219e3176/hypothesis-6.135.26-py3-none-any.whl", hash = "sha256:fa237cbe2ae2c31d65f7230dcb866139ace635dcfec6c30dddf25974dd8ff4b9", size = 521517, upload-time = "2025-07-05T04:59:42.061Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -150,12 +172,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "testtips" version = "0.0.1" source = { virtual = "." } dependencies = [ { name = "httpx" }, + { name = "hypothesis" }, { name = "pytest" }, { name = "python-dotenv" }, ] @@ -163,6 +195,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, + { name = "hypothesis", specifier = ">=6.135.26" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "python-dotenv", specifier = ">=1.1.1" }, ] diff --git a/2025/testtips/weather_refactor.py b/2025/testtips/weather_refactor.py new file mode 100644 index 0000000..26dc140 --- /dev/null +++ b/2025/testtips/weather_refactor.py @@ -0,0 +1,34 @@ +# weather_service.py +import os + +import httpx +from dotenv import load_dotenv + +load_dotenv() + + +class WeatherService: + def __init__(self, client: httpx.Client, api_key: str): + self.client = client + self.api_key = api_key + + def get_temperature(self, city: str) -> float: + response = self.client.get( + "https://api.weatherapi.com/v1/current.json", + params={"key": self.api_key, "q": city}, + ) + response.raise_for_status() + data = response.json() + return data["current"]["temp_c"] + + +def main(): + api_key = os.getenv("WEATHER_API_KEY", "") + client = httpx.Client() + weather_service = WeatherService(client, api_key) + temperature = weather_service.get_temperature("Amsterdam") + print(f"The current temperature in Amsterdam is {temperature}°C.") + + +if __name__ == "__main__": + main() From 2200f921ca9f378951ff2b2b6df23049a2e7873c Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 15 Jul 2025 15:34:12 +0200 Subject: [PATCH 29/44] Added standard library code examples. --- 2025/standard/01_dataclasses.py | 17 +++++++++++++++++ 2025/standard/02_pathlib.py | 12 ++++++++++++ 2025/standard/03_functools.py | 23 +++++++++++++++++++++++ 2025/standard/04_tomllib.py | 6 ++++++ 2025/standard/05_graphlib.py | 17 +++++++++++++++++ 2025/standard/06_heapq.py | 28 ++++++++++++++++++++++++++++ 2025/standard/07_secrets.py | 22 ++++++++++++++++++++++ 2025/standard/08_shutil.py | 13 +++++++++++++ 2025/standard/09_textwrap.py | 22 ++++++++++++++++++++++ 2025/standard/10_itertools.py | 32 ++++++++++++++++++++++++++++++++ 2025/standard/pyproject.toml | 7 +++++++ 2025/standard/uv.lock | 8 ++++++++ 12 files changed, 207 insertions(+) create mode 100644 2025/standard/01_dataclasses.py create mode 100644 2025/standard/02_pathlib.py create mode 100644 2025/standard/03_functools.py create mode 100644 2025/standard/04_tomllib.py create mode 100644 2025/standard/05_graphlib.py create mode 100644 2025/standard/06_heapq.py create mode 100644 2025/standard/07_secrets.py create mode 100644 2025/standard/08_shutil.py create mode 100644 2025/standard/09_textwrap.py create mode 100644 2025/standard/10_itertools.py create mode 100644 2025/standard/pyproject.toml create mode 100644 2025/standard/uv.lock diff --git a/2025/standard/01_dataclasses.py b/2025/standard/01_dataclasses.py new file mode 100644 index 0000000..3385387 --- /dev/null +++ b/2025/standard/01_dataclasses.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass + + +@dataclass +class Product: + name: str + price: float + in_stock: bool = True + + +def main(): + product = Product(name="Widget", price=19.99) + print(product) + + +if __name__ == "__main__": + main() diff --git a/2025/standard/02_pathlib.py b/2025/standard/02_pathlib.py new file mode 100644 index 0000000..a50b232 --- /dev/null +++ b/2025/standard/02_pathlib.py @@ -0,0 +1,12 @@ +from pathlib import Path + +base = Path("my_project") +config = base / "config" / "settings.toml" + +print("Config path:", config) + +if config.exists(): + print("File size:", config.stat().st_size) +else: + config.parent.mkdir(parents=True, exist_ok=True) + config.write_text("[settings]\nname = 'Example'") \ No newline at end of file diff --git a/2025/standard/03_functools.py b/2025/standard/03_functools.py new file mode 100644 index 0000000..e094275 --- /dev/null +++ b/2025/standard/03_functools.py @@ -0,0 +1,23 @@ +from functools import cache, partial + +@cache +def power(base: int, exponent: int) -> int: + print(f"Computing {base}^{exponent}") + return base ** exponent + +def main() -> None: + # cached power function + print("Calculating powers with caching:") + print("Power of 2^10:", power(2, 10)) + print("Power of 3^5:", power(3, 5)) + print("Power of 2^10 again (cached):", power(2, 10)) + + square = partial(power, exponent=2) + cube = partial(power, exponent=3) + + print("Square of 5:", square(5)) + print("Cube of 2:", cube(2)) + print("Square of 5 again (cached):", square(5)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/04_tomllib.py b/2025/standard/04_tomllib.py new file mode 100644 index 0000000..921c68d --- /dev/null +++ b/2025/standard/04_tomllib.py @@ -0,0 +1,6 @@ +import tomllib + +with open("pyproject.toml", "rb") as f: + data = tomllib.load(f) + +print(data["project"]["name"]) \ No newline at end of file diff --git a/2025/standard/05_graphlib.py b/2025/standard/05_graphlib.py new file mode 100644 index 0000000..b8a277a --- /dev/null +++ b/2025/standard/05_graphlib.py @@ -0,0 +1,17 @@ +from graphlib import TopologicalSorter + +ts = TopologicalSorter[str]() + +def main(): + # Tasks and their dependencies + ts.add("compile", "fetch_sources") + ts.add("test", "compile") + ts.add("package", "test") + ts.add("deploy", "package") + ts.add("fetch_sources") + + order = list(ts.static_order()) + print("Execution order:", order) + +if __name__ == "__main__": + main() diff --git a/2025/standard/06_heapq.py b/2025/standard/06_heapq.py new file mode 100644 index 0000000..9e55d01 --- /dev/null +++ b/2025/standard/06_heapq.py @@ -0,0 +1,28 @@ +import heapq + +def main() -> None: + # Initial tasks with priorities + tasks = [ + (3, "Send email to client"), + (2, "Write documentation"), + (1, "Fix critical bug"), + ] + + heapq.heapify(tasks) + + print("Starting task processing...") + + while tasks: + priority, task = heapq.heappop(tasks) + print(f"Processing task: {task} (priority {priority})") + + # Dynamically add new tasks + if task == "Fix critical bug": + print("New urgent task arrived!") + heapq.heappush(tasks, (0, "Deploy hotfix")) + + if task == "Write documentation": + heapq.heappush(tasks, (4, "Refactor old module")) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/07_secrets.py b/2025/standard/07_secrets.py new file mode 100644 index 0000000..33b2935 --- /dev/null +++ b/2025/standard/07_secrets.py @@ -0,0 +1,22 @@ +import secrets + +def main() -> None: + # Generate a secure hexadecimal token + token_hex: str = secrets.token_hex(16) + print("Secure hex token:", token_hex) + + # Generate a secure URL-safe token + token_url: str = secrets.token_urlsafe(16) + print("Secure URL-safe token:", token_url) + + # Generate random bytes + random_bytes: bytes = secrets.token_bytes(16) + print("Random bytes:", random_bytes) + + # Randomly choose an item from a sequence + choices = ["apple", "banana", "cherry"] + selected: str = secrets.choice(choices) + print("Random choice:", selected) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/08_shutil.py b/2025/standard/08_shutil.py new file mode 100644 index 0000000..0140e7c --- /dev/null +++ b/2025/standard/08_shutil.py @@ -0,0 +1,13 @@ +from py_compile import main +import shutil + +def main() -> None: + + # Copy a file + shutil.copy("pyproject.toml", "backup_pyproject.toml") + + # Create a zip archive + shutil.make_archive("project_backup", "zip", ".") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/09_textwrap.py b/2025/standard/09_textwrap.py new file mode 100644 index 0000000..0447d0b --- /dev/null +++ b/2025/standard/09_textwrap.py @@ -0,0 +1,22 @@ +import textwrap + +def main() -> None: + text = ( + "Python is amazing. It has a huge standard library that saves you time " + "and helps you write clean, maintainable code." + ) + + wrapped = textwrap.fill(text, width=40) + print("Wrapped text:") + print(wrapped) + + indented = textwrap.indent(wrapped, prefix="> ") + print("\nIndented text:") + print(indented) + + shortened = textwrap.shorten(text, width=50, placeholder="...") + print("\nShortened text:") + print(shortened) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/10_itertools.py b/2025/standard/10_itertools.py new file mode 100644 index 0000000..92375ff --- /dev/null +++ b/2025/standard/10_itertools.py @@ -0,0 +1,32 @@ +import itertools +from collections.abc import Iterable + +def main() -> None: + items = ["a", "b", "c"] + + # All pairs of items + pairs = list(itertools.combinations(items, 2)) + print("Pairs:", pairs) + + # Infinite counter + counter = itertools.count(start=10, step=5) + print("First three counter values:") + print(next(counter)) + print(next(counter)) + print(next(counter)) + + # Cycle through items + cycle = itertools.cycle(["on", "off"]) + print("Cycle example:") + for _ in range(4): + print(next(cycle)) + + # Group consecutive identical items + data = ["a", "a", "b", "b", "b", "c", "a", "a"] + print("Grouping consecutive items:") + for key, group in itertools.groupby(data): + group_list = list(group) + print(f"{key}: {group_list}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/standard/pyproject.toml b/2025/standard/pyproject.toml new file mode 100644 index 0000000..6463b73 --- /dev/null +++ b/2025/standard/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "standard" +version = "0.1.0" +description = "Nice Python standard library tips" +requires-python = ">=3.13" +dependencies = [ +] diff --git a/2025/standard/uv.lock b/2025/standard/uv.lock new file mode 100644 index 0000000..e73db73 --- /dev/null +++ b/2025/standard/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "standard" +version = "0.1.0" +source = { virtual = "." } From e415018a06d3c45dc5e05dbe648072630b5be869 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 15 Jul 2025 16:37:32 +0200 Subject: [PATCH 30/44] Added code example --- 2025/pydanticai/main.py | 101 +++ 2025/pydanticai/pyproject.toml | 10 + 2025/pydanticai/uv.lock | 1153 ++++++++++++++++++++++++++++++++ 3 files changed, 1264 insertions(+) create mode 100644 2025/pydanticai/main.py create mode 100644 2025/pydanticai/pyproject.toml create mode 100644 2025/pydanticai/uv.lock diff --git a/2025/pydanticai/main.py b/2025/pydanticai/main.py new file mode 100644 index 0000000..8e8bd92 --- /dev/null +++ b/2025/pydanticai/main.py @@ -0,0 +1,101 @@ +from dataclasses import dataclass +from typing import Any +import asyncio + +from pydantic import BaseModel, Field +from pydantic_ai import Agent, RunContext + +from dotenv import load_dotenv + + +# Load environment variables from .env +load_dotenv() + + +# Mock database +@dataclass +class Patient: + id: int + name: str + vitals: dict[str, Any] + +PATIENT_DB = { + 42: Patient(id=42, name="John Doe", vitals={"heart_rate": 72, "blood_pressure": "120/80"}), + 43: Patient(id=43, name="Jane Smith", vitals={"heart_rate": 65, "blood_pressure": "110/70"}), +} + +class DatabaseConn: + async def patient_name(self, id: int) -> str: + patient = PATIENT_DB.get(id) + return patient.name if patient else "Unknown Patient" + + async def latest_vitals(self, id: int) -> dict[str, Any]: + patient = PATIENT_DB.get(id) + return patient.vitals if patient else {"heart_rate": 0, "blood_pressure": "N/A"} + + +@dataclass +class TriageDependencies: + patient_id: int + db: DatabaseConn + + +class TriageOutput(BaseModel): + response_text: str = Field(description="Message to the patient") + escalate: bool = Field(description="Should escalate to a human nurse") + urgency: int = Field(description="Urgency level from 0 to 10", ge=0, le=10) + + +triage_agent = Agent( + "openai:gpt-4o", + deps_type=TriageDependencies, + output_type=TriageOutput, + system_prompt=( + "You are a triage assistant helping patients. " + "Provide clear advice and assess urgency." + ), +) + + +@triage_agent.system_prompt +async def add_patient_name(ctx: RunContext[TriageDependencies]) -> str: + patient_name = await ctx.deps.db.patient_name(id=ctx.deps.patient_id) + return f"The patient's name is {patient_name!r}." + + +@triage_agent.tool +async def latest_vitals(ctx: RunContext[TriageDependencies]) -> dict[str, Any]: + """Returns the patient's latest vital signs.""" + return await ctx.deps.db.latest_vitals(id=ctx.deps.patient_id) + + +async def main() -> None: + deps = TriageDependencies(patient_id=43, db=DatabaseConn()) + + result = await triage_agent.run( + "I have chest pain and trouble breathing.", + deps=deps, + ) + print(result.output) + """ + Example output: + response_text='Your symptoms are serious. Please call emergency services immediately. A nurse will contact you shortly.' + escalate=True + urgency=10 + """ + + result = await triage_agent.run( + "I have a mild headache since yesterday.", + deps=deps, + ) + print(result.output) + """ + Example output: + response_text='It sounds like your headache is not severe, but monitor it closely. If it worsens or you develop new symptoms, contact your doctor.' + escalate=False + urgency=3 + """ + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/2025/pydanticai/pyproject.toml b/2025/pydanticai/pyproject.toml new file mode 100644 index 0000000..3874920 --- /dev/null +++ b/2025/pydanticai/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "pydanticai" +version = "0.1.0" +description = "Pydantic AI tutorial" +requires-python = ">=3.13" +dependencies = [ + "pydantic>=2.11.7", + "pydantic-ai>=0.4.2", + "python-dotenv>=1.1.1", +] diff --git a/2025/pydanticai/uv.lock b/2025/pydanticai/uv.lock new file mode 100644 index 0000000..17df9ef --- /dev/null +++ b/2025/pydanticai/uv.lock @@ -0,0 +1,1153 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.57.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/75/6261a1a8d92aed47e27d2fcfb3a411af73b1435e6ae1186da02b760565d0/anthropic-0.57.1.tar.gz", hash = "sha256:7815dd92245a70d21f65f356f33fc80c5072eada87fb49437767ea2918b2c4b0", size = 423775, upload-time = "2025-07-03T16:57:35.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/cf/ca0ba77805aec6171629a8b665c7dc224dab374539c3d27005b5d8c100a0/anthropic-0.57.1-py3-none-any.whl", hash = "sha256:33afc1f395af207d07ff1bffc0a3d1caac53c371793792569c5d2f09283ea306", size = 292779, upload-time = "2025-07-03T16:57:34.636Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "boto3" +version = "1.39.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/1f/b7510dcd26eb14735d6f4b2904e219b825660425a0cf0b6f35b84c7249b0/boto3-1.39.4.tar.gz", hash = "sha256:6c955729a1d70181bc8368e02a7d3f350884290def63815ebca8408ee6d47571", size = 111829, upload-time = "2025-07-09T19:23:01.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/5c/93292e4d8c809950c13950b3168e0eabdac828629c21047959251ad3f28c/boto3-1.39.4-py3-none-any.whl", hash = "sha256:f8e9534b429121aa5c5b7c685c6a94dd33edf14f87926e9a182d5b50220ba284", size = 139908, upload-time = "2025-07-09T19:22:59.808Z" }, +] + +[[package]] +name = "botocore" +version = "1.39.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/9f/21c823ea2fae3fa5a6c9e8caaa1f858acd55018e6d317505a4f14c5bb999/botocore-1.39.4.tar.gz", hash = "sha256:e662ac35c681f7942a93f2ec7b4cde8f8b56dd399da47a79fa3e370338521a56", size = 14136116, upload-time = "2025-07-09T19:22:49.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/44/f120319e0a9afface645e99f300175b9b308e4724cb400b32e1bd6eb3060/botocore-1.39.4-py3-none-any.whl", hash = "sha256:c41e167ce01cfd1973c3fa9856ef5244a51ddf9c82cb131120d8617913b6812a", size = 13795516, upload-time = "2025-07-09T19:22:44.446Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cohere" +version = "5.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "tokenizers" }, + { name = "types-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/c7/fd1e4c61cf3f0aac9d9d73fce63a766c9778e1270f7a26812eb289b4851d/cohere-5.16.1.tar.gz", hash = "sha256:02aa87668689ad0fbac2cda979c190310afdb99fb132552e8848fdd0aff7cd40", size = 162300, upload-time = "2025-07-09T20:47:36.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/c6/72309ac75f3567425ca31a601ad394bfee8d0f4a1569dfbc80cbb2890d07/cohere-5.16.1-py3-none-any.whl", hash = "sha256:37e2c1d69b1804071b5e5f5cb44f8b74127e318376e234572d021a1a729c6baa", size = 291894, upload-time = "2025-07-09T20:47:34.919Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + +[[package]] +name = "fastavro" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/8f/32664a3245247b13702d13d2657ea534daf64e58a3f72a3a2d10598d6916/fastavro-1.11.1.tar.gz", hash = "sha256:bf6acde5ee633a29fb8dfd6dfea13b164722bc3adc05a0e055df080549c1c2f8", size = 1016250, upload-time = "2025-05-18T04:54:31.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/08/8e25b9e87a98f8c96b25e64565fa1a1208c0095bb6a84a5c8a4b925688a5/fastavro-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f963b8ddaf179660e814ab420850c1b4ea33e2ad2de8011549d958b21f77f20a", size = 931520, upload-time = "2025-05-18T04:55:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/02/ee/7cf5561ef94781ed6942cee6b394a5e698080f4247f00f158ee396ec244d/fastavro-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0253e5b6a3c9b62fae9fc3abd8184c5b64a833322b6af7d666d3db266ad879b5", size = 3195989, upload-time = "2025-05-18T04:55:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/f02f097d79f090e5c5aca8a743010c4e833a257c0efdeb289c68294f7928/fastavro-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca637b150e1f4c0e8e564fad40a16bd922bcb7ffd1a6e4836e6084f2c4f4e8db", size = 3239755, upload-time = "2025-05-18T04:55:16.463Z" }, + { url = "https://files.pythonhosted.org/packages/09/4c/46626b4ee4eb8eb5aa7835973c6ba8890cf082ef2daface6071e788d2992/fastavro-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76af1709031621828ca6ce7f027f7711fa33ac23e8269e7a5733996ff8d318da", size = 3243788, upload-time = "2025-05-18T04:55:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6f/8ed42524e9e8dc0554f0f211dd1c6c7a9dde83b95388ddcf7c137e70796f/fastavro-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8224e6d8d9864d4e55dafbe88920d6a1b8c19cc3006acfac6aa4f494a6af3450", size = 3378330, upload-time = "2025-05-18T04:55:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/b8/51/38cbe243d5facccab40fc43a4c17db264c261be955ce003803d25f0da2c3/fastavro-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:cde7ed91b52ff21f0f9f157329760ba7251508ca3e9618af3ffdac986d9faaa2", size = 443115, upload-time = "2025-05-18T04:55:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/d0/57/0d31ed1a49c65ad9f0f0128d9a928972878017781f9d4336f5f60982334c/fastavro-1.11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e5ed1325c1c414dd954e7a2c5074daefe1eceb672b8c727aa030ba327aa00693", size = 1021401, upload-time = "2025-05-18T04:55:23.431Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/a3f1a75fbfc16b3eff65dc0efcdb92364967923194312b3f8c8fc2cb95be/fastavro-1.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd3c95baeec37188899824faf44a5ee94dfc4d8667b05b2f867070c7eb174c4", size = 3384349, upload-time = "2025-05-18T04:55:25.575Z" }, + { url = "https://files.pythonhosted.org/packages/be/84/02bceb7518867df84027232a75225db758b9b45f12017c9743f45b73101e/fastavro-1.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e0babcd81acceb4c60110af9efa25d890dbb68f7de880f806dadeb1e70fe413", size = 3240658, upload-time = "2025-05-18T04:55:27.633Z" }, + { url = "https://files.pythonhosted.org/packages/f2/17/508c846c644d39bc432b027112068b8e96e7560468304d4c0757539dd73a/fastavro-1.11.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c0cb8063c7208b53b6867983dc6ae7cc80b91116b51d435d2610a5db2fc52f", size = 3372809, upload-time = "2025-05-18T04:55:30.063Z" }, + { url = "https://files.pythonhosted.org/packages/fe/84/9c2917a70ed570ddbfd1d32ac23200c1d011e36c332e59950d2f6d204941/fastavro-1.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1bc2824e9969c04ab6263d269a1e0e5d40b9bd16ade6b70c29d6ffbc4f3cc102", size = 3387171, upload-time = "2025-05-18T04:55:32.531Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-genai" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/59/c9b9148c8702b60253f5a251f16ae436534c5d4362da193c9db05ac9858c/google_genai-1.25.0.tar.gz", hash = "sha256:a08a79c819a5d949d9948cd372e36e512bf85cd28158994daaa36d0ec4cb2b02", size = 228141, upload-time = "2025-07-09T20:53:47.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/ec/149f3d49b56cf848142071772aabb1c290b535bd9b5327a5dfccf1d00332/google_genai-1.25.0-py3-none-any.whl", hash = "sha256:fb5cee79b9a0a1b2afd5cfdf279099ecebd186551eefcaa6ec0c6016244e6138", size = 226847, upload-time = "2025-07-09T20:53:46.532Z" }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137, upload-time = "2025-04-23T11:29:09.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303, upload-time = "2025-04-23T11:29:07.145Z" }, +] + +[[package]] +name = "groq" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b1/72ca20dc9b977b7f604648e8944c77b267bddeb90d8e16bda0cf0e397844/groq-0.30.0.tar.gz", hash = "sha256:919466e48fcbebef08fed3f71debb0f96b0ea8d2ec77842c384aa843019f6e2c", size = 134928, upload-time = "2025-07-11T20:28:36.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/b8/5b90edf9fbd795597220e3d1b5534d845e69a73ffe1fdeb967443ed2a6cf/groq-0.30.0-py3-none-any.whl", hash = "sha256:6d9609a7778ba56432f45c1bac21b005f02c6c0aca9c1c094e65536f162c1e83", size = 131056, upload-time = "2025-07-11T20:28:35.591Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, + { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.33.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/9e/9366b7349fc125dd68b9d384a0fea84d67b7497753fe92c71b67e13f47c4/huggingface_hub-0.33.4.tar.gz", hash = "sha256:6af13478deae120e765bfd92adad0ae1aec1ad8c439b46f23058ad5956cbca0a", size = 426674, upload-time = "2025-07-11T12:32:48.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/7b/98daa50a2db034cab6cd23a3de04fa2358cb691593d28e9130203eb7a805/huggingface_hub-0.33.4-py3-none-any.whl", hash = "sha256:09f9f4e7ca62547c70f8b82767eefadd2667f4e116acba2e3e62a5a81815a7bb", size = 515339, upload-time = "2025-07-11T12:32:46.346Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "jiter" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, + { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, + { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, + { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, + { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" }, + { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" }, + { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" }, + { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" }, + { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "logfire-api" +version = "3.24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9c/48de4d1869a3266d5d333fa699020b6197efe4c1b214a6411874c39aa708/logfire_api-3.24.2.tar.gz", hash = "sha256:13c913916d1f627b7155c898eac232bf60d9b927706b8fae05cbaf803ec1e9e5", size = 50767, upload-time = "2025-07-14T16:04:34.004Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/45/37fb3a69ab24ede522304bad4d04dff94fbeb606df2b7a63062f634ee9b2/logfire_api-3.24.2-py3-none-any.whl", hash = "sha256:855fdffd9f1f20d054ac12064a773e0fd80f706dfcced8775a429c05dfade89a", size = 85381, upload-time = "2025-07-14T16:04:30.838Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mcp" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/f5/9506eb5578d5bbe9819ee8ba3198d0ad0e2fbe3bab8b257e4131ceb7dfb6/mcp-1.11.0.tar.gz", hash = "sha256:49a213df56bb9472ff83b3132a4825f5c8f5b120a90246f08b0dac6bedac44c8", size = 406907, upload-time = "2025-07-10T16:41:09.388Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9c/c9ca79f9c512e4113a5d07043013110bb3369fc7770040c61378c7fbcf70/mcp-1.11.0-py3-none-any.whl", hash = "sha256:58deac37f7483e4b338524b98bc949b7c2b7c33d978f5fafab5bde041c5e2595", size = 155880, upload-time = "2025-07-10T16:41:07.935Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mistralai" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e7/204a54d07c37ebf173590af85bf46cddf8bc343b9d6005804581967b4751/mistralai-1.9.2.tar.gz", hash = "sha256:c0c6d5aff18ffccbc0d22c06fbc84280d71eeaeb08fa4e1ef7326b36629cfb0b", size = 192678, upload-time = "2025-07-10T13:07:08.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/eb/f746a3f977d3c0059e4afa19d26b1293f54c6258fcf841957e584be6927f/mistralai-1.9.2-py3-none-any.whl", hash = "sha256:7c3fff00e50227d379dea82052455c2610612a8ef476fa97393191aeeb7ab15f", size = 411581, upload-time = "2025-07-10T13:07:07.226Z" }, +] + +[[package]] +name = "openai" +version = "1.95.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/a3/70cd57c7d71086c532ce90de5fdef4165dc6ae9dbf346da6737ff9ebafaa/openai-1.95.1.tar.gz", hash = "sha256:f089b605282e2a2b6776090b4b46563ac1da77f56402a222597d591e2dcc1086", size = 488271, upload-time = "2025-07-11T20:47:24.437Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/1d/0432ea635097f4dbb34641a3650803d8a4aa29d06bafc66583bf1adcceb4/openai-1.95.1-py3-none-any.whl", hash = "sha256:8bbdfeceef231b1ddfabbc232b179d79f8b849aab5a7da131178f8d10e0f162f", size = 755613, upload-time = "2025-07-11T20:47:22.629Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/c9/4509bfca6bb43220ce7f863c9f791e0d5001c2ec2b5867d48586008b3d96/opentelemetry_api-1.35.0.tar.gz", hash = "sha256:a111b959bcfa5b4d7dffc2fbd6a241aa72dd78dd8e79b5b1662bda896c5d2ffe", size = 64778, upload-time = "2025-07-11T12:23:28.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-ai" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "mcp", "mistral", "openai", "vertexai"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/c4/697c3f35d539f244d9cf0535d835547151eb0cde9ba74c87db3ee46a92df/pydantic_ai-0.4.2.tar.gz", hash = "sha256:3db0461ff9e5b383f37db1bf4645ccc3a82d3391bef0d42ed78a16930d7b9a71", size = 41347323, upload-time = "2025-07-10T18:53:46.839Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/02/eccaeb0f04b00cd19f9b6d0ba52071e6f864c7bd12aa5f00624456e36117/pydantic_ai-0.4.2-py3-none-any.whl", hash = "sha256:2335aed33b84769666087aa85852c191ae0cdbd47b13765bc5a2d1370b5a5d73", size = 10139, upload-time = "2025-07-10T18:53:35.216Z" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/27/463ed1dc0d02dc9984c74eb8fe423dd33fef6f4c93ce66f33630f0f71ebf/pydantic_ai_slim-0.4.2.tar.gz", hash = "sha256:31430bbe61bc1c3a7f212eb99b50cf18399c7842d4a51c3ffc1ea973f644a99b", size = 165984, upload-time = "2025-07-10T18:53:50.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/6b/08c23ac8fcfa8ed2ccf564c51828e5f612e3e20cc86313e36e9b7ec48a41/pydantic_ai_slim-0.4.2-py3-none-any.whl", hash = "sha256:1dbbf31066b68b9e3cbb391e62114b620f02736607b90e64bccc3aa0e8f30475", size = 218913, upload-time = "2025-07-10T18:53:40.213Z" }, +] + +[package.optional-dependencies] +anthropic = [ + { name = "anthropic" }, +] +bedrock = [ + { name = "boto3" }, +] +cli = [ + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "rich" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-evals" }, +] +google = [ + { name = "google-genai" }, +] +groq = [ + { name = "groq" }, +] +mcp = [ + { name = "mcp" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-evals" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-ai-slim" }, + { name = "pyyaml" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/76/f71365828e36a18e12da940d52ca52f883cffde64cbb92aa8de1603bd2a0/pydantic_evals-0.4.2.tar.gz", hash = "sha256:f2f48a47548bbfd5a3a86c54b22a37f4d633d161a19e0948e2c5c2319d486c8f", size = 43281, upload-time = "2025-07-10T18:53:52.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/d3/f00d959b7fd56c7abe95ec195fab791de4c5b6c236f36affc78a4eb9072a/pydantic_evals-0.4.2-py3-none-any.whl", hash = "sha256:1486fc25d616928763c0a7a07c94f6a3ead8e922ac746494a13a799e87d3d473", size = 52023, upload-time = "2025-07-10T18:53:42.023Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/31/fb922d6c0cc19ad4da3cf6ecfc8f763c6b10d86f71ac7590e4b7d5e7c8fb/pydantic_graph-0.4.2.tar.gz", hash = "sha256:81339ff4e376b6149a4d548052f76019d0b969288f19d8dd4963ba854a18201c", size = 21868, upload-time = "2025-07-10T18:53:52.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/70/444c5311cdcc7d6f16c0921fee0bb82c54717f7d7cc600c14d72aeac9cdf/pydantic_graph-0.4.2-py3-none-any.whl", hash = "sha256:6a89fa4a8472c468e39843ad9ce9eaef79cdc8318e6bac868baff2bc7adf09b2", size = 27494, upload-time = "2025-07-10T18:53:43.795Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pydanticai" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pydantic" }, + { name = "pydantic-ai" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "pydantic-ai", specifier = ">=0.4.2" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, + { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" }, + { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" }, + { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" }, + { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" }, + { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" }, + { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" }, + { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, + { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, + { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, + { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635, upload-time = "2025-07-06T09:41:33.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824, upload-time = "2025-07-06T09:41:32.321Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1791eb329c07122a75b01035b1a3aa22ad139f3ce0ece1b059b506d9d9de/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12", size = 3019202, upload-time = "2025-06-24T10:24:31.791Z" }, + { url = "https://files.pythonhosted.org/packages/05/15/fd2d8104faa9f86ac68748e6f7ece0b5eb7983c7efc3a2c197cb98c99030/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91", size = 2934539, upload-time = "2025-06-24T10:24:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2e/53e8fd053e1f3ffbe579ca5f9546f35ac67cf0039ed357ad7ec57f5f5af0/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb", size = 3248665, upload-time = "2025-06-24T10:24:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/79713359f4037aa8f4d1f06ffca35312ac83629da062670e8830917e2153/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab", size = 3451305, upload-time = "2025-06-24T10:24:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/38/5f/959f3a8756fc9396aeb704292777b84f02a5c6f25c3fc3ba7530db5feb2c/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae", size = 3214757, upload-time = "2025-06-24T10:24:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/c5/74/f41a432a0733f61f3d21b288de6dfa78f7acff309c6f0f323b2833e9189f/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020", size = 3121887, upload-time = "2025-06-24T10:24:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6a/bc220a11a17e5d07b0dfb3b5c628621d4dcc084bccd27cfaead659963016/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19", size = 9091965, upload-time = "2025-06-24T10:24:44.431Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/ac386d79c4ef20dc6f39c4706640c24823dca7ebb6f703bfe6b5f0292d88/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d", size = 9053372, upload-time = "2025-06-24T10:24:46.455Z" }, + { url = "https://files.pythonhosted.org/packages/63/7b/5440bf203b2a5358f074408f7f9c42884849cd9972879e10ee6b7a8c3b3d/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365", size = 9298632, upload-time = "2025-06-24T10:24:48.446Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d2/faa1acac3f96a7427866e94ed4289949b2524f0c1878512516567d80563c/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958", size = 9470074, upload-time = "2025-06-24T10:24:50.378Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/896e1ef0707212745ae9f37e84c7d50269411aef2e9ccd0de63623feecdf/tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962", size = 2330115, upload-time = "2025-06-24T10:24:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload-time = "2025-06-24T10:24:53.71Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From 106f95736c0544416539a91dc6c2a9fe439bc62c Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 17 Jul 2025 16:08:10 +0200 Subject: [PATCH 31/44] Minor cleanup of code examples. --- 2025/standard/01_dataclasses.py | 2 +- 2025/standard/02_pathlib.py | 22 ++++++++++++++-------- 2025/standard/03_functools.py | 7 +++++-- 2025/standard/04_tomllib.py | 13 ++++++++++--- 2025/standard/05_graphlib.py | 4 +++- 2025/standard/08_shutil.py | 6 +++--- 2025/standard/10_itertools.py | 5 +++-- 7 files changed, 39 insertions(+), 20 deletions(-) diff --git a/2025/standard/01_dataclasses.py b/2025/standard/01_dataclasses.py index 3385387..4ca9058 100644 --- a/2025/standard/01_dataclasses.py +++ b/2025/standard/01_dataclasses.py @@ -8,7 +8,7 @@ class Product: in_stock: bool = True -def main(): +def main() -> None: product = Product(name="Widget", price=19.99) print(product) diff --git a/2025/standard/02_pathlib.py b/2025/standard/02_pathlib.py index a50b232..ace4ee1 100644 --- a/2025/standard/02_pathlib.py +++ b/2025/standard/02_pathlib.py @@ -1,12 +1,18 @@ from pathlib import Path -base = Path("my_project") -config = base / "config" / "settings.toml" -print("Config path:", config) +def main() -> None: + base = Path("my_project") + config = base / "config" / "settings.toml" -if config.exists(): - print("File size:", config.stat().st_size) -else: - config.parent.mkdir(parents=True, exist_ok=True) - config.write_text("[settings]\nname = 'Example'") \ No newline at end of file + print("Config path:", config) + + if config.exists(): + print("File size:", config.stat().st_size) + else: + config.parent.mkdir(parents=True, exist_ok=True) + config.write_text("[settings]\nname = 'Example'") + + +if __name__ == "__main__": + main() diff --git a/2025/standard/03_functools.py b/2025/standard/03_functools.py index e094275..9e06259 100644 --- a/2025/standard/03_functools.py +++ b/2025/standard/03_functools.py @@ -1,9 +1,11 @@ from functools import cache, partial + @cache def power(base: int, exponent: int) -> int: print(f"Computing {base}^{exponent}") - return base ** exponent + return base**exponent + def main() -> None: # cached power function @@ -19,5 +21,6 @@ def main() -> None: print("Cube of 2:", cube(2)) print("Square of 5 again (cached):", square(5)) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/standard/04_tomllib.py b/2025/standard/04_tomllib.py index 921c68d..9abbc73 100644 --- a/2025/standard/04_tomllib.py +++ b/2025/standard/04_tomllib.py @@ -1,6 +1,13 @@ import tomllib -with open("pyproject.toml", "rb") as f: - data = tomllib.load(f) -print(data["project"]["name"]) \ No newline at end of file +def main() -> None: + # Load a TOML file + with open("pyproject.toml", "rb") as f: + data = tomllib.load(f) + + print(data["project"]["name"]) + + +if __name__ == "__main__": + main() diff --git a/2025/standard/05_graphlib.py b/2025/standard/05_graphlib.py index b8a277a..370461e 100644 --- a/2025/standard/05_graphlib.py +++ b/2025/standard/05_graphlib.py @@ -2,7 +2,8 @@ ts = TopologicalSorter[str]() -def main(): + +def main() -> None: # Tasks and their dependencies ts.add("compile", "fetch_sources") ts.add("test", "compile") @@ -13,5 +14,6 @@ def main(): order = list(ts.static_order()) print("Execution order:", order) + if __name__ == "__main__": main() diff --git a/2025/standard/08_shutil.py b/2025/standard/08_shutil.py index 0140e7c..5851bb4 100644 --- a/2025/standard/08_shutil.py +++ b/2025/standard/08_shutil.py @@ -1,13 +1,13 @@ -from py_compile import main import shutil -def main() -> None: +def main() -> None: # Copy a file shutil.copy("pyproject.toml", "backup_pyproject.toml") # Create a zip archive shutil.make_archive("project_backup", "zip", ".") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/standard/10_itertools.py b/2025/standard/10_itertools.py index 92375ff..0a8b40e 100644 --- a/2025/standard/10_itertools.py +++ b/2025/standard/10_itertools.py @@ -1,5 +1,5 @@ import itertools -from collections.abc import Iterable + def main() -> None: items = ["a", "b", "c"] @@ -28,5 +28,6 @@ def main() -> None: group_list = list(group) print(f"{key}: {group_list}") + if __name__ == "__main__": - main() \ No newline at end of file + main() From 65c1ce693278540215be319206ca1633d66abd74 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 22 Jul 2025 16:09:57 +0200 Subject: [PATCH 32/44] Updated testtips code example --- 2025/testtips/tests/test_tips.py | 67 +++++++---------- 2025/testtips/tests/test_weather.py | 78 -------------------- 2025/testtips/tests/test_weather_fixture.py | 55 +++----------- 2025/testtips/tests/test_weather_mock.py | 34 +++++++++ 2025/testtips/tests/test_weather_mock_mp.py | 41 ---------- 2025/testtips/tests/test_weather_patch.py | 16 ++++ 2025/testtips/tests/test_weather_refactor.py | 28 ++++--- 2025/testtips/weather.py | 7 +- 2025/testtips/weather_refactor.py | 5 +- 9 files changed, 114 insertions(+), 217 deletions(-) delete mode 100644 2025/testtips/tests/test_weather.py create mode 100644 2025/testtips/tests/test_weather_mock.py delete mode 100644 2025/testtips/tests/test_weather_mock_mp.py create mode 100644 2025/testtips/tests/test_weather_patch.py diff --git a/2025/testtips/tests/test_tips.py b/2025/testtips/tests/test_tips.py index 2d93863..3cffefe 100644 --- a/2025/testtips/tests/test_tips.py +++ b/2025/testtips/tests/test_tips.py @@ -1,73 +1,62 @@ -# tests/test_weather.py - import sys +from typing import Any import pytest -from weather import WeatherService -# from hypothesis import given -# from hypothesis.strategies import floats +from weather import WeatherService -# ✅ Parametrization @pytest.mark.parametrize( - "city,expected_temp", [("London", 15), ("Berlin", 20), ("Paris", 17)] + "city,expected_temp", + [ + ("London", 15), + ("Berlin", 20), + ("Rome", 18), + ], ) -def test_get_temperature_multiple_cities(monkeypatch, city, expected_temp): - def fake_get(url, params): +def test_parametrized_temperatures( + monkeypatch: pytest.MonkeyPatch, city: str, expected_temp: float +) -> None: + def fake_get(url: str, params: dict[str, Any]) -> Any: class FakeResponse: - def raise_for_status(self): + def raise_for_status(self) -> None: pass - def json(self): + def json(self) -> dict[str, Any]: return {"current": {"temp_c": expected_temp}} return FakeResponse() - monkeypatch.setattr("weather.httpx.get", fake_get) - + monkeypatch.setattr("httpx.get", fake_get) service = WeatherService(api_key="fake-key") - temp = service.get_temperature(city) + assert service.get_temperature(city) == expected_temp - assert temp == expected_temp - -# ✅ pytest.raises -def test_get_temperature_raises_http_error(monkeypatch): - def fake_get(url, params): +def test_temperature_raises_error(monkeypatch: pytest.MonkeyPatch) -> None: + def fake_get(url: str, params: dict[str, Any]) -> Any: class FakeResponse: - def raise_for_status(self): + def raise_for_status(self) -> None: raise Exception("API error") return FakeResponse() - monkeypatch.setattr("weather.httpx.get", fake_get) - + monkeypatch.setattr("httpx.get", fake_get) service = WeatherService(api_key="fake-key") with pytest.raises(Exception): - service.get_temperature("Tokyo") + service.get_temperature("Oslo") -# ✅ pytest.mark.skip -@pytest.mark.skip(reason="Skipping this test for demonstration purposes.") -def test_skipped_example(): +@pytest.mark.skip(reason="Temporarily skipping for demo purposes") +def test_skipped() -> None: assert False -# ✅ pytest.mark.skipif -@pytest.mark.skipif(sys.platform == "win32", reason="Does not run on Windows") -def test_only_runs_on_non_windows(): +@pytest.mark.skipif(sys.platform == "win32", reason="Fails on Windows") +def test_non_windows_behavior() -> None: assert True -# ✅ pytest.mark.xfail -@pytest.mark.xfail(reason="Known bug: API sometimes returns wrong temperature") -def test_expected_failure_example(): - assert 2 + 2 == 5 - - -# ✅ Hypothesis property-based test -# @given(floats(min_value=-50, max_value=50)) -# def test_temperature_range_property(temp): -# assert -50 <= temp <= 50 +@pytest.mark.xfail(reason="Intentional failure due to API bug") +def test_expected_failure() -> None: + assert 1 + 1 == 3 diff --git a/2025/testtips/tests/test_weather.py b/2025/testtips/tests/test_weather.py deleted file mode 100644 index 542a624..0000000 --- a/2025/testtips/tests/test_weather.py +++ /dev/null @@ -1,78 +0,0 @@ -import httpx -import pytest -from weather import WeatherService - - -# Tip 1: Single assert per test -def test_get_temperature_returns_expected_value(monkeypatch): - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - pass - - def json(self): - return {"current": {"temp_c": 22}} - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - service = WeatherService(api_key="fake-key") - temp = service.get_temperature("Amsterdam") - - assert temp == 22 - - -# Tip 2: Clear and descriptive names -def test_get_temperature_for_different_city(monkeypatch): - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - pass - - def json(self): - return {"current": {"temp_c": 18}} - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - service = WeatherService(api_key="fake-key") - temp = service.get_temperature("Berlin") - - assert temp == 18 - - -def test_get_temperature_handles_api_error(monkeypatch): - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - raise httpx.HTTPError("API error") - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - service = WeatherService(api_key="fake-key") - - with pytest.raises(httpx.HTTPError): - service.get_temperature("Paris") - - -def test_get_temperature_returns_float(monkeypatch): - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - pass - - def json(self): - return {"current": {"temp_c": 19.5}} - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - service = WeatherService(api_key="fake-key") - temp = service.get_temperature("Rome") - - assert isinstance(temp, float) diff --git a/2025/testtips/tests/test_weather_fixture.py b/2025/testtips/tests/test_weather_fixture.py index 5d88dd2..31c9172 100644 --- a/2025/testtips/tests/test_weather_fixture.py +++ b/2025/testtips/tests/test_weather_fixture.py @@ -1,50 +1,19 @@ import pytest from weather import WeatherService +from typing import Any +from unittest.mock import MagicMock - -# This fixture creates a WeatherService instance you can reuse @pytest.fixture -def weather_service(): +def weather_service(monkeypatch: pytest.MonkeyPatch) -> WeatherService: + def fake_get(url: str, params: dict[str, Any]) -> Any: + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"current": {"temp_c": 25}} + return mock_response + + monkeypatch.setattr("httpx.get", fake_get) return WeatherService(api_key="fake-key") +def test_fixture_usage(weather_service: WeatherService) -> None: + assert weather_service.get_temperature("Paris") == 25 -def test_get_temperature_returns_expected_value(weather_service, monkeypatch): - """ - Test that get_temperature returns the correct temperature. - """ - - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - pass - - def json(self): - return {"current": {"temp_c": 20}} - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - temp = weather_service.get_temperature("Amsterdam") - assert temp == 20 - - -def test_get_temperature_returns_float(weather_service, monkeypatch): - """ - Test that get_temperature returns a float value. - """ - - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): - pass - - def json(self): - return {"current": {"temp_c": 18.5}} - - return FakeResponse() - - monkeypatch.setattr("weather.httpx.get", fake_get) - - temp = weather_service.get_temperature("Berlin") - assert isinstance(temp, float) diff --git a/2025/testtips/tests/test_weather_mock.py b/2025/testtips/tests/test_weather_mock.py new file mode 100644 index 0000000..44abc7d --- /dev/null +++ b/2025/testtips/tests/test_weather_mock.py @@ -0,0 +1,34 @@ +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from weather import WeatherService + + +def test_get_temperature_with_mocking_monkeypatch( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def fake_get(url: str, params: dict[str, Any]) -> Any: + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"current": {"temp_c": 25}} + return mock_response + + monkeypatch.setattr("httpx.get", fake_get) + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Amsterdam") + assert temp == 25 + + +def test_get_temperature_with_mocking() -> None: + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"current": {"temp_c": 25}} + + with patch("httpx.get", return_value=mock_response) as mock_get: + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("London") + + assert temp == 25 + mock_get.assert_called_once() diff --git a/2025/testtips/tests/test_weather_mock_mp.py b/2025/testtips/tests/test_weather_mock_mp.py deleted file mode 100644 index b28340a..0000000 --- a/2025/testtips/tests/test_weather_mock_mp.py +++ /dev/null @@ -1,41 +0,0 @@ -from weather import WeatherService -from unittest.mock import patch, MagicMock - -# Section 4: Mocking with patch + MagicMock -def test_get_temperature_with_mocking(): - """ - Example of mocking httpx.get with a MagicMock. - """ - # Create a MagicMock response object - mock_response = MagicMock() - mock_response.raise_for_status.return_value = None - mock_response.json.return_value = {"current": {"temp_c": 25}} - - # Patch httpx.get so it returns our mock_response - with patch("weather.httpx.get", return_value=mock_response) as mock_get: - service = WeatherService(api_key="fake-key") - temp = service.get_temperature("Paris") - - assert temp == 25 - mock_get.assert_called_once() - - -# Section 5: Monkey patching with monkeypatch fixture -def test_get_temperature_with_monkeypatch(monkeypatch): - """ - Example of monkeypatching httpx.get with a manual stub. - """ - - def fake_get(url, params): - class FakeResponse: - def raise_for_status(self): pass - def json(self): return {"current": {"temp_c": 19}} - return FakeResponse() - - # Monkeypatch httpx.get to use fake_get - monkeypatch.setattr("weather.httpx.get", fake_get) - - service = WeatherService(api_key="fake-key") - temp = service.get_temperature("Berlin") - - assert temp == 19 \ No newline at end of file diff --git a/2025/testtips/tests/test_weather_patch.py b/2025/testtips/tests/test_weather_patch.py new file mode 100644 index 0000000..2e8aab8 --- /dev/null +++ b/2025/testtips/tests/test_weather_patch.py @@ -0,0 +1,16 @@ +from weather import WeatherService +import pytest +from typing import Any + +def test_get_temperature_with_monkeypatch(monkeypatch: pytest.MonkeyPatch) -> None: + def fake_get(url: str, params: dict[str, Any]) -> Any: + class FakeResponse: + def raise_for_status(self) -> None: pass + def json(self) -> dict[str, Any]: + return {"current": {"temp_c": 19}} + return FakeResponse() + + monkeypatch.setattr("httpx.get", fake_get) + service = WeatherService(api_key="fake-key") + temp = service.get_temperature("Amsterdam") + assert temp == 19 \ No newline at end of file diff --git a/2025/testtips/tests/test_weather_refactor.py b/2025/testtips/tests/test_weather_refactor.py index 26c362f..5324580 100644 --- a/2025/testtips/tests/test_weather_refactor.py +++ b/2025/testtips/tests/test_weather_refactor.py @@ -1,12 +1,22 @@ +from unittest.mock import MagicMock + +import pytest + from weather_refactor import WeatherService -def test_get_temperature_with_stub_client(): - class StubClient: - def get(self, url, params): - class Response: - def raise_for_status(self): pass - def json(self): return {"current": {"temp_c": 18}} - return Response() - service = WeatherService(client=StubClient(), api_key="fake_key") - assert service.get_temperature("Oslo") == 18 \ No newline at end of file +@pytest.fixture +def weather_service() -> WeatherService: + mock_http_client = MagicMock() + mock_http_client.get.return_value = MagicMock( + **{ + "raise_for_status": lambda: None, + "json": lambda: {"current": {"temp_c": 17}}, + } + ) + return WeatherService(client=mock_http_client, api_key="fake-key") + + +def test_weather_service_with_mock_http_client(weather_service: WeatherService): + temp = weather_service.get_temperature("Amsterdam") + assert temp == 17 diff --git a/2025/testtips/weather.py b/2025/testtips/weather.py index 5ad9916..1f10f14 100644 --- a/2025/testtips/weather.py +++ b/2025/testtips/weather.py @@ -1,4 +1,3 @@ -# weather_service.py import os import httpx @@ -8,7 +7,7 @@ class WeatherService: - def __init__(self, api_key: str): + def __init__(self, api_key: str) -> None: self.api_key = api_key def get_temperature(self, city: str) -> float: @@ -21,8 +20,8 @@ def get_temperature(self, city: str) -> float: return data["current"]["temp_c"] -def main(): - api_key = os.getenv("WEATHER_API_KEY") +def main() -> None: + api_key = os.getenv("WEATHER_API_KEY", "") weather_service = WeatherService(api_key) temperature = weather_service.get_temperature("Amsterdam") print(f"The current temperature in Amsterdam is {temperature}°C.") diff --git a/2025/testtips/weather_refactor.py b/2025/testtips/weather_refactor.py index 26dc140..95944c8 100644 --- a/2025/testtips/weather_refactor.py +++ b/2025/testtips/weather_refactor.py @@ -1,4 +1,3 @@ -# weather_service.py import os import httpx @@ -8,7 +7,7 @@ class WeatherService: - def __init__(self, client: httpx.Client, api_key: str): + def __init__(self, client: httpx.Client, api_key: str) -> None: self.client = client self.api_key = api_key @@ -22,7 +21,7 @@ def get_temperature(self, city: str) -> float: return data["current"]["temp_c"] -def main(): +def main() -> None: api_key = os.getenv("WEATHER_API_KEY", "") client = httpx.Client() weather_service = WeatherService(client, api_key) From a63283ba90e89f770f3c10306757d2c1b97504a1 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Mon, 28 Jul 2025 16:17:46 +0200 Subject: [PATCH 33/44] Added initial ai design pattern code examples. --- 2025/aidesign/chain_of_responsibility.py | 168 +++++++++++++++++++++++ 2025/aidesign/observer.py | 131 ++++++++++++++++++ 2025/aidesign/pyproject.toml | 11 ++ 2025/aidesign/strategy.py | 117 ++++++++++++++++ 2025/aidesign/uv.lock | 100 ++++++++++++++ 5 files changed, 527 insertions(+) create mode 100644 2025/aidesign/chain_of_responsibility.py create mode 100644 2025/aidesign/observer.py create mode 100644 2025/aidesign/pyproject.toml create mode 100644 2025/aidesign/strategy.py create mode 100644 2025/aidesign/uv.lock diff --git a/2025/aidesign/chain_of_responsibility.py b/2025/aidesign/chain_of_responsibility.py new file mode 100644 index 0000000..b68ba29 --- /dev/null +++ b/2025/aidesign/chain_of_responsibility.py @@ -0,0 +1,168 @@ +import asyncio +import os +from dataclasses import dataclass +from typing import Optional + +from dotenv import load_dotenv +from pydantic import BaseModel +from pydantic_ai import Agent + +# Load env vars +load_dotenv() +os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") + +# ---------------------------------- +# Shared Dependencies +# ---------------------------------- + + +@dataclass +class TravelDeps: + user_name: str + + +# ---------------------------------- +# Shared Data Passed Through the Chain +# ---------------------------------- + + +class TripContext(BaseModel): + destination: Optional[str] = None + from_city: Optional[str] = None + arrival_time: Optional[str] = None + hotel_name: Optional[str] = None + hotel_location: Optional[str] = None + + +# ---------------------------------- +# Step 1: Choose Destination +# ---------------------------------- + + +class DestinationOutput(BaseModel): + destination: str + + +destination_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=DestinationOutput, + system_prompt="You help users select an ideal travel destination based on their preferences.", +) + + +# ---------------------------------- +# Step 2: Plan Flight +# ---------------------------------- + + +class FlightPlan(BaseModel): + from_city: str + to_city: str + arrival_time: str + + +flight_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=FlightPlan, + system_prompt="Plan a realistic flight itinerary for a trip. Include origin city and arrival time.", +) + + +# ---------------------------------- +# Step 3: Recommend Hotel +# ---------------------------------- + + +class HotelOption(BaseModel): + name: str + location: str + price_per_night_usd: int + stars: int + + +hotel_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=HotelOption, + system_prompt="Suggest a good hotel near the arrival airport or city center. Consider time of arrival and convenience.", +) + + +# ---------------------------------- +# Step 4: Suggest Activities +# ---------------------------------- + + +class Activities(BaseModel): + personalized_for: str + top_activities: list[str] + + +activity_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=Activities, + system_prompt="Suggest local activities close to the hotel and suitable for arrival time (e.g., evening, morning).", +) + + +# ---------------------------------- +# Chain Execution Logic +# ---------------------------------- + + +async def plan_trip(user_input: str, deps: TravelDeps): + print(f"\n👤 {deps.user_name} says: {user_input}") + ctx = TripContext() + + # Step 1: Destination + dest_result = await destination_agent.run(prompt=user_input, deps=deps) + ctx.destination = dest_result.output.destination + print(f"📍 Destination: {ctx.destination}") + + # Step 2: Flight + flight_prompt = f"Plan a flight to {ctx.destination}." + flight_result = await flight_agent.run(prompt=flight_prompt, deps=deps) + ctx.from_city = flight_result.output.from_city + ctx.arrival_time = flight_result.output.arrival_time + print( + f"✈️ Flight: from {ctx.from_city} → {ctx.destination}, arriving at {ctx.arrival_time}" + ) + + # Step 3: Hotel + hotel_prompt = ( + f"Recommend a hotel in {ctx.destination} for a traveler arriving at {ctx.arrival_time}. " + f"Prefer locations near the airport or city center." + ) + hotel_result = await hotel_agent.run(prompt=hotel_prompt, deps=deps) + ctx.hotel_name = hotel_result.output.name + ctx.hotel_location = hotel_result.output.location + print( + f"🏨 Hotel: {ctx.hotel_name}, {hotel_result.output.stars}★ at ${hotel_result.output.price_per_night_usd}/night" + ) + + # Step 4: Activities + activities_prompt = ( + f"Suggest activities in {ctx.destination} close to {ctx.hotel_location} " + f"and suitable for a traveler arriving at {ctx.arrival_time}." + ) + activity_result = await activity_agent.run(prompt=activities_prompt, deps=deps) + print(f"🎯 Activities for {activity_result.output.personalized_for}:") + for a in activity_result.output.top_activities: + print(f" - {a}") + + +# ---------------------------------- +# Main Execution +# ---------------------------------- + + +async def main(): + deps = TravelDeps(user_name="Maria") + await plan_trip("I want a quiet, sunny destination near the ocean.", deps) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/2025/aidesign/observer.py b/2025/aidesign/observer.py new file mode 100644 index 0000000..590ead0 --- /dev/null +++ b/2025/aidesign/observer.py @@ -0,0 +1,131 @@ +import asyncio +import os +from dataclasses import dataclass + +from dotenv import load_dotenv +from pydantic import BaseModel, Field +from pydantic_ai import Agent + +# Load API key +load_dotenv() +os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") + +# ------------------------------- +# Dependencies +# ------------------------------- + + +@dataclass +class TravelDeps: + user_name: str + + +# ------------------------------- +# Main Response Output +# ------------------------------- + + +class TravelResponse(BaseModel): + message: str = Field(..., description="Response to the user") + destination: str = Field(..., description="Suggested destination") + + +# ------------------------------- +# Structured Log Entry +# ------------------------------- + + +class LogEntry(BaseModel): + level: str = Field(..., description="Log level: info, warning, error, etc.") + message: str = Field(..., description="What the agent did") + source: str = Field( + ..., description="The part of the system that generated the log" + ) + + +# ------------------------------- +# Agent: Travel Recommender +# ------------------------------- + +travel_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=TravelResponse, + system_prompt="You are a helpful travel assistant. Recommend a good destination and respond politely.", +) + + +# ------------------------------- +# Log Agent: Parallel Output +# ------------------------------- + +log_agent = Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=LogEntry, + system_prompt="Log what the travel agent just did in a structured format.", +) + + +# ------------------------------- +# Observer Interface +# ------------------------------- + + +class Observer: + def notify(self, log: LogEntry): + pass + + +# ------------------------------- +# Example Concrete Observer +# ------------------------------- + + +class ConsoleLogger(Observer): + def notify(self, log: LogEntry): + print(f"[{log.level.upper()}] from {log.source}: {log.message}") + + +# ------------------------------- +# Execution Function +# ------------------------------- + + +async def recommend_travel( + user_prompt: str, deps: TravelDeps, observers: list[Observer] +): + # Step 1: Get the response from the agent + response_result = await travel_agent.run(user_prompt, deps=deps) + response = response_result.output + + # Step 2: Generate structured log from a separate agent + log_prompt = ( + f"The agent suggested {response.destination} in response to a user prompt." + ) + log_result = await log_agent.run(log_prompt, deps=deps) + log_entry = log_result.output + + # Step 3: Notify observers + for observer in observers: + observer.notify(log_entry) + + # Step 4: Show user response + print(f"\n👤 {deps.user_name} asked: {user_prompt}") + print(f"🤖 Travel Agent says: {response.message}") + print(f"📍 Destination Suggested: {response.destination}") + + +# ------------------------------- +# Main +# ------------------------------- + + +async def main(): + deps = TravelDeps(user_name="Alex") + observers = [ConsoleLogger()] + await recommend_travel("I want to go somewhere warm with beaches.", deps, observers) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/2025/aidesign/pyproject.toml b/2025/aidesign/pyproject.toml new file mode 100644 index 0000000..e4d1804 --- /dev/null +++ b/2025/aidesign/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "aidesign" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "pydantic>=2.11.7", + "python-dotenv>=1.1.1", +] + +[tool.uv] +environments = ["sys_platform == 'darwin'"] \ No newline at end of file diff --git a/2025/aidesign/strategy.py b/2025/aidesign/strategy.py new file mode 100644 index 0000000..59f73c7 --- /dev/null +++ b/2025/aidesign/strategy.py @@ -0,0 +1,117 @@ +import asyncio +import os +from dataclasses import dataclass +from typing import Callable + +from dotenv import load_dotenv +from pydantic import BaseModel, Field +from pydantic_ai import Agent + +# Load API key +load_dotenv() +os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") + +# ---------------------------------- +# Common Output Model +# ---------------------------------- + + +class TravelRecommendation(BaseModel): + destination: str = Field(..., description="Recommended destination") + message: str = Field(..., description="Message from the travel agent to the user") + + +# ---------------------------------- +# Dependencies (for extensibility) +# ---------------------------------- + + +@dataclass +class TravelDeps: + user_name: str + + +# ---------------------------------- +# Strategy Functions +# Each strategy returns an Agent with a specific personality +# ---------------------------------- + + +def professional_agent() -> Agent[TravelDeps, TravelRecommendation]: + return Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=TravelRecommendation, + system_prompt=( + "You are a highly professional and polite travel agent. " + "You give thoughtful recommendations based on user preferences." + ), + ) + + +def fun_agent() -> Agent[TravelDeps, TravelRecommendation]: + return Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=TravelRecommendation, + system_prompt=( + "You are a fun, quirky travel agent who gets super excited about cool places. " + "Your responses are friendly and humorous, but still helpful." + ), + ) + + +def budget_agent() -> Agent[TravelDeps, TravelRecommendation]: + return Agent( + "openai:gpt-4o", + deps_type=TravelDeps, + output_type=TravelRecommendation, + system_prompt=( + "You are a frugal travel expert who finds great destinations with low cost. " + "Your suggestions should highlight affordability and value." + ), + ) + + +# ---------------------------------- +# Function to Run Strategy +# ---------------------------------- + + +async def run_travel_strategy( + user_prompt: str, + deps: TravelDeps, + strategy_fn: Callable[[], Agent[TravelDeps, TravelRecommendation]], +): + agent = strategy_fn() + result = await agent.run(user_prompt, deps=deps) + output = result.output + + # Display the structured response + print(f"\n👤 {deps.user_name} asked: {user_prompt}") + print(f"🤖 {agent.model}: {output.message}") + print(f"📍 Destination: {output.destination}") + + +# ---------------------------------- +# Run Example with Different Strategies +# ---------------------------------- + + +async def main(): + deps = TravelDeps(user_name="Sam") + + print("=== Professional Agent ===") + await run_travel_strategy( + "I want a peaceful place by the sea.", deps, professional_agent + ) + + print("\n=== Fun Agent ===") + await run_travel_strategy("I want a peaceful place by the sea.", deps, fun_agent) + + print("\n=== Budget Agent ===") + await run_travel_strategy("I want a peaceful place by the sea.", deps, budget_agent) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/2025/aidesign/uv.lock b/2025/aidesign/uv.lock new file mode 100644 index 0000000..93a620c --- /dev/null +++ b/2025/aidesign/uv.lock @@ -0,0 +1,100 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "aidesign" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] From b2cafadb65e01e1030b798469b88d3a8a03dce0c Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Mon, 28 Jul 2025 23:58:45 +0200 Subject: [PATCH 34/44] Added pydantic ai dependency --- 2025/aidesign/pyproject.toml | 3 +- 2025/aidesign/uv.lock | 1082 +++++++++++++++++++++++++++++++++- 2 files changed, 1062 insertions(+), 23 deletions(-) diff --git a/2025/aidesign/pyproject.toml b/2025/aidesign/pyproject.toml index e4d1804..2b7681a 100644 --- a/2025/aidesign/pyproject.toml +++ b/2025/aidesign/pyproject.toml @@ -4,8 +4,9 @@ version = "0.1.0" requires-python = ">=3.13" dependencies = [ "pydantic>=2.11.7", + "pydantic-ai>=0.4.8", "python-dotenv>=1.1.1", ] [tool.uv] -environments = ["sys_platform == 'darwin'"] \ No newline at end of file +environments = ["sys_platform == 'darwin'"] diff --git a/2025/aidesign/uv.lock b/2025/aidesign/uv.lock index 93a620c..4805e73 100644 --- a/2025/aidesign/uv.lock +++ b/2025/aidesign/uv.lock @@ -1,22 +1,83 @@ version = 1 revision = 2 requires-python = ">=3.13" +resolution-markers = [ + "sys_platform == 'darwin'", +] +supported-markers = [ + "sys_platform == 'darwin'", +] + +[[package]] +name = "ag-ui-protocol" +version = "0.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/de/0bddf7f26d5f38274c99401735c82ad59df9cead6de42f4bb2ad837286fe/ag_ui_protocol-0.1.8.tar.gz", hash = "sha256:eb745855e9fc30964c77e953890092f8bd7d4bbe6550d6413845428dd0faac0b", size = 5323, upload-time = "2025-07-15T10:55:36.389Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/00/40c6b0313c25d1ab6fac2ecba1cd5b15b1cd3c3a71b3d267ad890e405889/ag_ui_protocol-0.1.8-py3-none-any.whl", hash = "sha256:1567ccb067b7b8158035b941a985e7bb185172d660d4542f3f9c6fff77b55c6e", size = 7066, upload-time = "2025-07-15T10:55:35.075Z" }, +] [[package]] name = "aidesign" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-ai", marker = "sys_platform == 'darwin'" }, + { name = "python-dotenv", marker = "sys_platform == 'darwin'" }, ] [package.metadata] requires-dist = [ { name = "pydantic", specifier = ">=2.11.7" }, + { name = "pydantic-ai", specifier = ">=0.4.8" }, { name = "python-dotenv", specifier = ">=1.1.1" }, ] +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs", marker = "sys_platform == 'darwin'" }, + { name = "aiosignal", marker = "sys_platform == 'darwin'" }, + { name = "attrs", marker = "sys_platform == 'darwin'" }, + { name = "frozenlist", marker = "sys_platform == 'darwin'" }, + { name = "multidict", marker = "sys_platform == 'darwin'" }, + { name = "propcache", marker = "sys_platform == 'darwin'" }, + { name = "yarl", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921, upload-time = "2025-07-10T13:05:33.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471, upload-time = "2025-07-10T13:04:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128, upload-time = "2025-07-10T13:04:21.928Z" }, + { url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426, upload-time = "2025-07-10T13:04:24.071Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -26,47 +87,767 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anthropic" +version = "0.60.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "distro", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "jiter", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "sniffio", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/03/3334921dc54ed822b3dd993ae72d823a7402588521bbba3e024b3333a1fd/anthropic-0.60.0.tar.gz", hash = "sha256:a22ba187c6f4fd5afecb2fc913b960feccf72bc0d25c1b7ce0345e87caede577", size = 425983, upload-time = "2025-07-28T19:53:47.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/bb/d84f287fb1c217b30c328af987cf8bbe3897edf0518dcc5fa39412f794ec/anthropic-0.60.0-py3-none-any.whl", hash = "sha256:65ad1f088a960217aaf82ba91ff743d6c89e9d811c6d64275b9a7c59ee9ac3c6", size = 293116, upload-time = "2025-07-28T19:53:45.944Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin'" }, + { name = "sniffio", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "boto3" +version = "1.39.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "sys_platform == 'darwin'" }, + { name = "jmespath", marker = "sys_platform == 'darwin'" }, + { name = "s3transfer", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/65/ddd4f52d138e52c1345c2d2421281a98449a6e4365290477befe06fa649a/boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409", size = 111856, upload-time = "2025-07-28T19:56:49.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/c5/27f50a31317041dc3ad79d62f37d5fcfb3f349c2fba8ea3e81de169db870/boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a", size = 139901, upload-time = "2025-07-28T19:56:47.381Z" }, +] + +[[package]] +name = "botocore" +version = "1.39.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath", marker = "sys_platform == 'darwin'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin'" }, + { name = "urllib3", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/e2/8cd9560e7e44cf977dc0cc2e48da7634e78b7104ae6e47f4e1dfc1093965/botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600", size = 14237556, upload-time = "2025-07-28T19:56:39.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/6e/f25b8633e7ab2008de4c27466c9bc39e32dc73816619ffebbea12936135a/botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff", size = 13894884, upload-time = "2025-07-28T19:56:33.715Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cohere" +version = "5.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "httpx-sse", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-core", marker = "sys_platform == 'darwin'" }, + { name = "requests", marker = "sys_platform == 'darwin'" }, + { name = "tokenizers", marker = "sys_platform == 'darwin'" }, + { name = "types-requests", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/c7/fd1e4c61cf3f0aac9d9d73fce63a766c9778e1270f7a26812eb289b4851d/cohere-5.16.1.tar.gz", hash = "sha256:02aa87668689ad0fbac2cda979c190310afdb99fb132552e8848fdd0aff7cd40", size = 162300, upload-time = "2025-07-09T20:47:36.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/c6/72309ac75f3567425ca31a601ad394bfee8d0f4a1569dfbc80cbb2890d07/cohere-5.16.1-py3-none-any.whl", hash = "sha256:37e2c1d69b1804071b5e5f5cb44f8b74127e318376e234572d021a1a729c6baa", size = 291894, upload-time = "2025-07-09T20:47:34.919Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + +[[package]] +name = "fastavro" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/8f/32664a3245247b13702d13d2657ea534daf64e58a3f72a3a2d10598d6916/fastavro-1.11.1.tar.gz", hash = "sha256:bf6acde5ee633a29fb8dfd6dfea13b164722bc3adc05a0e055df080549c1c2f8", size = 1016250, upload-time = "2025-05-18T04:54:31.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/08/8e25b9e87a98f8c96b25e64565fa1a1208c0095bb6a84a5c8a4b925688a5/fastavro-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f963b8ddaf179660e814ab420850c1b4ea33e2ad2de8011549d958b21f77f20a", size = 931520, upload-time = "2025-05-18T04:55:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/d0/57/0d31ed1a49c65ad9f0f0128d9a928972878017781f9d4336f5f60982334c/fastavro-1.11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e5ed1325c1c414dd954e7a2c5074daefe1eceb672b8c727aa030ba327aa00693", size = 1021401, upload-time = "2025-05-18T04:55:23.431Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools", marker = "sys_platform == 'darwin'" }, + { name = "pyasn1-modules", marker = "sys_platform == 'darwin'" }, + { name = "rsa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-genai" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "google-auth", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "requests", marker = "sys_platform == 'darwin'" }, + { name = "tenacity", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, + { name = "websockets", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/37/6c0ececc3a7a629029b5beed2ceb9f28f73292236eb96272355636769b0d/google_genai-1.27.0.tar.gz", hash = "sha256:15a13ffe7b3938da50b9ab77204664d82122617256f55b5ce403d593848ef635", size = 220099, upload-time = "2025-07-23T22:00:46.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/12/279afe7357af73f9737a3412b6f0bc1482075b896340eb46a2f9cb0fd791/google_genai-1.27.0-py3-none-any.whl", hash = "sha256:afd6b4efaf8ec1d20a6e6657d768b68d998d60007c6e220e9024e23c913c1833", size = 218489, upload-time = "2025-07-23T22:00:44.879Z" }, +] + +[[package]] +name = "griffe" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/42/486d21a6c33ff69a7381511d507b6db7a7b7f4d5bec3279bc0dc45c658a9/griffe-1.9.0.tar.gz", hash = "sha256:b5531cf45e9b73f0842c2121cc4d4bcbb98a55475e191fc9830e7aef87a920a0", size = 409341, upload-time = "2025-07-28T17:45:38.712Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/65/7b3fcef8c9fb6d1023484d9caf87e78450a5c9cd1e191ce9632990b65284/griffe-1.9.0-py3-none-any.whl", hash = "sha256:bcf90ee3ad42bbae70a2a490c782fc8e443de9b84aa089d857c278a4e23215fc", size = 137060, upload-time = "2025-07-28T17:45:36.973Z" }, +] + +[[package]] +name = "groq" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "distro", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "sniffio", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b1/72ca20dc9b977b7f604648e8944c77b267bddeb90d8e16bda0cf0e397844/groq-0.30.0.tar.gz", hash = "sha256:919466e48fcbebef08fed3f71debb0f96b0ea8d2ec77842c384aa843019f6e2c", size = 134928, upload-time = "2025-07-11T20:28:36.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/b8/5b90edf9fbd795597220e3d1b5534d845e69a73ffe1fdeb967443ed2a6cf/groq-0.30.0-py3-none-any.whl", hash = "sha256:6d9609a7778ba56432f45c1bac21b005f02c6c0aca9c1c094e65536f162c1e83", size = 131056, upload-time = "2025-07-11T20:28:35.591Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin'" }, + { name = "h11", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "certifi", marker = "sys_platform == 'darwin'" }, + { name = "httpcore", marker = "sys_platform == 'darwin'" }, + { name = "idna", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", marker = "sys_platform == 'darwin'" }, + { name = "fsspec", marker = "sys_platform == 'darwin'" }, + { name = "hf-xet", marker = "(platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, + { name = "packaging", marker = "sys_platform == 'darwin'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin'" }, + { name = "requests", marker = "sys_platform == 'darwin'" }, + { name = "tqdm", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/53/572b9c03ca0cabb3d71e02b1750b595196332cfb8c4d74a90de383451171/huggingface_hub-0.34.2.tar.gz", hash = "sha256:a27c1ba3d2a70b378dce546c8be3a90349a64e6bd5d7a806679d4bf5e5d2d8fe", size = 456837, upload-time = "2025-07-28T10:12:09.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/20/5ee412acef0af05bd3ccc78186ccb7ca672f9998a7cbc94c011df8f101f4/huggingface_hub-0.34.2-py3-none-any.whl", hash = "sha256:699843fc58d3d257dbd3cb014e0cd34066a56372246674322ba0909981ec239c", size = 558843, upload-time = "2025-07-28T10:12:07.064Z" }, +] + +[package.optional-dependencies] +inference = [ + { name = "aiohttp", marker = "sys_platform == 'darwin'" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "jiter" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "sys_platform == 'darwin'" }, + { name = "jsonschema-specifications", marker = "sys_platform == 'darwin'" }, + { name = "referencing", marker = "sys_platform == 'darwin'" }, + { name = "rpds-py", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "logfire-api" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/37/0ddb054e58b1b726f31390058f1a61440ce6383b6d0e8fd532aeb73e54f1/logfire_api-4.0.0.tar.gz", hash = "sha256:74a027693d5789d699c2b5f4c135e3e343faf1cc086a99cce894ff0d632b6165", size = 52094, upload-time = "2025-07-22T15:12:07.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/3b/9e49b11b0e9df8f224ac60849829bcf662c5894e681e9b5210e41094a47f/logfire_api-4.0.0-py3-none-any.whl", hash = "sha256:1b8ec5396d7327bc325235fe012e09f756ae6d9f631aa62a387d668669fb1bff", size = 87424, upload-time = "2025-07-22T15:12:04.099Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mcp" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "httpx-sse", marker = "sys_platform == 'darwin'" }, + { name = "jsonschema", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-settings", marker = "sys_platform == 'darwin'" }, + { name = "python-multipart", marker = "sys_platform == 'darwin'" }, + { name = "sse-starlette", marker = "sys_platform == 'darwin'" }, + { name = "starlette", marker = "sys_platform == 'darwin'" }, + { name = "uvicorn", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202, upload-time = "2025-07-24T18:29:05.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473, upload-time = "2025-07-24T18:29:03.419Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mistralai" +version = "1.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/1d/280c6582124ff4aab3009f0c0282fd48e7fa3a60457f25e9196dc3cc2b8f/mistralai-1.9.3.tar.gz", hash = "sha256:a69806247ed3a67820ecfc9a68b7dbc0c6120dad5e5c3d507bd57fa388b491b7", size = 197355, upload-time = "2025-07-23T19:12:16.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/9a/0c48706c646b0391b798f8568f2b1545e54d345805e988003c10450b7b4c/mistralai-1.9.3-py3-none-any.whl", hash = "sha256:962445e7cebadcbfbcd1daf973e853a832dcf7aba6320468fcf7e2cf5f943aec", size = 426266, upload-time = "2025-07-23T19:12:15.414Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, +] + +[[package]] +name = "openai" +version = "1.97.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "distro", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "jiter", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "sniffio", marker = "sys_platform == 'darwin'" }, + { name = "tqdm", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/57/1c471f6b3efb879d26686d31582997615e969f3bb4458111c9705e56332e/openai-1.97.1.tar.gz", hash = "sha256:a744b27ae624e3d4135225da9b1c89c107a2a7e5bc4c93e5b7b5214772ce7a4e", size = 494267, upload-time = "2025-07-22T13:10:12.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/35/412a0e9c3f0d37c94ed764b8ac7adae2d834dbd20e69f6aca582118e0f55/openai-1.97.1-py3-none-any.whl", hash = "sha256:4e96bbdf672ec3d44968c9ea39d2c375891db1acc1794668d8149d5fa6000606", size = 764380, upload-time = "2025-07-22T13:10:10.689Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/c9/4509bfca6bb43220ce7f863c9f791e0d5001c2ec2b5867d48586008b3d96/opentelemetry_api-1.35.0.tar.gz", hash = "sha256:a111b959bcfa5b4d7dffc2fbd6a241aa72dd78dd8e79b5b1662bda896c5d2ffe", size = 64778, upload-time = "2025-07-11T12:23:28.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pydantic" version = "2.11.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, + { name = "annotated-types", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-core", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] +[[package]] +name = "pydantic-ai" +version = "0.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mcp", "mistral", "openai", "retries", "vertexai"], marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/44/e1f6527d145c6f4ad42124c6393d71f59a58a0cbe21fd5650aee264a4452/pydantic_ai-0.4.8.tar.gz", hash = "sha256:b0e01494e881c4f72cc8137e37512473de04540761a581e996aeb9707ee68ae4", size = 43552745, upload-time = "2025-07-28T15:03:12.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/79/44c2c876d91d5e8c99f023489fd5a3e9364130dd05ef8e32b31ca243933d/pydantic_ai-0.4.8-py3-none-any.whl", hash = "sha256:34080e8ca4f28028e4643603ad903c18563e6435371c4f54729ad1ab5f402795", size = 10184, upload-time = "2025-07-28T15:03:00.4Z" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport", marker = "sys_platform == 'darwin'" }, + { name = "griffe", marker = "sys_platform == 'darwin'" }, + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "opentelemetry-api", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-graph", marker = "sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/74/38e4cd2cfd9a621bb9f5991ff5f23dfdfaf3b86e3c176ca2b501155ff006/pydantic_ai_slim-0.4.8.tar.gz", hash = "sha256:0852b1635caaf97e67bff8a8633f45ffe3265d5231f0bf261ec9e422d1222b31", size = 189216, upload-time = "2025-07-28T15:03:16.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/3e/a7c93524eb5b8f48596e0ddc4848328145974dce12e641a87f7035909041/pydantic_ai_slim-0.4.8-py3-none-any.whl", hash = "sha256:34f50350714247bd0a1e8af4bb07c2478f14ab09cd9e01d4a2a9897ff7af00bb", size = 254430, upload-time = "2025-07-28T15:03:04.543Z" }, +] + +[package.optional-dependencies] +ag-ui = [ + { name = "ag-ui-protocol", marker = "sys_platform == 'darwin'" }, + { name = "starlette", marker = "sys_platform == 'darwin'" }, +] +anthropic = [ + { name = "anthropic", marker = "sys_platform == 'darwin'" }, +] +bedrock = [ + { name = "boto3", marker = "sys_platform == 'darwin'" }, +] +cli = [ + { name = "argcomplete", marker = "sys_platform == 'darwin'" }, + { name = "prompt-toolkit", marker = "sys_platform == 'darwin'" }, + { name = "rich", marker = "sys_platform == 'darwin'" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform == 'darwin'" }, + { name = "tokenizers", marker = "sys_platform == 'darwin'" }, +] +evals = [ + { name = "pydantic-evals", marker = "sys_platform == 'darwin'" }, +] +google = [ + { name = "google-genai", marker = "sys_platform == 'darwin'" }, +] +groq = [ + { name = "groq", marker = "sys_platform == 'darwin'" }, +] +huggingface = [ + { name = "huggingface-hub", extra = ["inference"], marker = "sys_platform == 'darwin'" }, +] +mcp = [ + { name = "mcp", marker = "sys_platform == 'darwin'" }, +] +mistral = [ + { name = "mistralai", marker = "sys_platform == 'darwin'" }, +] +openai = [ + { name = "openai", marker = "sys_platform == 'darwin'" }, +] +retries = [ + { name = "tenacity", marker = "sys_platform == 'darwin'" }, +] +vertexai = [ + { name = "google-auth", marker = "sys_platform == 'darwin'" }, + { name = "requests", marker = "sys_platform == 'darwin'" }, +] + [[package]] name = "pydantic-core" version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-evals" +version = "0.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, + { name = "logfire-api", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "pydantic-ai-slim", marker = "sys_platform == 'darwin'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin'" }, + { name = "rich", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/2c/5927176e27dbb73592684e48ae26701a96e36e944dde023e860573659226/pydantic_evals-0.4.8.tar.gz", hash = "sha256:a2f265d4bdaab6fb1f13fa8b99590ba48086d0214d8ffc544bf2c8400ca7ba5d", size = 43729, upload-time = "2025-07-28T15:03:17.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/b1/353bc5050ef6cb075dada0ba00e42ea0d5d5e2123406f4e71046b11cebeb/pydantic_evals-0.4.8-py3-none-any.whl", hash = "sha256:7e05f79f13c4a2b146090c0c031765ec5a658f96059c541335b3fd4708275ad3", size = 52505, upload-time = "2025-07-28T15:03:06.678Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "0.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", marker = "sys_platform == 'darwin'" }, + { name = "logfire-api", marker = "sys_platform == 'darwin'" }, + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/a4/7805229a07bf32cb4545b4c942e3a8ac570a4a0b91a0010237fb43217467/pydantic_graph-0.4.8.tar.gz", hash = "sha256:78a1d8ab084cfbbde418f901296d7e0b37299c5182de0b16efc1fd0bd173795d", size = 21980, upload-time = "2025-07-28T15:03:18.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/e1/fa5dd1a98350790e2f952ea9778f09f1fd6be31eee885b5c096514ff535e/pydantic_graph-0.4.8-py3-none-any.whl", hash = "sha256:6e7282d07896d96c7678bcf17cef7643718b05cda15024a11e8a22e3b5dfb42f", size = 27566, upload-time = "2025-07-28T15:03:08.572Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "sys_platform == 'darwin'" }, + { name = "python-dotenv", marker = "sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] @@ -78,6 +859,191 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "sys_platform == 'darwin'" }, + { name = "rpds-py", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin'" }, + { name = "charset-normalizer", marker = "sys_platform == 'darwin'" }, + { name = "idna", marker = "sys_platform == 'darwin'" }, + { name = "urllib3", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", marker = "sys_platform == 'darwin'" }, + { name = "pygments", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, + { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.1" @@ -92,9 +1058,81 @@ name = "typing-inspection" version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin'" }, + { name = "h11", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin'" }, + { name = "multidict", marker = "sys_platform == 'darwin'" }, + { name = "propcache", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From b6fa9de84785e345205130a43fe24f8cd1ded8f1 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 30 Jul 2025 17:12:08 +0200 Subject: [PATCH 35/44] Added code examples for SOLID video. --- 2025/solid/class_based_report.py | 121 ++++++++++++++++++++++++++++ 2025/solid/functional_report.py | 91 +++++++++++++++++++++ 2025/solid/messy_report.py | 54 +++++++++++++ 2025/solid/pyproject.toml | 8 ++ 2025/solid/sales_data.csv | 51 ++++++++++++ 2025/solid/uv.lock | 132 +++++++++++++++++++++++++++++++ 6 files changed, 457 insertions(+) create mode 100644 2025/solid/class_based_report.py create mode 100644 2025/solid/functional_report.py create mode 100644 2025/solid/messy_report.py create mode 100644 2025/solid/pyproject.toml create mode 100644 2025/solid/sales_data.csv create mode 100644 2025/solid/uv.lock diff --git a/2025/solid/class_based_report.py b/2025/solid/class_based_report.py new file mode 100644 index 0000000..86ff605 --- /dev/null +++ b/2025/solid/class_based_report.py @@ -0,0 +1,121 @@ +import json +from dataclasses import dataclass +from datetime import datetime +from typing import Protocol + +import pandas as pd + + +@dataclass +class ReportConfig: + input_file: str + output_file: str + start_date: datetime | None = None + end_date: datetime | None = None + + +class SalesReader(Protocol): + def read(self, file: str) -> pd.DataFrame: ... + + +class CsvSalesReader: + def read(self, file: str) -> pd.DataFrame: + return pd.read_csv(file, parse_dates=["date"]) + + +class DateRangeFilter: + def apply( + self, df: pd.DataFrame, start: datetime | None, end: datetime | None + ) -> pd.DataFrame: + if start: + df = df[df["date"] >= pd.Timestamp(start)] + if end: + df = df[df["date"] <= pd.Timestamp(end)] + return df + + +class Metric(Protocol): + def compute(self, df: pd.DataFrame) -> dict[str, object]: ... + + +class CustomerCountMetric: + def compute(self, df: pd.DataFrame) -> dict[str, object]: + return {"number_of_customers": df["name"].nunique()} + + +class AverageOrderValueMetric: + def compute(self, df: pd.DataFrame) -> dict[str, object]: + sales = df[df["price"] > 0]["price"] + avg = sales.mean() if not sales.empty else 0.0 + return {"average_order_value (pre-tax)": round(avg, 2)} + + +class ReturnPercentageMetric: + def compute(self, df: pd.DataFrame) -> dict[str, object]: + returns = df[df["price"] < 0] + pct = (len(returns) / len(df)) * 100 if len(df) > 0 else 0 + return {"percentage_of_returns": round(pct, 2)} + + +class TotalSalesMetric: + def compute(self, df: pd.DataFrame) -> dict[str, object]: + return {"total_sales_in_period (pre-tax)": round(df["price"].sum(), 2)} + + +class SalesReportGenerator: + def __init__( + self, reader: SalesReader, filterer: DateRangeFilter, metrics: list[Metric] + ): + self.reader = reader + self.filterer = filterer + self.metrics = metrics + + def generate(self, config: ReportConfig) -> dict[str, object]: + df = self.reader.read(config.input_file) + df = self.filterer.apply(df, config.start_date, config.end_date) + + result = {} + for metric in self.metrics: + result.update(metric.compute(df)) + + result["report_start"] = ( + config.start_date.strftime("%Y-%m-%d") if config.start_date else "N/A" + ) + result["report_end"] = ( + config.end_date.strftime("%Y-%m-%d") if config.end_date else "N/A" + ) + return result + + +class JSONReportWriter: + def write(self, report: dict[str, object], output_file: str) -> None: + with open(output_file, "w") as f: + json.dump(report, f, indent=2) + + +def main() -> None: + config = ReportConfig( + input_file="sales_data.csv", + output_file="sales_report.json", + start_date=datetime(2024, 1, 1), + end_date=datetime(2024, 12, 31), + ) + + reader = CsvSalesReader() + filterer = DateRangeFilter() + metrics: list[Metric] = [ + CustomerCountMetric(), + AverageOrderValueMetric(), + ReturnPercentageMetric(), + TotalSalesMetric(), + ] + + generator = SalesReportGenerator(reader, filterer, metrics) + report = generator.generate(config) + + writer = JSONReportWriter() + writer.write(report, config.output_file) + + +if __name__ == "__main__": + main() diff --git a/2025/solid/functional_report.py b/2025/solid/functional_report.py new file mode 100644 index 0000000..3ab6e49 --- /dev/null +++ b/2025/solid/functional_report.py @@ -0,0 +1,91 @@ +import json +from dataclasses import dataclass, field +from datetime import datetime +from typing import Any, Callable + +import pandas as pd + +type MetricFn = Callable[[pd.DataFrame], dict[str, Any]] + + +@dataclass +class ReportConfig: + input_file: str + start_date: datetime | None = None + end_date: datetime | None = None + metrics: list[MetricFn] = field(default_factory=list) + + +def read_sales(file: str) -> pd.DataFrame: + return pd.read_csv(file, parse_dates=["date"]) + + +def filter_sales( + df: pd.DataFrame, start: datetime | None, end: datetime | None +) -> pd.DataFrame: + if start: + df = df[df["date"] >= pd.Timestamp(start)] + if end: + df = df[df["date"] <= pd.Timestamp(end)] + return df + + +def customer_count_metric(df: pd.DataFrame) -> dict[str, Any]: + return {"number_of_customers": df["name"].nunique()} + + +def average_order_value_metric(df: pd.DataFrame) -> dict[str, Any]: + sales = df[df["price"] > 0]["price"] + avg = sales.mean() if not sales.empty else 0.0 + return {"average_order_value (pre-tax)": round(avg, 2)} + + +def return_percentage_metric(df: pd.DataFrame) -> dict[str, Any]: + returns = df[df["price"] < 0] + pct = (len(returns) / len(df)) * 100 if len(df) > 0 else 0 + return {"percentage_of_returns": round(pct, 2)} + + +def total_sales_metric(df: pd.DataFrame) -> dict[str, Any]: + return {"total_sales_in_period (pre-tax)": round(df["price"].sum(), 2)} + + +def generate_report_data(df: pd.DataFrame, config: ReportConfig) -> dict[str, Any]: + result: dict[str, Any] = {} + for metric in config.metrics: + result.update(metric(df)) + result["report_start"] = ( + config.start_date.strftime("%Y-%m-%d") if config.start_date else "N/A" + ) + result["report_end"] = ( + config.end_date.strftime("%Y-%m-%d") if config.end_date else "N/A" + ) + return result + + +def write_report(data: dict[str, Any], filename: str): + with open(filename, "w") as f: + json.dump(data, f, indent=2) + + +def main() -> None: + config = ReportConfig( + input_file="sales_data.csv", + start_date=datetime(2024, 1, 1), + end_date=datetime(2024, 12, 31), + metrics=[ + customer_count_metric, + average_order_value_metric, + return_percentage_metric, + total_sales_metric, + ], + ) + + df = read_sales(config.input_file) + df = filter_sales(df, config.start_date, config.end_date) + report_data = generate_report_data(df, config) + write_report(report_data, "sales_report.json") + + +if __name__ == "__main__": + main() diff --git a/2025/solid/messy_report.py b/2025/solid/messy_report.py new file mode 100644 index 0000000..85b8ca6 --- /dev/null +++ b/2025/solid/messy_report.py @@ -0,0 +1,54 @@ +import json +from datetime import datetime + +import pandas as pd + + +class MessySalesReport: + def generate( + self, + input_file: str, + output_file: str, + start_date: datetime | None = None, + end_date: datetime | None = None, + ) -> None: + df = pd.read_csv(input_file, parse_dates=["date"]) + + if start_date: + df = df[df["date"] >= pd.Timestamp(start_date)] + if end_date: + df = df[df["date"] <= pd.Timestamp(end_date)] + + num_customers = df["name"].nunique() + avg_order = ( + df[df["price"] > 0]["price"].mean() if not df[df["price"] > 0].empty else 0 + ) + returns = df[df["price"] < 0] + return_pct = (len(returns) / len(df)) * 100 if len(df) > 0 else 0 + total_sales = df["price"].sum() + + report = { + "report_start": start_date.strftime("%Y-%m-%d") if start_date else "N/A", + "report_end": end_date.strftime("%Y-%m-%d") if end_date else "N/A", + "number_of_customers": num_customers, + "average_order_value (pre-tax)": round(avg_order, 2), + "percentage_of_returns": round(return_pct, 2), + "total_sales_in_period (pre-tax)": round(total_sales, 2), + } + + with open(output_file, "w") as f: + json.dump(report, f, indent=2) + + +def main() -> None: + report = MessySalesReport() + report.generate( + input_file="sales_data.csv", + output_file="sales_report.json", + start_date=datetime(2024, 1, 1), + end_date=datetime(2024, 12, 31), + ) + + +if __name__ == "__main__": + main() diff --git a/2025/solid/pyproject.toml b/2025/solid/pyproject.toml new file mode 100644 index 0000000..9f9cf8a --- /dev/null +++ b/2025/solid/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "solid" +version = "0.1.0" +description = "Code refactoring using the SOLID principles" +requires-python = ">=3.13" +dependencies = [ + "pandas>=2.3.1", +] diff --git a/2025/solid/sales_data.csv b/2025/solid/sales_data.csv new file mode 100644 index 0000000..8627b4f --- /dev/null +++ b/2025/solid/sales_data.csv @@ -0,0 +1,51 @@ +"name","address","item","date","price","tax" +"John Guerrero","1344 Reed Spring Suite 944, Port Jonathan, DC 08436","Keyboard","2024-05-07",463.8,97.4 +"Molly Cowan","3783 Kelly Parks, Hoffmanville, MS 81569","Keyboard","2024-06-21",267.63,56.2 +"Lori Porter","318 Travis Trace, North Matthew, LA 12381","Desk","2023-05-11",986.89,207.25 +"Philip Miller","8018 Lee Drive Suite 871, Gutierreztown, NV 03929","Keyboard","2024-01-29",172.52,36.23 +"David Ponce","89433 Pruitt Plaza, Christensenland, KS 04867","Monitor","2024-04-14",-536.66,-112.7 +"Diane Greene","339 James Lake Suite 899, Bennettshire, ND 61981","Keyboard","2024-05-28",1169.86,245.67 +"Kim Brock","699 Mcdaniel Mountain Suite 815, West Julie, AK 78647","Monitor","2024-06-22",155.89,32.74 +"Jennifer Sellers","0630 Burke Summit, Port William, PA 16545","Keyboard","2023-09-04",633.84,133.11 +"Melinda Dennis","6019 Brian Trail, Lake Michellestad, WY 89381","Desk","2023-08-18",520.8,109.37 +"Miss Joanna Contreras DVM","217 Moon Wells Suite 288, Ortizfort, NE 87937","Chair","2023-09-07",1086.12,228.09 +"Angela Chapman","Unit 0607 Box 4655, DPO AP 12818","Laptop","2023-10-02",257.75,54.13 +"Jeffrey Tucker","737 Graves Wells Suite 676, Lake Hailey, RI 98451","Headphones","2024-09-04",519.26,109.04 +"Scott Rasmussen","3265 Delacruz Mountain, Hoffmanborough, TX 46752","Monitor","2023-08-21",421.92,88.6 +"Larry Erickson","16412 Smith Garden, Edwardstad, NV 82892","Mouse","2023-07-03",477.85,100.35 +"Rebecca Buchanan","9418 April Flat Suite 112, Jasonfurt, CA 80823","Desk","2023-03-17",1302.67,273.56 +"Mary Hill","8691 Clark Avenue, South Jessicachester, ME 26317","Chair","2024-09-18",935.48,196.45 +"Robin Oliver","1120 Matthew Rest, Tuckershire, CA 98947","Chair","2024-04-20",-428.78,-90.04 +"Charles Scott","27510 Aaron Inlet Suite 851, East Kelseychester, NH 55606","Chair","2023-10-31",1102.14,231.45 +"Sandy Spencer","19166 Blair Lane, Kellifurt, MI 37349","Keyboard","2024-09-18",84.55,17.76 +"Eric Dunn","55700 Rivera Loaf Apt. 771, West Chelsea, TX 23511","Mouse","2024-11-27",1257.19,264.01 +"Dr. Charles Myers","71166 Anthony Crossroad, Donnaport, NV 79264","Mouse","2023-07-21",704.39,147.92 +"Amanda Wallace","2237 Brandon Turnpike Suite 629, Loweberg, TN 07617","Chair","2024-09-25",1235.35,259.42 +"Michael Spencer","01698 Cox Freeway Apt. 043, East Douglas, NE 68236","Keyboard","2023-01-12",732.29,153.78 +"Sylvia Mitchell","716 Gregory Locks Apt. 086, Lake Chelsea, AL 85891","Laptop","2024-06-08",-78.58,-16.5 +"Gregory Hansen","718 Veronica Mountains Apt. 232, East John, NY 17047","Headphones","2024-02-18",878.4,184.46 +"Brenda Jackson","588 Donald Estate, East Michellefort, ND 75420","Keyboard","2024-08-25",1053.68,221.27 +"Alexander Lewis","61716 Deborah Coves, Mcdanielfurt, CA 04531","Laptop","2024-12-18",158.75,33.34 +"Dawn Kane","USNS Anderson, FPO AP 76056","Monitor","2023-11-03",786.68,165.2 +"Robert Roberts","6256 Padilla Center, Lake Shawnshire, RI 34060","Keyboard","2024-07-25",955.43,200.64 +"Steven Stanton","0208 Johnston Corner, South Sheila, NH 10538","Mouse","2024-06-19",1158.38,243.26 +"Adam English","296 Isaiah Meadow, Hunthaven, NE 01384","Laptop","2024-10-08",1013.07,212.74 +"Angela Lara","799 Susan Extension Apt. 955, Susantown, AR 95518","Mouse","2024-10-03",889.66,186.83 +"Joshua Townsend","209 Munoz Brook Suite 145, Ericmouth, TN 24982","Keyboard","2024-07-21",1249.22,262.34 +"Rhonda Johnson","PSC 1781, Box 0242, APO AE 36561","Headphones","2024-07-15",1051.27,220.77 +"Rachel Shaw","4463 Romero Keys, West Leslieville, GA 64799","Chair","2023-12-29",-317.42,-66.66 +"Mackenzie Blevins","PSC 2008, Box 5610, APO AP 24541","Desk","2024-07-04",453.37,95.21 +"Jaime Brown","924 Monica Key Apt. 814, Margaretberg, NC 39764","Monitor","2024-05-09",-568.46,-119.38 +"Ronald Williams II","1629 Brittany Turnpike Apt. 725, East Terry, ME 94547","Desk","2024-12-02",534.26,112.19 +"Joseph White","3564 Anderson Canyon, Perezmouth, NC 35482","Chair","2024-11-01",1454.68,305.48 +"Marie Brown","04699 Kimberly Rapid, Smithborough, WY 73900","Chair","2024-08-08",393.65,82.67 +"Stephanie Brown MD","06243 Short Cape, Johnbury, DE 54999","Mouse","2024-08-01",949.32,199.36 +"Luke Mccoy","7186 Newton Trail Apt. 182, Emilyton, DE 42065","Headphones","2023-04-28",764.15,160.47 +"Ralph Gonzalez","6396 Todd Squares, Georgechester, UT 14672","Headphones","2023-04-05",822.79,172.79 +"Michele Medina","716 Melissa Turnpike, Forbesfurt, MI 98049","Headphones","2024-06-10",647.54,135.98 +"Joseph Reed","Unit 4989 Box 6489, DPO AP 24839","Desk","2024-08-15",626.1,131.48 +"Eric Parker","7648 Robert Estates Apt. 485, Stevensland, AZ 29898","Mouse","2024-01-22",1236.03,259.57 +"Ashley Howe","837 Debra Ramp Suite 872, Port Tiffany, LA 39492","Monitor","2024-11-07",1321.36,277.49 +"Sean Briggs","791 Jose Knolls, Harrischester, ND 41641","Mouse","2023-09-18",1409.34,295.96 +"Donna Woods","92886 Crawford Point Apt. 749, Wellsfurt, CO 18301","Mouse","2024-12-16",721.4,151.49 +"Sara Sandoval","Unit 9935 Box 3484, DPO AA 53240","Keyboard","2023-08-12",1246.37,261.74 diff --git a/2025/solid/uv.lock b/2025/solid/uv.lock new file mode 100644 index 0000000..37b43f4 --- /dev/null +++ b/2025/solid/uv.lock @@ -0,0 +1,132 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, + { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, + { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "solid" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pandas" }, +] + +[package.metadata] +requires-dist = [{ name = "pandas", specifier = ">=2.3.1" }] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] From 8ca7ee54f0ac0f471fba430460330544020b90f9 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 7 Aug 2025 14:52:03 +0200 Subject: [PATCH 36/44] Added singleton code examples. --- 2025/singleton/basic_example.py | 23 +++++++++++++++++++++ 2025/singleton/config.py | 2 ++ 2025/singleton/instance_control.py | 29 +++++++++++++++++++++++++++ 2025/singleton/metaclass.py | 27 +++++++++++++++++++++++++ 2025/singleton/module_example.py | 8 ++++++++ 2025/singleton/singleton_multi.py | 27 +++++++++++++++++++++++++ 2025/singleton/singleton_threading.py | 22 ++++++++++++++++++++ 7 files changed, 138 insertions(+) create mode 100644 2025/singleton/basic_example.py create mode 100644 2025/singleton/config.py create mode 100644 2025/singleton/instance_control.py create mode 100644 2025/singleton/metaclass.py create mode 100644 2025/singleton/module_example.py create mode 100644 2025/singleton/singleton_multi.py create mode 100644 2025/singleton/singleton_threading.py diff --git a/2025/singleton/basic_example.py b/2025/singleton/basic_example.py new file mode 100644 index 0000000..36666e0 --- /dev/null +++ b/2025/singleton/basic_example.py @@ -0,0 +1,23 @@ +class Config: + _instance = None + + def __init__(self): + self.db_url = "sqlite:///:memory:" + self.debug = True + + def __new__(cls): + if cls._instance is None: + print("Creating new instance") + cls._instance = super().__new__(cls) + return cls._instance + +def main(): + s1 = Config() + s2 = Config() + + print(s1 is s2) + print(id(s1)) + print(id(s2)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/singleton/config.py b/2025/singleton/config.py new file mode 100644 index 0000000..215afab --- /dev/null +++ b/2025/singleton/config.py @@ -0,0 +1,2 @@ +db_uri = "sqlite://:memory:" +debug = True \ No newline at end of file diff --git a/2025/singleton/instance_control.py b/2025/singleton/instance_control.py new file mode 100644 index 0000000..9306188 --- /dev/null +++ b/2025/singleton/instance_control.py @@ -0,0 +1,29 @@ +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + + +class ModelLoader(metaclass=Singleton): + def __init__(self): + print("Loading large model...") + + def predict(self, data: str) -> str: + return f"Prediction for {data}" + + +def predict(data: str) -> str: + model = ModelLoader() + return model.predict(data) + +def main() -> None: + predictions = [predict(f"data_{i}") for i in range(5)] + for pred in predictions: + print(pred) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/singleton/metaclass.py b/2025/singleton/metaclass.py new file mode 100644 index 0000000..b023781 --- /dev/null +++ b/2025/singleton/metaclass.py @@ -0,0 +1,27 @@ +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + +class Config(metaclass=Singleton): + def __init__(self): + self.db_url = "sqlite:///:memory:" + self.debug = True + + def __str__(self) -> str: + return f"Config(db_url={self.db_url}, debug={self.debug})" + +def main() -> None: + s1 = Config() + s2 = Config() + + print(s1 is s2) # Should print True, indicating both are the same instance + print(id(s1)) # Prints the instance id + print(id(s2)) # Prints the same instance id as s1 + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/singleton/module_example.py b/2025/singleton/module_example.py new file mode 100644 index 0000000..e9c3df1 --- /dev/null +++ b/2025/singleton/module_example.py @@ -0,0 +1,8 @@ +import config + +def main() -> None: + if config.debug: + print(config.db_uri) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/singleton/singleton_multi.py b/2025/singleton/singleton_multi.py new file mode 100644 index 0000000..ec35a75 --- /dev/null +++ b/2025/singleton/singleton_multi.py @@ -0,0 +1,27 @@ +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + +class Config(metaclass=Singleton): + def __init__(self): + self.db_url = "sqlite:///:memory:" + self.debug = True + + def __str__(self) -> str: + return f"Config(db_url={self.db_url}, debug={self.debug})" + +def main() -> None: + c1 = Config() + c2 = Config.__new__(Config) + c2.__init__() + + print(c1 is c2) # ❌ False + +if __name__ == "__main__": + main() + diff --git a/2025/singleton/singleton_threading.py b/2025/singleton/singleton_threading.py new file mode 100644 index 0000000..e1b6d2b --- /dev/null +++ b/2025/singleton/singleton_threading.py @@ -0,0 +1,22 @@ +from threading import Thread + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + +class Unsafe(metaclass=Singleton): + def __init__(self): + print("Initializing...") + +def main() -> None: + threads = [Thread(target=Unsafe) for _ in range(20)] + [t.start() for t in threads] + [t.join() for t in threads] + +if __name__ == "__main__": + main() \ No newline at end of file From 0d9b041f3065650be449ee0d73cbc20c6052100e Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 7 Aug 2025 16:34:08 +0200 Subject: [PATCH 37/44] Updated code examples. --- 2025/aidesign/chain_of_responsibility.py | 50 +++++-- 2025/aidesign/observer.py | 179 +++++++++++------------ 2 files changed, 120 insertions(+), 109 deletions(-) diff --git a/2025/aidesign/chain_of_responsibility.py b/2025/aidesign/chain_of_responsibility.py index b68ba29..01573ed 100644 --- a/2025/aidesign/chain_of_responsibility.py +++ b/2025/aidesign/chain_of_responsibility.py @@ -19,6 +19,7 @@ @dataclass class TravelDeps: user_name: str + origin_city: str # ---------------------------------- @@ -112,47 +113,64 @@ class Activities(BaseModel): # Chain Execution Logic # ---------------------------------- - -async def plan_trip(user_input: str, deps: TravelDeps): - print(f"\n👤 {deps.user_name} says: {user_input}") - ctx = TripContext() - - # Step 1: Destination - dest_result = await destination_agent.run(prompt=user_input, deps=deps) +async def handle_destination( + user_input: str, deps: TravelDeps, ctx: TripContext +) -> None: + dest_result = await destination_agent.run(user_input, deps=deps) ctx.destination = dest_result.output.destination print(f"📍 Destination: {ctx.destination}") - # Step 2: Flight - flight_prompt = f"Plan a flight to {ctx.destination}." - flight_result = await flight_agent.run(prompt=flight_prompt, deps=deps) +async def handle_flight( + user_input: str, deps: TravelDeps, ctx: TripContext +) -> None: + flight_prompt = f"Plan a flight from {deps.origin_city} to {ctx.destination}." + flight_result = await flight_agent.run(flight_prompt, deps=deps) ctx.from_city = flight_result.output.from_city ctx.arrival_time = flight_result.output.arrival_time print( f"✈️ Flight: from {ctx.from_city} → {ctx.destination}, arriving at {ctx.arrival_time}" ) - # Step 3: Hotel +async def handle_hotel( + user_input: str, deps: TravelDeps, ctx: TripContext +) -> None: hotel_prompt = ( f"Recommend a hotel in {ctx.destination} for a traveler arriving at {ctx.arrival_time}. " f"Prefer locations near the airport or city center." ) - hotel_result = await hotel_agent.run(prompt=hotel_prompt, deps=deps) + hotel_result = await hotel_agent.run(hotel_prompt, deps=deps) ctx.hotel_name = hotel_result.output.name ctx.hotel_location = hotel_result.output.location print( f"🏨 Hotel: {ctx.hotel_name}, {hotel_result.output.stars}★ at ${hotel_result.output.price_per_night_usd}/night" ) - # Step 4: Activities +async def handle_activities( + user_input: str, deps: TravelDeps, ctx: TripContext +) -> None: activities_prompt = ( f"Suggest activities in {ctx.destination} close to {ctx.hotel_location} " f"and suitable for a traveler arriving at {ctx.arrival_time}." ) - activity_result = await activity_agent.run(prompt=activities_prompt, deps=deps) + activity_result = await activity_agent.run(activities_prompt, deps=deps) print(f"🎯 Activities for {activity_result.output.personalized_for}:") for a in activity_result.output.top_activities: print(f" - {a}") +async def plan_trip(user_input: str, deps: TravelDeps): + print(f"\n👤 {deps.user_name} says: {user_input}") + ctx = TripContext() + + chain = [ + handle_destination, + handle_flight, + handle_hotel, + handle_activities, + ] + + for step in chain: + await step(user_input, deps, ctx) + # ---------------------------------- # Main Execution @@ -160,8 +178,8 @@ async def plan_trip(user_input: str, deps: TravelDeps): async def main(): - deps = TravelDeps(user_name="Maria") - await plan_trip("I want a quiet, sunny destination near the ocean.", deps) + deps = TravelDeps(user_name="Maria", origin_city="Berlin") + await plan_trip("I want a rainy city trip within Europe.", deps) if __name__ == "__main__": diff --git a/2025/aidesign/observer.py b/2025/aidesign/observer.py index 590ead0..a3e5c3d 100644 --- a/2025/aidesign/observer.py +++ b/2025/aidesign/observer.py @@ -1,131 +1,124 @@ import asyncio import os +import time from dataclasses import dataclass +from typing import Protocol from dotenv import load_dotenv -from pydantic import BaseModel, Field +from pydantic import BaseModel from pydantic_ai import Agent -# Load API key load_dotenv() os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") -# ------------------------------- -# Dependencies -# ------------------------------- +# ---------------------------------- +# Dependencies and Output Schema +# ---------------------------------- @dataclass class TravelDeps: user_name: str - - -# ------------------------------- -# Main Response Output -# ------------------------------- + origin_city: str class TravelResponse(BaseModel): - message: str = Field(..., description="Response to the user") - destination: str = Field(..., description="Suggested destination") + destination: str + message: str -# ------------------------------- -# Structured Log Entry -# ------------------------------- +# ---------------------------------- +# Observer Interface +# ---------------------------------- + +class AgentCallObserver(Protocol): + def notify( + self, + agent_name: str, + prompt: str, + deps: TravelDeps, + output: BaseModel, + duration: float, + ) -> None: + ... + + +class ConsoleLogger(AgentCallObserver): + def notify( + self, + agent_name: str, + prompt: str, + deps: TravelDeps, + output: BaseModel, + duration: float, + ) -> None: + print("\n📋 Agent Call Log") + print(f"Agent: {agent_name}") + print(f"Prompt: {prompt}") + print(f"User: {deps.user_name}, Origin: {deps.origin_city}") + print(f"Output: {output.model_dump()}") + print(f"Duration: {duration:.2f}s") + + +# ---------------------------------- +# Wrapper to run agent with observers +# ---------------------------------- + +async def run_with_observers( + *, + agent: Agent[TravelDeps, BaseModel], + prompt: str, + deps: TravelDeps, + observers: list[AgentCallObserver], +) -> TravelResponse: + start = time.perf_counter() + result = await agent.run(prompt, deps=deps) + end = time.perf_counter() + duration = end - start + for observer in observers: + observer.notify( + agent_name=agent.name or "Unnamed Agent", + prompt=prompt, + deps=deps, + output=result.output, + duration=duration, + ) -class LogEntry(BaseModel): - level: str = Field(..., description="Log level: info, warning, error, etc.") - message: str = Field(..., description="What the agent did") - source: str = Field( - ..., description="The part of the system that generated the log" - ) + return result.output -# ------------------------------- -# Agent: Travel Recommender -# ------------------------------- +# ---------------------------------- +# Agent Definition +# ---------------------------------- travel_agent = Agent( "openai:gpt-4o", + name="TravelAgent", deps_type=TravelDeps, output_type=TravelResponse, - system_prompt="You are a helpful travel assistant. Recommend a good destination and respond politely.", + system_prompt="You are a friendly travel assistant. Recommend a destination based on user preferences.", ) -# ------------------------------- -# Log Agent: Parallel Output -# ------------------------------- - -log_agent = Agent( - "openai:gpt-4o", - deps_type=TravelDeps, - output_type=LogEntry, - system_prompt="Log what the travel agent just did in a structured format.", -) - - -# ------------------------------- -# Observer Interface -# ------------------------------- - - -class Observer: - def notify(self, log: LogEntry): - pass - +# ---------------------------------- +# Main Program +# ---------------------------------- -# ------------------------------- -# Example Concrete Observer -# ------------------------------- - - -class ConsoleLogger(Observer): - def notify(self, log: LogEntry): - print(f"[{log.level.upper()}] from {log.source}: {log.message}") - - -# ------------------------------- -# Execution Function -# ------------------------------- - - -async def recommend_travel( - user_prompt: str, deps: TravelDeps, observers: list[Observer] -): - # Step 1: Get the response from the agent - response_result = await travel_agent.run(user_prompt, deps=deps) - response = response_result.output - - # Step 2: Generate structured log from a separate agent - log_prompt = ( - f"The agent suggested {response.destination} in response to a user prompt." +async def main(): + deps = TravelDeps(user_name="Nina", origin_city="Copenhagen") + + prompt = "I want to escape to a cozy place in the mountains for the weekend." + output = await run_with_observers( + agent=travel_agent, + prompt=prompt, + deps=deps, + observers=[ConsoleLogger()], ) - log_result = await log_agent.run(log_prompt, deps=deps) - log_entry = log_result.output - # Step 3: Notify observers - for observer in observers: - observer.notify(log_entry) - - # Step 4: Show user response - print(f"\n👤 {deps.user_name} asked: {user_prompt}") - print(f"🤖 Travel Agent says: {response.message}") - print(f"📍 Destination Suggested: {response.destination}") - - -# ------------------------------- -# Main -# ------------------------------- - - -async def main(): - deps = TravelDeps(user_name="Alex") - observers = [ConsoleLogger()] - await recommend_travel("I want to go somewhere warm with beaches.", deps, observers) + print(f"\n🤖 Travel Agent says: {output.message}") + print(f"📍 Destination Suggested: {output.destination}") if __name__ == "__main__": - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file From 5051b1d6f469431cdba8785afd87128289b08dd7 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 12 Aug 2025 10:57:10 +0200 Subject: [PATCH 38/44] Added project code example. --- 2025/project/.env.example | 2 + 2025/project/.python-version | 1 + 2025/project/Dockerfile | 24 ++ 2025/project/README.md | 39 +++ 2025/project/app/api/v1/user.py | 47 +++ 2025/project/app/core/config.py | 19 ++ 2025/project/app/core/logging.py | 7 + 2025/project/app/db/schema.py | 18 ++ 2025/project/app/main.py | 15 + 2025/project/app/models/user.py | 10 + 2025/project/app/services/user_service.py | 38 +++ 2025/project/docker-compose.yaml | 9 + 2025/project/pyproject.toml | 18 ++ 2025/project/tests/api/v1/test_user.py | 34 ++ 2025/project/tests/test_db.py | 16 + 2025/project/uv.lock | 369 ++++++++++++++++++++++ 16 files changed, 666 insertions(+) create mode 100644 2025/project/.env.example create mode 100644 2025/project/.python-version create mode 100644 2025/project/Dockerfile create mode 100644 2025/project/README.md create mode 100644 2025/project/app/api/v1/user.py create mode 100644 2025/project/app/core/config.py create mode 100644 2025/project/app/core/logging.py create mode 100644 2025/project/app/db/schema.py create mode 100644 2025/project/app/main.py create mode 100644 2025/project/app/models/user.py create mode 100644 2025/project/app/services/user_service.py create mode 100644 2025/project/docker-compose.yaml create mode 100644 2025/project/pyproject.toml create mode 100644 2025/project/tests/api/v1/test_user.py create mode 100644 2025/project/tests/test_db.py create mode 100644 2025/project/uv.lock diff --git a/2025/project/.env.example b/2025/project/.env.example new file mode 100644 index 0000000..ae2acab --- /dev/null +++ b/2025/project/.env.example @@ -0,0 +1,2 @@ +db_user=your_db_user +db_password=your_db_password \ No newline at end of file diff --git a/2025/project/.python-version b/2025/project/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/2025/project/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/2025/project/Dockerfile b/2025/project/Dockerfile new file mode 100644 index 0000000..84147e4 --- /dev/null +++ b/2025/project/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.13-slim-bookworm + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +ADD https://astral.sh/uv/install.sh /install.sh +RUN chmod -R 755 /install.sh && /install.sh && rm /install.sh + +# Set up the UV environment path correctly +ENV PATH="/root/.local/bin:${PATH}" + +WORKDIR /app + +COPY . . + +RUN uv sync + +ENV PATH="/app/.venv/bin:{$PATH}" + +# Expose the specified port for FastAPI +EXPOSE $PORT + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] \ No newline at end of file diff --git a/2025/project/README.md b/2025/project/README.md new file mode 100644 index 0000000..90097a9 --- /dev/null +++ b/2025/project/README.md @@ -0,0 +1,39 @@ +Starting the server: + +uv run uvicorn app.main:app --reload + +Example requests: + +Here’s a set of simple curl examples you can use to interact with your FastAPI app once it’s running (default at http://localhost:8000): + +1️⃣ Create a User + +curl -X POST "http://localhost:8000/api/v1/users" \ + -H "Content-Type: application/json" \ + -d '{"name": "Ada Lovelace"}' + + +2️⃣ Get All Users + +curl -X GET "http://localhost:8000/api/v1/users" + + +3️⃣ Get a User by ID + +(Replace 1 with the actual ID from the create response) + +curl -X GET "http://localhost:8000/api/v1/users/1" + + +4️⃣ Update a User + +curl -X PUT "http://localhost:8000/api/v1/users/1" \ + -H "Content-Type: application/json" \ + -d '{"name": "Grace Hopper"}' + + +⸻ + +5️⃣ Delete a User + +curl -X DELETE "http://localhost:8000/api/v1/users/1" \ No newline at end of file diff --git a/2025/project/app/api/v1/user.py b/2025/project/app/api/v1/user.py new file mode 100644 index 0000000..565afbd --- /dev/null +++ b/2025/project/app/api/v1/user.py @@ -0,0 +1,47 @@ +from fastapi import APIRouter, Depends, HTTPException + +from app.db.schema import SessionLocal +from app.models.user import UserCreate, UserRead +from app.services.user_service import UserService + +router = APIRouter() + + +def get_user_service() -> UserService: + return UserService(session=SessionLocal()) + + +@router.get("/users", response_model=list[UserRead]) +def get_users(service: UserService = Depends(get_user_service)): + return service.list_users() + + +@router.post("/users", response_model=UserRead) +def create_user(user: UserCreate, service: UserService = Depends(get_user_service)): + return service.create_user(user.name) + + +@router.get("/users/{user_id}", response_model=UserRead) +def get_user(user_id: int, service: UserService = Depends(get_user_service)): + user = service.get_user(user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + return user + + +@router.put("/users/{user_id}", response_model=UserRead) +def update_user( + user_id: int, user: UserCreate, service: UserService = Depends(get_user_service) +): + updated = service.update_user(user_id, user.name) + if not updated: + raise HTTPException(status_code=404, detail="User not found") + return updated + + +@router.delete("/users/{user_id}") +def delete_user(user_id: int, service: UserService = Depends(get_user_service)): + success = service.delete_user(user_id) + if not success: + raise HTTPException(status_code=404, detail="User not found") + return {"success": True} diff --git a/2025/project/app/core/config.py b/2025/project/app/core/config.py new file mode 100644 index 0000000..e573715 --- /dev/null +++ b/2025/project/app/core/config.py @@ -0,0 +1,19 @@ +from dotenv import load_dotenv +from pydantic_settings import BaseSettings + +load_dotenv() + + +class Config(BaseSettings): + app_name: str = "ScalableFastAPIProject" + debug: bool = False + db_user: str = "" + db_password: str = "" + db_name: str = "test.db" + + @property + def db_url(https://github.com/self): + return f"sqlite:///./{self.db_name}" + + +config = Config() diff --git a/2025/project/app/core/logging.py b/2025/project/app/core/logging.py new file mode 100644 index 0000000..3d787a5 --- /dev/null +++ b/2025/project/app/core/logging.py @@ -0,0 +1,7 @@ +import logging + +def setup_logging(): + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s [%(name)s] %(message)s" + ) \ No newline at end of file diff --git a/2025/project/app/db/schema.py b/2025/project/app/db/schema.py new file mode 100644 index 0000000..830d39e --- /dev/null +++ b/2025/project/app/db/schema.py @@ -0,0 +1,18 @@ +from sqlalchemy import String, create_engine +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker + +from app.core.config import config + +engine = create_engine(config.db_url, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +class Base(DeclarativeBase): + pass + + +class User(Base): + __tablename__ = "users" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String, index=True) diff --git a/2025/project/app/main.py b/2025/project/app/main.py new file mode 100644 index 0000000..dd88371 --- /dev/null +++ b/2025/project/app/main.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI + +from app.api.v1 import user +from app.core.config import config +from app.core.logging import setup_logging +from app.db.schema import Base, engine + +setup_logging() +Base.metadata.create_all(bind=engine) + +app = FastAPI(title=config.app_name) + + +# Register routes +app.include_router(user.router, prefix="/api/v1") diff --git a/2025/project/app/models/user.py b/2025/project/app/models/user.py new file mode 100644 index 0000000..c37604e --- /dev/null +++ b/2025/project/app/models/user.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class UserCreate(BaseModel): + name: str + + +class UserRead(BaseModel): + id: int + name: str diff --git a/2025/project/app/services/user_service.py b/2025/project/app/services/user_service.py new file mode 100644 index 0000000..c0cf44b --- /dev/null +++ b/2025/project/app/services/user_service.py @@ -0,0 +1,38 @@ +from sqlalchemy.orm import Session + +from app.db.schema import User + + +class UserService: + def __init__(self, session: Session): + self._db = session + + def list_users(self) -> list[User]: + return self._db.query(User).all() + + def get_user(self, user_id: int) -> User | None: + return self._db.query(User).filter(User.id == user_id).first() + + def create_user(self, name: str) -> User: + user = User(name=name) + self._db.add(user) + self._db.commit() + self._db.refresh(user) + return user + + def update_user(self, user_id: int, name: str) -> User | None: + user = self.get_user(user_id) + if not user: + return None + user.name = name + self._db.commit() + self._db.refresh(user) + return user + + def delete_user(self, user_id: int) -> bool: + user = self.get_user(user_id) + if not user: + return False + self._db.delete(user) + self._db.commit() + return True diff --git a/2025/project/docker-compose.yaml b/2025/project/docker-compose.yaml new file mode 100644 index 0000000..856a126 --- /dev/null +++ b/2025/project/docker-compose.yaml @@ -0,0 +1,9 @@ +services: + web: + build: . + ports: + - "8000:80" + environment: + - DB_USER=admin + - DB_PASSWORD=secret + - DB_NAME=test.db \ No newline at end of file diff --git a/2025/project/pyproject.toml b/2025/project/pyproject.toml new file mode 100644 index 0000000..b74fbd7 --- /dev/null +++ b/2025/project/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "recording" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "fastapi>=0.116.1", + "httpx>=0.28.1", + "pydantic-settings>=2.10.1", + "pytest>=8.4.1", + "python-dotenv>=1.1.1", + "sqlalchemy>=2.0.42", + "uvicorn>=0.35.0", +] + +[tool.pytest.ini_options] +pythonpath = "." \ No newline at end of file diff --git a/2025/project/tests/api/v1/test_user.py b/2025/project/tests/api/v1/test_user.py new file mode 100644 index 0000000..781e66b --- /dev/null +++ b/2025/project/tests/api/v1/test_user.py @@ -0,0 +1,34 @@ +from fastapi.testclient import TestClient + +from app.api.v1.user import get_user_service +from app.main import app +from app.services.user_service import UserService +from tests.test_db import TestingSessionLocal + +# Setup the TestClient +client = TestClient(app) + + +# Dependency to override the get_db dependency in the main app +def override_get_user_service(): + session = TestingSessionLocal() + yield UserService(session=session) + + +app.dependency_overrides[get_user_service] = override_get_user_service + + +def test_create_and_get_user(): + # Create a new user + response = client.post("/api/v1/users", json={"name": "Test User"}) + assert response.status_code == 200 + created_user = response.json() + assert created_user["name"] == "Test User" + assert "id" in created_user + + # Fetch the same user + get_response = client.get(f"/api/v1/users/{created_user['id']}") + assert get_response.status_code == 200 + fetched_user = get_response.json() + assert fetched_user["id"] == created_user["id"] + assert fetched_user["name"] == "Test User" diff --git a/2025/project/tests/test_db.py b/2025/project/tests/test_db.py new file mode 100644 index 0000000..6c0c311 --- /dev/null +++ b/2025/project/tests/test_db.py @@ -0,0 +1,16 @@ +from sqlalchemy import StaticPool, create_engine +from sqlalchemy.orm import sessionmaker + +from app.db.schema import Base + +# Setup the in-memory SQLite database for testing +DATABASE_URL = "sqlite:///:memory:" +engine = create_engine( + DATABASE_URL, + connect_args={ + "check_same_thread": False, + }, + poolclass=StaticPool, +) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base.metadata.create_all(bind=engine) diff --git a/2025/project/uv.lock b/2025/project/uv.lock new file mode 100644 index 0000000..a622bd2 --- /dev/null +++ b/2025/project/uv.lock @@ -0,0 +1,369 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "inject" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/b6/762d2dac6b587698abde000dc0fe85dfe9cd469cbda8856699a84adee82a/inject-5.3.0.tar.gz", hash = "sha256:bc9db0fe05a42990dbdaf570db085c409bc8d1a9dea6d06143049476b922abba", size = 26482, upload-time = "2025-06-20T10:20:51.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/6e/b00ef8fe9a43aa3a6f5687b710832f0d876c0812bd0ce1c3af3e71bf7dd1/inject-5.3.0-py2.py3-none-any.whl", hash = "sha256:4758eb6c464d3e2badbbf65ac991c64752b05429d6af4c3c0e5b2765efaf7e73", size = 14349, upload-time = "2025-06-20T10:20:50.717Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "recording" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi" }, + { name = "httpx" }, + { name = "inject" }, + { name = "pydantic-settings" }, + { name = "pytest" }, + { name = "python-dotenv" }, + { name = "sqlalchemy" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.116.1" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "inject", specifier = ">=5.3.0" }, + { name = "pydantic-settings", specifier = ">=2.10.1" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, + { name = "sqlalchemy", specifier = ">=2.0.42" }, + { name = "uvicorn", specifier = ">=0.35.0" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.42" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/03/a0af991e3a43174d6b83fca4fb399745abceddd1171bdabae48ce877ff47/sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f", size = 9749972, upload-time = "2025-07-29T12:48:09.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/7e/25d8c28b86730c9fb0e09156f601d7a96d1c634043bf8ba36513eb78887b/sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c", size = 2127905, upload-time = "2025-07-29T13:29:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a1/9d8c93434d1d983880d976400fcb7895a79576bd94dca61c3b7b90b1ed0d/sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01", size = 2115726, upload-time = "2025-07-29T13:29:23.496Z" }, + { url = "https://files.pythonhosted.org/packages/a2/cc/d33646fcc24c87cc4e30a03556b611a4e7bcfa69a4c935bffb923e3c89f4/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9", size = 3246007, upload-time = "2025-07-29T13:26:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/67/08/4e6c533d4c7f5e7c4cbb6fe8a2c4e813202a40f05700d4009a44ec6e236d/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04", size = 3250919, upload-time = "2025-07-29T13:22:33.74Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/f680e9a636d217aece1b9a8030d18ad2b59b5e216e0c94e03ad86b344af3/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894", size = 3180546, upload-time = "2025-07-29T13:26:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a2/8c8f6325f153894afa3775584c429cc936353fb1db26eddb60a549d0ff4b/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350", size = 3216683, upload-time = "2025-07-29T13:22:34.977Z" }, + { url = "https://files.pythonhosted.org/packages/39/44/3a451d7fa4482a8ffdf364e803ddc2cfcafc1c4635fb366f169ecc2c3b11/sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f", size = 2093990, upload-time = "2025-07-29T13:16:13.036Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9e/9bce34f67aea0251c8ac104f7bdb2229d58fb2e86a4ad8807999c4bee34b/sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577", size = 2120473, upload-time = "2025-07-29T13:16:14.502Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/ba2546ab09a6adebc521bf3974440dc1d8c06ed342cceb30ed62a8858835/sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835", size = 1922072, upload-time = "2025-07-29T13:09:17.061Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] From 588a82e8e885b5824ec1dafc72eb25daae5a902a Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 12 Aug 2025 13:09:31 +0200 Subject: [PATCH 39/44] Removed unneccesary environment variable setting. --- 2025/aidesign/chain_of_responsibility.py | 2 -- 2025/aidesign/observer.py | 2 -- 2025/aidesign/strategy.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/2025/aidesign/chain_of_responsibility.py b/2025/aidesign/chain_of_responsibility.py index 01573ed..6238108 100644 --- a/2025/aidesign/chain_of_responsibility.py +++ b/2025/aidesign/chain_of_responsibility.py @@ -1,5 +1,4 @@ import asyncio -import os from dataclasses import dataclass from typing import Optional @@ -9,7 +8,6 @@ # Load env vars load_dotenv() -os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") # ---------------------------------- # Shared Dependencies diff --git a/2025/aidesign/observer.py b/2025/aidesign/observer.py index a3e5c3d..265f21d 100644 --- a/2025/aidesign/observer.py +++ b/2025/aidesign/observer.py @@ -1,5 +1,4 @@ import asyncio -import os import time from dataclasses import dataclass from typing import Protocol @@ -9,7 +8,6 @@ from pydantic_ai import Agent load_dotenv() -os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") # ---------------------------------- diff --git a/2025/aidesign/strategy.py b/2025/aidesign/strategy.py index 59f73c7..d8dd899 100644 --- a/2025/aidesign/strategy.py +++ b/2025/aidesign/strategy.py @@ -1,5 +1,4 @@ import asyncio -import os from dataclasses import dataclass from typing import Callable @@ -9,7 +8,6 @@ # Load API key load_dotenv() -os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") # ---------------------------------- # Common Output Model From adc713d2e522b8773e9c320e1c41cba9b2209b9f Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 12 Aug 2025 14:07:36 +0200 Subject: [PATCH 40/44] Added pyproject file. Minor cleanup. Added thread-safe example. --- 2025/singleton/instance_control.py | 13 +++------- 2025/singleton/metaclass.py | 14 ++++------- 2025/singleton/pyproject.toml | 7 ++++++ 2025/singleton/singleton.py | 8 ++++++ 2025/singleton/singleton_multi.py | 13 +++------- 2025/singleton/singleton_safe.py | 29 ++++++++++++++++++++++ 2025/singleton/singleton_threading.py | 14 ++++------- 2025/singleton/singleton_threading_safe.py | 18 ++++++++++++++ 2025/singleton/uv.lock | 8 ++++++ 9 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 2025/singleton/pyproject.toml create mode 100644 2025/singleton/singleton.py create mode 100644 2025/singleton/singleton_safe.py create mode 100644 2025/singleton/singleton_threading_safe.py create mode 100644 2025/singleton/uv.lock diff --git a/2025/singleton/instance_control.py b/2025/singleton/instance_control.py index 9306188..9320c3f 100644 --- a/2025/singleton/instance_control.py +++ b/2025/singleton/instance_control.py @@ -1,11 +1,4 @@ -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - print(f"Creating instance of {cls.__name__}") - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] +from singleton import Singleton class ModelLoader(metaclass=Singleton): @@ -20,10 +13,12 @@ def predict(data: str) -> str: model = ModelLoader() return model.predict(data) + def main() -> None: predictions = [predict(f"data_{i}") for i in range(5)] for pred in predictions: print(pred) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/singleton/metaclass.py b/2025/singleton/metaclass.py index b023781..d1eef02 100644 --- a/2025/singleton/metaclass.py +++ b/2025/singleton/metaclass.py @@ -1,11 +1,5 @@ -class Singleton(type): - _instances = {} +from singleton import Singleton - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - print(f"Creating instance of {cls.__name__}") - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] class Config(metaclass=Singleton): def __init__(self): @@ -14,7 +8,8 @@ def __init__(self): def __str__(self) -> str: return f"Config(db_url={self.db_url}, debug={self.debug})" - + + def main() -> None: s1 = Config() s2 = Config() @@ -23,5 +18,6 @@ def main() -> None: print(id(s1)) # Prints the instance id print(id(s2)) # Prints the same instance id as s1 + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/singleton/pyproject.toml b/2025/singleton/pyproject.toml new file mode 100644 index 0000000..7f778fc --- /dev/null +++ b/2025/singleton/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "singleton" +version = "0.1.0" +description = "Singleton code example" +requires-python = ">=3.13" +dependencies = [ +] diff --git a/2025/singleton/singleton.py b/2025/singleton/singleton.py new file mode 100644 index 0000000..7fa6970 --- /dev/null +++ b/2025/singleton/singleton.py @@ -0,0 +1,8 @@ +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/2025/singleton/singleton_multi.py b/2025/singleton/singleton_multi.py index ec35a75..03a16fa 100644 --- a/2025/singleton/singleton_multi.py +++ b/2025/singleton/singleton_multi.py @@ -1,11 +1,5 @@ -class Singleton(type): - _instances = {} +from singleton import Singleton - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - print(f"Creating instance of {cls.__name__}") - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] class Config(metaclass=Singleton): def __init__(self): @@ -14,7 +8,8 @@ def __init__(self): def __str__(self) -> str: return f"Config(db_url={self.db_url}, debug={self.debug})" - + + def main() -> None: c1 = Config() c2 = Config.__new__(Config) @@ -22,6 +17,6 @@ def main() -> None: print(c1 is c2) # ❌ False + if __name__ == "__main__": main() - diff --git a/2025/singleton/singleton_safe.py b/2025/singleton/singleton_safe.py new file mode 100644 index 0000000..3b955f6 --- /dev/null +++ b/2025/singleton/singleton_safe.py @@ -0,0 +1,29 @@ +# singleton.py +from __future__ import annotations + +import threading +from typing import Any + + +class Singleton(type): + _instances: dict[type, Any] = {} + _locks: dict[type, threading.Lock] = {} + _global_lock = threading.Lock() # protects _locks map creation + + def __call__(cls, *args, **kwargs): + # Fast path: already created + if cls in cls._instances: + return cls._instances[cls] + + # Ensure a per-class lock exists + with cls._global_lock: + lock = cls._locks.setdefault(cls, threading.Lock()) + + # Double-checked locking: only one thread creates the instance + with lock: + if cls not in cls._instances: + print(f"Creating instance of {cls.__name__}") + instance = super().__call__(*args, **kwargs) # calls __init__ once + cls._instances[cls] = instance + + return cls._instances[cls] diff --git a/2025/singleton/singleton_threading.py b/2025/singleton/singleton_threading.py index e1b6d2b..d3518c1 100644 --- a/2025/singleton/singleton_threading.py +++ b/2025/singleton/singleton_threading.py @@ -1,22 +1,18 @@ from threading import Thread -class Singleton(type): - _instances = {} +from singleton import Singleton + - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - print(f"Creating instance of {cls.__name__}") - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] - class Unsafe(metaclass=Singleton): def __init__(self): print("Initializing...") + def main() -> None: threads = [Thread(target=Unsafe) for _ in range(20)] [t.start() for t in threads] [t.join() for t in threads] + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/2025/singleton/singleton_threading_safe.py b/2025/singleton/singleton_threading_safe.py new file mode 100644 index 0000000..4dd453e --- /dev/null +++ b/2025/singleton/singleton_threading_safe.py @@ -0,0 +1,18 @@ +from threading import Thread + +from singleton_safe import Singleton + + +class Unsafe(metaclass=Singleton): + def __init__(self): + print("Initializing...") + + +def main() -> None: + threads = [Thread(target=Unsafe) for _ in range(20)] + [t.start() for t in threads] + [t.join() for t in threads] + + +if __name__ == "__main__": + main() diff --git a/2025/singleton/uv.lock b/2025/singleton/uv.lock new file mode 100644 index 0000000..e7a4c07 --- /dev/null +++ b/2025/singleton/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "singleton" +version = "0.1.0" +source = { virtual = "." } From 8ccae72c9d47fbd6cd6966ede3dfbcdac40fdbd0 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Wed, 3 Sep 2025 15:19:19 +0200 Subject: [PATCH 41/44] Added registry code example. --- 2025/registry/README.md | 5 ++ 2025/registry/basic_example.py | 61 +++++++++++++ 2025/registry/commands/__init__.py | 0 2025/registry/commands/text/__init__.py | 0 2025/registry/commands/text/count.py | 8 ++ 2025/registry/commands/text/reverse.py | 8 ++ 2025/registry/main.py | 42 +++++++++ 2025/registry/plugins/__init__.py | 0 2025/registry/plugins/shout.py | 7 ++ 2025/registry/pyproject.toml | 8 ++ 2025/registry/registry.py | 13 +++ 2025/registry/uv.lock | 115 ++++++++++++++++++++++++ 12 files changed, 267 insertions(+) create mode 100644 2025/registry/README.md create mode 100644 2025/registry/basic_example.py create mode 100644 2025/registry/commands/__init__.py create mode 100644 2025/registry/commands/text/__init__.py create mode 100644 2025/registry/commands/text/count.py create mode 100644 2025/registry/commands/text/reverse.py create mode 100644 2025/registry/main.py create mode 100644 2025/registry/plugins/__init__.py create mode 100644 2025/registry/plugins/shout.py create mode 100644 2025/registry/pyproject.toml create mode 100644 2025/registry/registry.py create mode 100644 2025/registry/uv.lock diff --git a/2025/registry/README.md b/2025/registry/README.md new file mode 100644 index 0000000..7fbccd2 --- /dev/null +++ b/2025/registry/README.md @@ -0,0 +1,5 @@ +Examples of commands: + +- uv run main.py text count "This is a registry pattern example" +- uv run main.py text shout "plugin power" +- uv run main.py text reverse "Was it a car or a cat I saw" \ No newline at end of file diff --git a/2025/registry/basic_example.py b/2025/registry/basic_example.py new file mode 100644 index 0000000..71cb543 --- /dev/null +++ b/2025/registry/basic_example.py @@ -0,0 +1,61 @@ +from functools import wraps +from typing import Any, Callable + +type Data = dict[str, Any] +type ExportFn = Callable[[Data], None] + +# The registry: maps format name to export function +exporters: dict[str, ExportFn] = {} + + +def register_exporter(name: str): + def decorator(func: ExportFn): + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + return func(*args, **kwargs) + + exporters[name] = wrapper + return wrapper + + return decorator + + +@register_exporter("pdf") +def export_pdf(data: Data) -> None: + print(f"Exporting data to PDF: {data}") + + +@register_exporter("csv") +def export_csv(data: Data) -> None: + print(f"Exporting data to CSV: {data}") + + +@register_exporter("json") +def export_json(data: Data) -> None: + import json + + print("Exporting data to JSON:") + print(json.dumps(data, indent=2)) + + +def export_data(data: Data, format: str) -> None: + exporter = exporters.get(format) + if exporter is None: + raise ValueError(f"❌ No exporter found for format: {format}") + exporter(data) + + +def main() -> None: + sample_data: Data = {"name": "Alice", "age": 30} + + # Try exporting in different formats + export_data(sample_data, "pdf") + export_data(sample_data, "csv") + export_data(sample_data, "json") + + # This would raise an error: + export_data(sample_data, "xlsx") + + +if __name__ == "__main__": + main() diff --git a/2025/registry/commands/__init__.py b/2025/registry/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/registry/commands/text/__init__.py b/2025/registry/commands/text/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/registry/commands/text/count.py b/2025/registry/commands/text/count.py new file mode 100644 index 0000000..7da82f8 --- /dev/null +++ b/2025/registry/commands/text/count.py @@ -0,0 +1,8 @@ +from registry import register_command +from rich import print + + +@register_command("text", "count") +def count_words(text: str) -> None: + word_count = len(text.split()) + print(f"[green]Word count:[/green] [bold]{word_count}[/bold]") diff --git a/2025/registry/commands/text/reverse.py b/2025/registry/commands/text/reverse.py new file mode 100644 index 0000000..f3657d5 --- /dev/null +++ b/2025/registry/commands/text/reverse.py @@ -0,0 +1,8 @@ +from registry import register_command +from rich import print + + +@register_command("text", "reverse") +def reverse_text(text: str) -> None: + reversed_text = text[::-1] + print(f"[cyan]Reversed:[/cyan] [bold]{reversed_text}[/bold]") diff --git a/2025/registry/main.py b/2025/registry/main.py new file mode 100644 index 0000000..ee151fb --- /dev/null +++ b/2025/registry/main.py @@ -0,0 +1,42 @@ +import importlib +import pkgutil + +import typer +from registry import get_registry + +app = typer.Typer() + + +def load_text_commands() -> None: + import commands.text + + for _, module_name, _ in pkgutil.iter_modules(commands.text.__path__): + importlib.import_module(f"commands.text.{module_name}") + + +def load_plugins() -> None: + import plugins + + for _, module_name, _ in pkgutil.iter_modules(plugins.__path__): + importlib.import_module(f"plugins.{module_name}") + + +def register_with_typer() -> None: + group_apps: dict[str, typer.Typer] = {} + + for group, name, func in get_registry(): + if group not in group_apps: + group_apps[group] = typer.Typer() + app.add_typer(group_apps[group], name=group) + group_apps[group].command(name)(func) + + +def main() -> None: + load_text_commands() + load_plugins() + register_with_typer() + app() + + +if __name__ == "__main__": + main() diff --git a/2025/registry/plugins/__init__.py b/2025/registry/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/2025/registry/plugins/shout.py b/2025/registry/plugins/shout.py new file mode 100644 index 0000000..743fe5f --- /dev/null +++ b/2025/registry/plugins/shout.py @@ -0,0 +1,7 @@ +from registry import register_command +from rich import print + + +@register_command("text", "shout") +def shout(text: str) -> None: + print(f"[bold red]{text.upper()}!!![/bold red]") diff --git a/2025/registry/pyproject.toml b/2025/registry/pyproject.toml new file mode 100644 index 0000000..7a0c659 --- /dev/null +++ b/2025/registry/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "registry" +version = "0.1.0" +requires-python = ">=3.13" +dependencies = [ + "rich>=14.1.0", + "typer>=0.17.3", +] diff --git a/2025/registry/registry.py b/2025/registry/registry.py new file mode 100644 index 0000000..b7dee79 --- /dev/null +++ b/2025/registry/registry.py @@ -0,0 +1,13 @@ + +from typing import Callable + +_registry: list[tuple[str, str, Callable[..., None]]] = [] + +def register_command(group: str, name: str): + def decorator(func: Callable[..., None]): + _registry.append((group, name, func)) + return func + return decorator + +def get_registry() -> list[tuple[str, str, Callable[..., None]]]: + return _registry.copy() diff --git a/2025/registry/uv.lock b/2025/registry/uv.lock new file mode 100644 index 0000000..7325138 --- /dev/null +++ b/2025/registry/uv.lock @@ -0,0 +1,115 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "registry" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "rich" }, + { name = "typer" }, +] + +[package.metadata] +requires-dist = [ + { name = "rich", specifier = ">=14.1.0" }, + { name = "typer", specifier = ">=0.17.3" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "typer" +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/82/f4bfed3bc18c6ebd6f828320811bbe4098f92a31adf4040bee59c4ae02ea/typer-0.17.3.tar.gz", hash = "sha256:0c600503d472bcf98d29914d4dcd67f80c24cc245395e2e00ba3603c9332e8ba", size = 103517, upload-time = "2025-08-30T12:35:24.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/e8/b3d537470e8404659a6335e7af868e90657efb73916ef31ddf3d8b9cb237/typer-0.17.3-py3-none-any.whl", hash = "sha256:643919a79182ab7ac7581056d93c6a2b865b026adf2872c4d02c72758e6f095b", size = 46494, upload-time = "2025-08-30T12:35:22.391Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From e9d3d1060d20d8e7357e120659f10f648f8fac33 Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 9 Sep 2025 15:02:14 +0200 Subject: [PATCH 42/44] Added context object example. --- 2025/context/.python-version | 1 + 2025/context/before.py | 38 ++++++++++++++++++ 2025/context/context_v1.py | 57 ++++++++++++++++++++++++++ 2025/context/context_v2.py | 78 ++++++++++++++++++++++++++++++++++++ 2025/context/db.py | 34 ++++++++++++++++ 2025/context/pyproject.toml | 8 ++++ 2025/context/uv.lock | 68 +++++++++++++++++++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 2025/context/.python-version create mode 100644 2025/context/before.py create mode 100644 2025/context/context_v1.py create mode 100644 2025/context/context_v2.py create mode 100644 2025/context/db.py create mode 100644 2025/context/pyproject.toml create mode 100644 2025/context/uv.lock diff --git a/2025/context/.python-version b/2025/context/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/2025/context/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/2025/context/before.py b/2025/context/before.py new file mode 100644 index 0000000..ec5aaa9 --- /dev/null +++ b/2025/context/before.py @@ -0,0 +1,38 @@ +import logging +from db import db_session, init_db, Article, Session + +# ----------------------------------- +# Application Logic +# ----------------------------------- + +def render_article(article_id: int, db: Session, logger: logging.Logger, api_key: str) -> str: + article = db.query(Article).filter(Article.id == article_id).first() + if not article: + logger.error(f"Article {article_id} not found.") + return "

Article not found.

" + + logger.info(f"Rendering article {article_id} using API key {api_key[:4]}...") + html = f"

{article.title}

{article.body}

" + return html + +def main() -> None: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger("app") + + init_db() + + with db_session() as session: + + api_key = "abcdef123456" + + html = render_article(1, session, logger, api_key) + print(html) + + html = render_article(999, session, logger, api_key) # not found + print(html) + + +if __name__ == "__main__": + main() + + diff --git a/2025/context/context_v1.py b/2025/context/context_v1.py new file mode 100644 index 0000000..550eff0 --- /dev/null +++ b/2025/context/context_v1.py @@ -0,0 +1,57 @@ +import logging +from dataclasses import dataclass +from typing import Any +from db import db_session, init_db, Article , Session + +# ----------------------------------- +# Context Object +# ----------------------------------- + +@dataclass +class AppContext: + user_id: int + db: Session + logger: logging.Logger + config: dict[str, Any] + +# ----------------------------------- +# Application Logic +# ----------------------------------- + +def render_article(article_id: int, context: AppContext) -> str: + article = context.db.query(Article).filter(Article.id == article_id).first() + if not article: + context.logger.error(f"Article {article_id} not found.") + return "

Article not found.

" + + context.logger.info(f"Rendering article {article_id}") + html = f"

{article.title}

{article.body}

" + return html +# ----------------------------------- +# Entry Point +# ----------------------------------- + +def main() -> None: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger("app") + + init_db() + + with db_session() as session: + context = AppContext( + user_id=42, + db=session, + logger=logger, + config={"api_key": "abcdef123456"}, + ) + + html = render_article(1, context) + print(html) + + html = render_article(999, context) # not found + print(html) + +if __name__ == "__main__": + main() + + diff --git a/2025/context/context_v2.py b/2025/context/context_v2.py new file mode 100644 index 0000000..1bc45b5 --- /dev/null +++ b/2025/context/context_v2.py @@ -0,0 +1,78 @@ +import logging +from dataclasses import dataclass +from typing import Protocol, Any +from db import db_session, init_db, Article + +# ----------------------------------- +# Protocols for loose coupling +# ----------------------------------- + +class LoggerProtocol(Protocol): + def info(self, msg: str, *args: Any, **kwargs: Any) -> None: ... + def error(self, msg: str, *args: Any, **kwargs: Any) -> None: ... + +class DBProtocol(Protocol): + def query(self, *args: Any, **kwargs: Any) -> Any: ... + +# ----------------------------------- +# Context Object +# ----------------------------------- + +@dataclass +class AppContext: + user_id: int + db: DBProtocol + logger: LoggerProtocol + config: dict[str, Any] + +# ----------------------------------- +# Application Logic +# ----------------------------------- + +def render_article(article_id: int, db: DBProtocol, logger: LoggerProtocol) -> str: + article = db.query(Article).filter(Article.id == article_id).first() + if not article: + logger.error(f"Article {article_id} not found.") + return "

Article not found.

" + + logger.info(f"Rendering article {article_id}") + html = f"

{article.title}

{article.body}

" + return html + +def send_to_external_service(html: str, api_key: str) -> None: + print(f"Sending to API with key {api_key[:4]}... Content: {html[:30]}...") + +def publish_article(article_id: int, context: AppContext) -> None: + html = render_article( + article_id, + db=context.db, + logger=context.logger, + ) + send_to_external_service(html, context.config['api_key']) + +# ----------------------------------- +# Entry Point +# ----------------------------------- + +def main() -> None: + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger("app") + + init_db() + + with db_session() as session: + context = AppContext( + user_id=42, + db=session, + logger=logger, + config={"api_key": "abcdef123456"}, + ) + + publish_article(1, context) + publish_article(2, context) + publish_article(999, context) # Not found example + +if __name__ == "__main__": + main() + + diff --git a/2025/context/db.py b/2025/context/db.py new file mode 100644 index 0000000..cbd8b57 --- /dev/null +++ b/2025/context/db.py @@ -0,0 +1,34 @@ +from contextlib import contextmanager +from sqlalchemy import Column, Integer, String, create_engine +from sqlalchemy.orm import Session, declarative_base, sessionmaker +from typing import Generator + +Base = declarative_base() + +class Article(Base): + __tablename__ = "articles" + + id = Column(Integer, primary_key=True) + title = Column(String) + body = Column(String) + +# SQLite in-memory DB for demo purposes +engine = create_engine("sqlite:///:memory:", echo=False, future=True) +SessionLocal = sessionmaker(bind=engine, expire_on_commit=False) + +def init_db() -> None: + Base.metadata.create_all(bind=engine) + with db_session() as session: + session.add_all([ + Article(id=1, title="Hello", body="World!"), + Article(id=2, title="Python", body="Is Awesome"), + ]) + session.commit() + +@contextmanager +def db_session() -> Generator[Session, None, None]: + session = SessionLocal() + try: + yield session + finally: + session.close() \ No newline at end of file diff --git a/2025/context/pyproject.toml b/2025/context/pyproject.toml new file mode 100644 index 0000000..b700562 --- /dev/null +++ b/2025/context/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "context-object-example" +version = "0.1.0" +description = "An example of the Context Object Pattern using SQLite and SQLAlchemy" +requires-python = ">=3.13" +dependencies = [ + "sqlalchemy>=2.0", +] diff --git a/2025/context/uv.lock b/2025/context/uv.lock new file mode 100644 index 0000000..287b194 --- /dev/null +++ b/2025/context/uv.lock @@ -0,0 +1,68 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "context-object-example" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "sqlalchemy" }, +] + +[package.metadata] +requires-dist = [{ name = "sqlalchemy", specifier = ">=2.0" }] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From ecb422128e0a1c8317bc333f98463badeaebce2d Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Thu, 11 Sep 2025 16:26:56 +0200 Subject: [PATCH 43/44] Added Python 3.14 code examples. --- 2025/python314/annot.py | 17 +++++++ 2025/python314/compress.py | 67 +++++++++++++++++++++++++ 2025/python314/exception_parenthesis.py | 18 +++++++ 2025/python314/pyproject.toml | 7 +++ 2025/python314/template_strings.py | 42 ++++++++++++++++ 2025/python314/uv.lock | 8 +++ 6 files changed, 159 insertions(+) create mode 100644 2025/python314/annot.py create mode 100644 2025/python314/compress.py create mode 100644 2025/python314/exception_parenthesis.py create mode 100644 2025/python314/pyproject.toml create mode 100644 2025/python314/template_strings.py create mode 100644 2025/python314/uv.lock diff --git a/2025/python314/annot.py b/2025/python314/annot.py new file mode 100644 index 0000000..6faa8ce --- /dev/null +++ b/2025/python314/annot.py @@ -0,0 +1,17 @@ +import annotationlib + +# You no longer need string annotations or `from __future__ import annotations` +class Node: + def __init__(self, value: int, next: Node | None = None): # This works fine in Python 3.14 + self.value = value + self.next = next + +def my_function(x: int, y: Node | None) -> bool: + return x > 0 and y is not None + +def main() -> None: + sig = annotationlib.get_annotations(my_function) + print(sig) + +if __name__ == "__main__": + main() diff --git a/2025/python314/compress.py b/2025/python314/compress.py new file mode 100644 index 0000000..b699172 --- /dev/null +++ b/2025/python314/compress.py @@ -0,0 +1,67 @@ +import time +import gzip +import bz2 +import compression.zstd as zstd # New in Python 3.14 + +from typing import Callable + +type CompressionFunction = Callable[[bytes], bytes] + + +def benchmark_compression( + name: str, + compress_fn: CompressionFunction, + decompress_fn: CompressionFunction, + data: bytes +) -> None: + """Benchmark compression and decompression performance.""" + # Compress + start = time.perf_counter() + compressed: bytes = compress_fn(data) + compress_time: float = time.perf_counter() - start + + # Decompress + start = time.perf_counter() + decompressed: bytes = decompress_fn(compressed) + decompress_time: float = time.perf_counter() - start + + assert decompressed == data, f"{name} decompressed data mismatch!" + + print( + f"{name:>6} | Size: {len(compressed):7} bytes | " + f"Compress: {compress_time * 1000:7.2f} ms | " + f"Decompress: {decompress_time * 1000:7.2f} ms" + ) + + +def main() -> None: + data: bytes = ( + b"ArjanCodes is the best Python channel on YouTube!" * 50_000 + ) + + print("== Compression Benchmark ==") + + benchmark_compression( + "zstd", + compress_fn=zstd.compress, + decompress_fn=zstd.decompress, + data=data, + ) + + benchmark_compression( + "gzip", + compress_fn=gzip.compress, + decompress_fn=gzip.decompress, + data=data, + ) + + benchmark_compression( + "bz2", + compress_fn=bz2.compress, + decompress_fn=bz2.decompress, + data=data, + ) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/python314/exception_parenthesis.py b/2025/python314/exception_parenthesis.py new file mode 100644 index 0000000..37b5eea --- /dev/null +++ b/2025/python314/exception_parenthesis.py @@ -0,0 +1,18 @@ +def risky_function(value: str) -> int: + if value == "int": + return int("not a number") # raises ValueError + elif value == "key": + return {"a": 1}["b"] # raises KeyError + else: + raise RuntimeError("Something else went wrong") + +def main() -> None: + try: + risky_function("key") + except ValueError, KeyError: + print("Caught either ValueError or KeyError") + except RuntimeError as e: + print(f"Caught a runtime error: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/2025/python314/pyproject.toml b/2025/python314/pyproject.toml new file mode 100644 index 0000000..25c674d --- /dev/null +++ b/2025/python314/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "python314" +version = "0.1.0" +description = "Python 3.14 code example" +requires-python = ">=3.14" +dependencies = [ +] diff --git a/2025/python314/template_strings.py b/2025/python314/template_strings.py new file mode 100644 index 0000000..013daa3 --- /dev/null +++ b/2025/python314/template_strings.py @@ -0,0 +1,42 @@ +from string.templatelib import Template +from typing import Literal + + +from html import escape +from string.templatelib import Template, Interpolation + +def convert(value: object, conversion: Literal["a", "r", "s"] | None) -> object: + if conversion == "a": + return ascii(value) + elif conversion == "r": + return repr(value) + elif conversion == "s": + return str(value) + return value + +def f(template: Template, sanitize: bool = False) -> str: + parts: list[str] = [] + for item in template: + match item: + case str() as s: + parts.append(s) + case Interpolation(value, _, conversion, format_spec): + value = convert(value, conversion) + value = format(value, format_spec) + if sanitize: + value = escape(str(value)) + parts.append(value) + return "".join(parts) + +def to_html(template: Template) -> str: + return f(template, sanitize=True) + +def main() -> None: + evil = "" + template = t"

{evil}

" + + print(f(template)) + print(to_html(template)) + +if __name__ == "__main__": + main() diff --git a/2025/python314/uv.lock b/2025/python314/uv.lock new file mode 100644 index 0000000..cd2600b --- /dev/null +++ b/2025/python314/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "python314" +version = "0.1.0" +source = { virtual = "." } From 04469a2ab3c91a02fca005e760083aa25bd4044c Mon Sep 17 00:00:00 2001 From: Arjan Egges Date: Tue, 16 Sep 2025 13:42:23 +0200 Subject: [PATCH 44/44] Added uv workspaces example --- 2025/workspaces/packages/api/main.py | 16 + 2025/workspaces/packages/api/pyproject.toml | 10 + 2025/workspaces/packages/cli/main.py | 29 + 2025/workspaces/packages/cli/pyproject.toml | 10 + 2025/workspaces/packages/core/pyproject.toml | 8 + .../workspaces/packages/core/src/core/news.py | 11 + .../packages/summarizer/pyproject.toml | 8 + .../summarizer/src/summarizer/summarize.py | 15 + 2025/workspaces/pyproject.toml | 17 + 2025/workspaces/uv.lock | 497 ++++++++++++++++++ 10 files changed, 621 insertions(+) create mode 100644 2025/workspaces/packages/api/main.py create mode 100644 2025/workspaces/packages/api/pyproject.toml create mode 100644 2025/workspaces/packages/cli/main.py create mode 100644 2025/workspaces/packages/cli/pyproject.toml create mode 100644 2025/workspaces/packages/core/pyproject.toml create mode 100644 2025/workspaces/packages/core/src/core/news.py create mode 100644 2025/workspaces/packages/summarizer/pyproject.toml create mode 100644 2025/workspaces/packages/summarizer/src/summarizer/summarize.py create mode 100644 2025/workspaces/pyproject.toml create mode 100644 2025/workspaces/uv.lock diff --git a/2025/workspaces/packages/api/main.py b/2025/workspaces/packages/api/main.py new file mode 100644 index 0000000..cdb712b --- /dev/null +++ b/2025/workspaces/packages/api/main.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +import uvicorn +from core.news import fetch_headlines +import os +from dotenv import load_dotenv + +load_dotenv() # Load environment variables from .env + +app = FastAPI() + +@app.get("/headlines") +def get_headlines(limit: int = 5): + return {"headlines": fetch_headlines(limit)} + +if __name__ == "__main__": + uvicorn.run("main:app", host=os.getenv("HOST", "0.0.0.0"), port=int(os.getenv("PORT", 8000)), reload=True) \ No newline at end of file diff --git a/2025/workspaces/packages/api/pyproject.toml b/2025/workspaces/packages/api/pyproject.toml new file mode 100644 index 0000000..84c5054 --- /dev/null +++ b/2025/workspaces/packages/api/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "api_example" +version = "0.1.0" +description = "FastAPI app using uv workspaces" +requires-python = ">=3.12" +dependencies = [ + "fastapi", + "uvicorn", + +] \ No newline at end of file diff --git a/2025/workspaces/packages/cli/main.py b/2025/workspaces/packages/cli/main.py new file mode 100644 index 0000000..e5db0f6 --- /dev/null +++ b/2025/workspaces/packages/cli/main.py @@ -0,0 +1,29 @@ +import typer +from rich.console import Console +from core.news import fetch_headlines +from summarizer.summarize import summarize_text +import os + + +from dotenv import load_dotenv + +load_dotenv() # Load OPENAI_API_KEY from .env + +app = typer.Typer() +console = Console() + +@app.command() +def headlines(limit: int = 5): + headlines = fetch_headlines(limit) + console.print(f"[bold blue]Top {limit} headlines from Hacker News:[/bold blue]") + for i, title in enumerate(headlines, 1): + console.print(f"[green]{i}.[/green] {title}") + + api_key = os.environ.get("OPENAI_API_KEY") + + console.print("\n[bold blue]Summary:[/bold blue]") + summary = summarize_text("\n".join(headlines), api_key) + console.print(summary) + +if __name__ == "__main__": + app() diff --git a/2025/workspaces/packages/cli/pyproject.toml b/2025/workspaces/packages/cli/pyproject.toml new file mode 100644 index 0000000..47b08db --- /dev/null +++ b/2025/workspaces/packages/cli/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "cli_example" +version = "0.1.0" +description = "CLI tool using uv workspaces" +requires-python = ">=3.12" +dependencies = [ + "typer", + "rich", + +] \ No newline at end of file diff --git a/2025/workspaces/packages/core/pyproject.toml b/2025/workspaces/packages/core/pyproject.toml new file mode 100644 index 0000000..815ec2d --- /dev/null +++ b/2025/workspaces/packages/core/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "core" +version = "0.1.0" +description = "News aggregation core library" +requires-python = ">=3.12" +dependencies = [ + "httpx", "beautifulsoup4" +] \ No newline at end of file diff --git a/2025/workspaces/packages/core/src/core/news.py b/2025/workspaces/packages/core/src/core/news.py new file mode 100644 index 0000000..6e84099 --- /dev/null +++ b/2025/workspaces/packages/core/src/core/news.py @@ -0,0 +1,11 @@ +import httpx +from bs4 import BeautifulSoup + +NEWS_URL = "https://news.ycombinator.com/" + +def fetch_headlines(limit: int = 5) -> list[str]: + response = httpx.get(NEWS_URL, timeout=10) + response.raise_for_status() + soup = BeautifulSoup(response.text, "html.parser") + titles = [a.get_text() for a in soup.select(".titleline a")] + return titles[:limit] diff --git a/2025/workspaces/packages/summarizer/pyproject.toml b/2025/workspaces/packages/summarizer/pyproject.toml new file mode 100644 index 0000000..a66a539 --- /dev/null +++ b/2025/workspaces/packages/summarizer/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "summarizer" +version = "0.1.0" +description = "Library for summarizing text using OpenAI" +requires-python = ">=3.12" +dependencies = [ + "openai" +] \ No newline at end of file diff --git a/2025/workspaces/packages/summarizer/src/summarizer/summarize.py b/2025/workspaces/packages/summarizer/src/summarizer/summarize.py new file mode 100644 index 0000000..c3d70b5 --- /dev/null +++ b/2025/workspaces/packages/summarizer/src/summarizer/summarize.py @@ -0,0 +1,15 @@ +from openai import OpenAI + +def summarize_text(text: str, api_key: str) -> str: + client = OpenAI( + # This is the default and can be omitted + api_key=api_key, + ) + + response = client.responses.create( + model="gpt-5", + instructions="You are a text summary writer.", + input=f"Summarize the following text into a single concise paragraph: {text}" + ) + + return response.output_text \ No newline at end of file diff --git a/2025/workspaces/pyproject.toml b/2025/workspaces/pyproject.toml new file mode 100644 index 0000000..2f310e8 --- /dev/null +++ b/2025/workspaces/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "uv_workspace" +version = "0.1.0" +description = "Example of how to use uv workspaces" +requires-python = ">=3.12" +dependencies = [ + "python-dotenv", + "core", + "summarizer" +] + +[tool.uv.sources] +core = { workspace = true } +summarizer = { workspace = true } + +[tool.uv.workspace] +members = [ "packages/*" ] diff --git a/2025/workspaces/uv.lock b/2025/workspaces/uv.lock new file mode 100644 index 0000000..675974a --- /dev/null +++ b/2025/workspaces/uv.lock @@ -0,0 +1,497 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[manifest] +members = [ + "api-example", + "cli-example", + "core", + "summarizer", + "uv-workspace", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "api-example" +version = "0.1.0" +source = { virtual = "packages/api" } +dependencies = [ + { name = "fastapi" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi" }, + { name = "uvicorn" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cli-example" +version = "0.1.0" +source = { virtual = "packages/cli" } +dependencies = [ + { name = "rich" }, + { name = "typer" }, +] + +[package.metadata] +requires-dist = [ + { name = "rich" }, + { name = "typer" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "core" +version = "0.1.0" +source = { editable = "packages/core" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "httpx" }, +] + +[package.metadata] +requires-dist = [ + { name = "beautifulsoup4" }, + { name = "httpx" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "jiter" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada", size = 305510, upload-time = "2025-09-15T09:19:25.893Z" }, + { url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99", size = 316521, upload-time = "2025-09-15T09:19:27.525Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6", size = 338214, upload-time = "2025-09-15T09:19:28.727Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1", size = 361280, upload-time = "2025-09-15T09:19:30.013Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4", size = 487895, upload-time = "2025-09-15T09:19:31.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72", size = 378421, upload-time = "2025-09-15T09:19:32.746Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591", size = 347932, upload-time = "2025-09-15T09:19:34.612Z" }, + { url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09", size = 386959, upload-time = "2025-09-15T09:19:35.994Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5", size = 517187, upload-time = "2025-09-15T09:19:37.426Z" }, + { url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206", size = 509461, upload-time = "2025-09-15T09:19:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b", size = 206664, upload-time = "2025-09-15T09:19:40.096Z" }, + { url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c", size = 203520, upload-time = "2025-09-15T09:19:41.798Z" }, + { url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" }, + { url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" }, + { url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" }, + { url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" }, + { url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" }, + { url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" }, + { url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" }, + { url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" }, + { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "openai" +version = "1.107.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/24/7fb5749bcf66b52209e3ece05cb4eaeae2102e95f8ae77589e8afaf70ba8/openai-1.107.3.tar.gz", hash = "sha256:69bb8032b05c5f00f7660e422f70f9aabc94793b9a30c5f899360ed21e46314f", size = 564194, upload-time = "2025-09-15T20:09:20.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/1d/58ad0084451f64a9193de48c0afd63047682ffdedb6ae1d494a203e03fd5/openai-1.107.3-py3-none-any.whl", hash = "sha256:4ca54a847235ac04c6320da70fdc06b62d71439de9ec0aa40d5690c3064d4025", size = 947600, upload-time = "2025-09-15T20:09:18.219Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, +] + +[[package]] +name = "summarizer" +version = "0.1.0" +source = { editable = "packages/summarizer" } +dependencies = [ + { name = "openai" }, +] + +[package.metadata] +requires-dist = [{ name = "openai" }] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typer" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/e8/2a73ccf9874ec4c7638f172efc8972ceab13a0e3480b389d6ed822f7a822/typer-0.17.4.tar.gz", hash = "sha256:b77dc07d849312fd2bb5e7f20a7af8985c7ec360c45b051ed5412f64d8dc1580", size = 103734, upload-time = "2025-09-05T18:14:40.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl", hash = "sha256:015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824", size = 46643, upload-time = "2025-09-05T18:14:39.166Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "uv-workspace" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "core" }, + { name = "python-dotenv" }, + { name = "summarizer" }, +] + +[package.metadata] +requires-dist = [ + { name = "core", editable = "packages/core" }, + { name = "python-dotenv" }, + { name = "summarizer", editable = "packages/summarizer" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +]