Compare commits
5 Commits
748434e2cf
...
0055c66731
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0055c66731 | ||
![]() |
68026223ab | ||
![]() |
7e74c079af | ||
![]() |
a79acffe3c | ||
![]() |
42eec81c46 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,6 @@
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__
|
||||
|
||||
.env
|
||||
keys/
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.13
|
116
docments/DIコンテナ.md
Normal file
116
docments/DIコンテナ.md
Normal file
@ -0,0 +1,116 @@
|
||||
# [Python]DIコンテナの利用ガイドライン
|
||||
|
||||
## DIコンテナのメリット
|
||||
|
||||
- 依存性の解決を通じてクラスの自立性を高める
|
||||
- テストしやすくなる
|
||||
- 構成情報をコンテナで一括管理できる
|
||||
- モジュール化により小規模コードの再利用性が向上
|
||||
|
||||
## ユースケース
|
||||
|
||||
- Logger や Config などの共通サービスの渡し込み
|
||||
- DBクライアントなどの再利用
|
||||
- サービス層の分離
|
||||
- Flask/FastAPI との統合
|
||||
|
||||
## ライブラリの選定
|
||||
|
||||
### punq
|
||||
- 最簡易な軽量DIコンテナ
|
||||
- Python標準の型ヒントで自動組み立て
|
||||
- 小規模スクリプトに最適
|
||||
|
||||
```py
|
||||
import punq
|
||||
|
||||
class Logger:
|
||||
def log(self, msg):
|
||||
print(f"[LOG] {msg}")
|
||||
|
||||
class Service:
|
||||
def __init__(self, logger: Logger):
|
||||
self.logger = logger
|
||||
|
||||
def run(self):
|
||||
self.logger.log("Running service!")
|
||||
|
||||
# コンテナに登録
|
||||
container = punq.Container()
|
||||
container.register(Logger)
|
||||
container.register(Service)
|
||||
|
||||
# 自動で依存解決してインスタンス化
|
||||
svc = container.resolve(Service)
|
||||
svc.run()
|
||||
```
|
||||
|
||||
### injector
|
||||
- Google Guice に着想を得た正統派DI
|
||||
- singleton scope や module 構築が可能
|
||||
- Flask/FastAPI との連携実績が多い
|
||||
- 中央管理型プロジェクトに向いている
|
||||
|
||||
## 機能比較表
|
||||
|
||||
| 機能カテゴリ | 内容例 | punq | injector |
|
||||
| ---------------------- | ------------------------------------ | ---- | -------- |
|
||||
| 型ヒントによる自動解決 | `__init__(db: DBClient)`のような注入 | ✅ | ✅ |
|
||||
| スコープ管理 | singleton, transient の切り替え | ❌ | ✅ |
|
||||
| 明示的なバインディング | `register(ILogger, ConsoleLogger)` | ✅ | ✅ |
|
||||
| モジュール構造化 | DI定義をクラス単位で管理 | ❌ | ✅ |
|
||||
| 循環依存の検出 | 循環関係を検出・警告 | ❌ | △ |
|
||||
| テスト用差し替え | mock の注入や切替 | ✅ | ✅ |
|
||||
| パフォーマンス | 実装が軽く高速 | 高速 | 中程度 |
|
||||
|
||||
## 独自実装する場合のサンプルコード
|
||||
|
||||
以下はシンプルなDIコンテナの実装例で、singletonなインスタンス管理に対応した構造です。
|
||||
|
||||
```python
|
||||
class Container:
|
||||
def __init__(self):
|
||||
self._registrations = {}
|
||||
self._singletons = {}
|
||||
|
||||
def register(self, interface, implementation, singleton=True):
|
||||
self._registrations[interface] = (implementation, singleton)
|
||||
|
||||
def resolve(self, interface):
|
||||
impl, singleton = self._registrations.get(interface, (None, None))
|
||||
if impl is None:
|
||||
raise ValueError(f"No registration for {interface}")
|
||||
|
||||
if singleton:
|
||||
if interface not in self._singletons:
|
||||
self._singletons[interface] = impl()
|
||||
return self._singletons[interface]
|
||||
else:
|
||||
return impl()
|
||||
|
||||
# 利用例
|
||||
class ILogger:
|
||||
def log(self, msg):
|
||||
raise NotImplementedError
|
||||
|
||||
class ConsoleLogger(ILogger):
|
||||
def log(self, msg):
|
||||
print(f"[LOG] {msg}")
|
||||
|
||||
class App:
|
||||
def __init__(self, logger: ILogger):
|
||||
self.logger = logger
|
||||
|
||||
def run(self):
|
||||
self.logger.log("App is running")
|
||||
|
||||
# DIコンテナの使用
|
||||
container = Container()
|
||||
container.register(ILogger, ConsoleLogger, singleton=True)
|
||||
logger = container.resolve(ILogger)
|
||||
|
||||
app = App(logger)
|
||||
app.run()
|
||||
```
|
||||
|
||||
|
191
docments/uv.md
Normal file
191
docments/uv.md
Normal file
@ -0,0 +1,191 @@
|
||||
# \[Python] uvに関する内部ノウハウ
|
||||
|
||||
## 目的
|
||||
|
||||
Pythonの依存関係管理・仮想環境・ツール実行・Python本体のバージョン管理を、1つの高速なCLIツールで統合するための実践手順と運用の勘所をまとめる。対象ツールはRust実装の高速パッケージ/プロジェクトマネージャである「uv」。pip、pip-tools、virtualenv、pipx、pyenv、poetry等の役割を多くの場合で置き換え、開発からCI/CDまでの再現性とスピードを両立させる。([Astral Docs][1])
|
||||
|
||||
## 適用範囲
|
||||
|
||||
* 対象環境: macOS, Linux, Windows の各開発端末およびCI環境(GitHub Actions等)。([Astral Docs][1])
|
||||
* 対象バージョンやサービス: uv本体はバイナリ配布があり、Homebrew・WinGet・pip等で導入可能。([Astral Docs][2])
|
||||
* 前提知識や依存関係: Python一般、pyproject.toml、バージョン管理(Git)、シェル操作。pip互換の低レベルインターフェース「uv pip」も用意されるが、挙動は完全互換ではなく差異点がある。([Astral Docs][3])
|
||||
|
||||
---
|
||||
|
||||
## 実現方法の概要
|
||||
|
||||
uvは「プロジェクト」を単位に、pyproject.tomlで要求仕様を定義し、uv.lockに解決済みの依存バージョンを固定する。コマンド実行前にロックと環境同期を自動で行い、プロジェクトの一貫性を担保する。さらに、`uvx`や`uv tool install`でツールを隔離実行/インストールし、`uv python`でPython本体の取得・切替も一括管理できる。([Astral Docs][4])
|
||||
|
||||
---
|
||||
|
||||
## 特徴とメリット
|
||||
|
||||
* 高速: pip比10〜100倍の高速化を狙った設計。単一バイナリ配布・グローバルキャッシュなどにより開発体験を大幅に短縮。([Astral Docs][1])
|
||||
* 統合: 依存解決、ロック、仮想環境、ツール実行、Python本体の管理を1コマンド体系に集約。([Astral Docs][1])
|
||||
* 再現性: uv.lockによるクロスプラットフォームなロックと自動同期で環境差異を低減。([Astral Docs][5])
|
||||
* 柔軟性: pip互換の「uv pip」や`requirements.txt`エクスポートも可能で、段階的移行に対応。([Astral Docs][3])
|
||||
|
||||
---
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. 環境準備(インストール)
|
||||
|
||||
* macOS/Linux
|
||||
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# もしくは Homebrew
|
||||
brew install uv
|
||||
```
|
||||
|
||||
Windows
|
||||
|
||||
```powershell
|
||||
winget install --id=astral-sh.uv -e
|
||||
# もしくは PowerShell スクリプト
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
参考: pip/pipx経由やDockerイメージ、自己アップデート`uv self update`も可。([Astral Docs][2])
|
||||
2. プロジェクト作成
|
||||
|
||||
```bash
|
||||
uv init hello-world
|
||||
cd hello-world
|
||||
uv run main.py
|
||||
```
|
||||
|
||||
初回実行で `.venv` と `uv.lock` が作成される。`uv.lock` はVCSにコミットする。([Astral Docs][5])
|
||||
3. 依存追加と同期
|
||||
|
||||
```bash
|
||||
uv add fastapi uvicorn
|
||||
uv lock # 明示的にロックを更新
|
||||
uv sync # ロックに従って環境を同期
|
||||
```
|
||||
|
||||
uvは`uv run`時にもロック/同期を自動実行する。([Astral Docs][4])
|
||||
4. Python本体の管理(任意)
|
||||
|
||||
```bash
|
||||
# 複数版インストール
|
||||
uv python install 3.10 3.11 3.12
|
||||
# プロジェクトに使用する版をピン留め
|
||||
uv python pin 3.11
|
||||
```
|
||||
|
||||
`.python-version` を生成し、以後の環境作成に使用される。([Astral Docs][1])
|
||||
5. 動作確認(アプリ起動例)
|
||||
|
||||
```bash
|
||||
uv run -- uvicorn app:app --reload --port 3000
|
||||
# 仮想環境を有効化して手動実行する場合
|
||||
uv sync && source .venv/bin/activate && uvicorn app:app --reload --port 3000
|
||||
```
|
||||
|
||||
`uv run`は実行前にロックと環境の整合を保証する。([Astral Docs][5])
|
||||
|
||||
## 使い方
|
||||
|
||||
* 基本的な利用例(依存の追加/更新/削除)
|
||||
|
||||
```bash
|
||||
uv add "requests==2.32.*"
|
||||
uv remove requests
|
||||
uv lock --upgrade-package requests
|
||||
```
|
||||
|
||||
依存の追加や特定パッケージのみのアップグレードが可能。([Astral Docs][5])
|
||||
|
||||
* dependency-groups と extras
|
||||
|
||||
```toml
|
||||
# pyproject.toml (抜粋)
|
||||
[project]
|
||||
name = "sample"
|
||||
version = "0.1.0"
|
||||
dependencies = ["fastapi", "uvicorn"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["pytest", "ruff"]
|
||||
|
||||
# dev群を含めて同期
|
||||
# dev群は既定で含まれる。除外は --no-dev
|
||||
# 任意の群は --group foo, すべては --all-groups
|
||||
```
|
||||
|
||||
依存群(dev等)やextrasの同期はフラグで制御できる。([Astral Docs][4])
|
||||
|
||||
* ツール実行とインストール
|
||||
|
||||
```bash
|
||||
# 一時環境で実行(= pipx相当)
|
||||
uvx ruff@latest check
|
||||
# ツールをユーザー環境にインストール
|
||||
uv tool install ruff
|
||||
ruff --version
|
||||
```
|
||||
|
||||
`uvx`は`uv tool run`のエイリアス。ツールはプロジェクト環境と分離して管理される。([Astral Docs][6])
|
||||
|
||||
* pip互換インターフェースの活用(段階的移行)
|
||||
|
||||
```bash
|
||||
# 既存 requirements.txt を高速に同期
|
||||
uv venv
|
||||
uv pip sync requirements.txt
|
||||
# requirements をロック生成
|
||||
uv pip compile requirements.in -o requirements.txt
|
||||
```
|
||||
|
||||
`uv pip`は低レベル操作向けで、完全互換ではない点に留意。([Astral Docs][3])
|
||||
|
||||
* CIでの利用(GitHub Actions例)
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- run: uv python install
|
||||
- run: uv sync --locked --all-extras --dev
|
||||
- run: uv run pytest -q
|
||||
```
|
||||
|
||||
公式の`setup-uv`で導入・キャッシュ・Python設定が簡便。([Astral Docs][7])
|
||||
|
||||
---
|
||||
|
||||
## 制限・注意点
|
||||
|
||||
* pip完全互換ではない: `uv pip`は名前のとおり低レベル操作を提供するが、挙動差があるため複雑なケースではドキュメントを確認する。([Astral Docs][3])
|
||||
* 自動ロック/同期の制御: 既定で`uv run`等はロック/同期を自動実行する。固定したい場合は`--locked`や`--frozen`、同期抑止は`--no-sync`を利用する。([Astral Docs][4])
|
||||
* Python版の可用性: uvの各リリースごとに利用可能なPython版リストが固定される場合がある。新しいPythonを入れる前にuv自体の更新が必要なことがある。([Astral Docs][8])
|
||||
* ツール実行の選択: プロジェクトに依存するテスト系(例: pytest, mypy)は`uvx`ではなく`uv run`で実行する。`uvx`はプロジェクトから隔離された一時環境で動く。([Astral Docs][6])
|
||||
* エクスポートの使い分け: 他ツール連携のため`uv export --format requirements-txt`は可能だが、基本は`uv.lock`単独運用が推奨。([Astral Docs][4])
|
||||
|
||||
---
|
||||
|
||||
## 関連リンク
|
||||
|
||||
* 公式トップ/概要・Highlights・コマンド早見: uv docs ([Astral Docs][1])
|
||||
* インストール方法(Homebrew, WinGet, pip/pipx, Self update 他): Installing uv ([Astral Docs][2])
|
||||
* プロジェクト運用(uv init, add, run, uv.lock, .venv): Working on projects ([Astral Docs][5])
|
||||
* ロック/同期の概念と運用(--locked/--frozen/--no-sync, dev群, export): Locking and syncing ([Astral Docs][4])
|
||||
* ツール実行/インストール(uvx, uv tool install, --from, extras): Using tools ([Astral Docs][6])
|
||||
* pip互換インターフェースの考え方と注意点: The pip interface ([Astral Docs][3])
|
||||
* GitHub Actionsでの利用(公式setup-uv, キャッシュ, Python設定): Using uv in GitHub Actions ([Astral Docs][7])
|
||||
|
||||
---
|
||||
|
||||
## 結論
|
||||
|
||||
uvは「速い」「一貫性がある」「運用が単純」の3点を同時に満たす実用的な選択肢である。日常開発では`uv add`→`uv run`で迷いなく進め、CIでは`uv sync --locked`で再現環境を固定し、ツールは`uvx`や`uv tool install`で隔離管理する。既存資産が多い場合は`uv pip`と`uv export`で段階的に移行しつつ、最終的には`uv.lock`中心の運用に統一することを推奨する。([Astral Docs][4])
|
||||
|
||||
[1]: https://docs.astral.sh/uv/ "uv"
|
||||
[2]: https://docs.astral.sh/uv/getting-started/installation/ "Installation | uv"
|
||||
[3]: https://docs.astral.sh/uv/pip/ "Index | uv"
|
||||
[4]: https://docs.astral.sh/uv/concepts/projects/sync/ "Locking and syncing | uv"
|
||||
[5]: https://docs.astral.sh/uv/guides/projects/ "Working on projects | uv"
|
||||
[6]: https://docs.astral.sh/uv/guides/tools/ "Using tools | uv"
|
||||
[7]: https://docs.astral.sh/uv/guides/integration/github/ "Using uv in GitHub Actions | uv"
|
||||
[8]: https://docs.astral.sh/uv/concepts/python-versions/?utm_source=chatgpt.com "Python versions | uv - Astral Docs"
|
19
example/debug.py
Normal file
19
example/debug.py
Normal file
@ -0,0 +1,19 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from libs.custom_logger import get_logger
|
||||
from libs.time_watch import TimeWatch
|
||||
|
||||
# level = 10 # DEBUG level
|
||||
logger = get_logger(level=10)
|
||||
logger.debug("Debug message")
|
||||
|
||||
@TimeWatch.debug(logger=logger)
|
||||
def app_run():
|
||||
import time
|
||||
time.sleep(2)
|
||||
return "Function executed."
|
||||
|
||||
if __name__ == "__main__":
|
||||
app_run()
|
49
example/example_firestore_provider.py
Normal file
49
example/example_firestore_provider.py
Normal file
@ -0,0 +1,49 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(".env")
|
||||
|
||||
from lib.custom_logger import get_logger
|
||||
logger = get_logger(level=10)
|
||||
|
||||
from providers.firestore_provider import FireStoreProvider
|
||||
from models.user import UserModel
|
||||
FireStoreProvider()
|
||||
|
||||
def example_firestore():
|
||||
logger.info("Starting Firestore example")
|
||||
doc_id = FireStoreProvider.create_document(
|
||||
"users", {"name": "Alice", "age": 30, "tags": ["admin", "beta"]}
|
||||
)
|
||||
logger.info(f"Created document with ID: {doc_id}")
|
||||
doc = FireStoreProvider.get_document("users", doc_id)
|
||||
logger.info(f"show document: {doc}")
|
||||
rows = FireStoreProvider.list_documents(
|
||||
"users",
|
||||
filters=[("age", ">=", 18)],
|
||||
order_by=["-age"],
|
||||
limit=50,
|
||||
)
|
||||
logger.info(f"Listed documents: {rows}")
|
||||
FireStoreProvider.update_document("users", doc_id, {"age": 31}, merge=True)
|
||||
FireStoreProvider.delete_document("users", doc_id)
|
||||
|
||||
def example_user_model():
|
||||
logger.info("Starting UserModel example")
|
||||
user = UserModel(name="Bob", email="example@example.com", age=30)
|
||||
user.save()
|
||||
logger.info(f"User saved with ID: {user._doc_id}")
|
||||
|
||||
user2 = UserModel.get(user._doc_id)
|
||||
logger.info(f"Fetched user: {user2._doc_id}")
|
||||
user2.name = "Robert"
|
||||
user2.age = 31
|
||||
user2.save()
|
||||
logger.info(f"Updated user: {user2._doc_id}")
|
||||
users = UserModel.list()
|
||||
logger.info(f"Listed users: {[user._doc_id for user in users] }")
|
||||
|
||||
# example_firestore()
|
||||
example_user_model()
|
10
pyproject.toml
Normal file
10
pyproject.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "python-common-code"
|
||||
version = "0.1.0"
|
||||
description = "Python Code"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"firebase-admin>=7.1.0",
|
||||
"python-dotenv>=1.1.1",
|
||||
]
|
@ -1,2 +1,4 @@
|
||||
matplotlib
|
||||
pyttsx3
|
||||
|
||||
# firebase_provider
|
11
src/lib/__init__.py
Normal file
11
src/lib/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
from .custom_logger import get_logger, CustomLogger
|
||||
from .time_watch import TimeWatch
|
||||
from .singleton import Singleton
|
||||
|
||||
__all__ = [
|
||||
"get_logger",
|
||||
"CustomLogger",
|
||||
"TimeWatch",
|
||||
"Singleton",
|
||||
]
|
||||
|
76
src/lib/custom_logger.py
Normal file
76
src/lib/custom_logger.py
Normal file
@ -0,0 +1,76 @@
|
||||
import logging
|
||||
import functools
|
||||
from .singleton import Singleton
|
||||
|
||||
# ANSIカラーコード定義
|
||||
RESET = "\033[0m"
|
||||
RED = "\033[31m"
|
||||
YELLOW = "\033[33m"
|
||||
GREEN = "\033[32m"
|
||||
BLUE = "\033[34m"
|
||||
|
||||
class ColorFormatter(logging.Formatter):
|
||||
COLORS = {
|
||||
logging.DEBUG: BLUE,
|
||||
logging.INFO: GREEN,
|
||||
logging.WARNING: YELLOW,
|
||||
logging.ERROR: RED,
|
||||
logging.CRITICAL: RED + "\033[1m", # 太字赤
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
color = self.COLORS.get(record.levelno, RESET)
|
||||
message = super().format(record)
|
||||
return f"{color}{message}{RESET}"
|
||||
|
||||
|
||||
class CustomLogger(Singleton):
|
||||
|
||||
def __init__(self, name='main', log_file=None, level=logging.INFO):
|
||||
if hasattr(self, '_initialized') and self._initialized:
|
||||
self.logger.setLevel(level)
|
||||
return # すでに初期化済みなら何もしない
|
||||
|
||||
self.logger = logging.getLogger(name)
|
||||
self.logger.setLevel(level)
|
||||
self.logger.propagate = False
|
||||
|
||||
# formatter = logging.Formatter(
|
||||
# '%(asctime)s %(levelname)s [%(filename)s:%(lineno)3d]: %(message)s'
|
||||
# )
|
||||
formatter = ColorFormatter(
|
||||
'%(asctime)s %(levelname)s [%(filename)s:%(lineno)3d]: %(message)s'
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(formatter)
|
||||
self.logger.addHandler(ch)
|
||||
|
||||
# File handler
|
||||
if log_file:
|
||||
fh = logging.FileHandler(log_file, encoding='utf-8')
|
||||
fh.setFormatter(formatter)
|
||||
self.logger.addHandler(fh)
|
||||
|
||||
self._initialized = True
|
||||
|
||||
|
||||
def get_logger(self):
|
||||
return self.logger
|
||||
|
||||
def log_entry_exit(self, func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
self.logger.info(f"Enter: {func.__qualname__}")
|
||||
result = func(*args, **kwargs)
|
||||
self.logger.info(f"Exit: {func.__qualname__}")
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_logger(name='main', log_file=None, level=logging.INFO):
|
||||
custom_logger = CustomLogger(name, log_file, level)
|
||||
return custom_logger.get_logger()
|
22
src/lib/di_container.py
Normal file
22
src/lib/di_container.py
Normal file
@ -0,0 +1,22 @@
|
||||
from .singleton import SingletonMeta
|
||||
|
||||
|
||||
class DiContainer(metaclass=SingletonMeta):
|
||||
def __init__(self):
|
||||
self._registrations = {}
|
||||
self._singletons = {}
|
||||
|
||||
def register(self, interface, implementation, singleton=True):
|
||||
self._registrations[interface] = (implementation, singleton)
|
||||
|
||||
def resolve(self, interface):
|
||||
impl, singleton = self._registrations.get(interface, (None, None))
|
||||
if impl is None:
|
||||
raise ValueError(f"No registration for {interface}")
|
||||
|
||||
if singleton:
|
||||
if interface not in self._singletons:
|
||||
self._singletons[interface] = impl()
|
||||
return self._singletons[interface]
|
||||
else:
|
||||
return impl()
|
29
src/lib/singleton.py
Normal file
29
src/lib/singleton.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Singleton pattern implementation in Python.
|
||||
This implementation is thread-safe and ensures that only one instance of the class is created.
|
||||
|
||||
Singleton が提供するのは「同じインスタンスを返す仕組み」
|
||||
* __init__() は毎回呼ばれる(多くの人が意図しない動作)
|
||||
* __init__の2回目は_initialized というフラグは 使う側で管理する必要がある。
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
class Singleton(object):
|
||||
_instances = {}
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
with cls._lock:
|
||||
if cls not in cls._instances: # ダブルチェック
|
||||
cls._instances[cls] = super(Singleton, cls).__new__(cls)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class SingletonMeta(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
@ -36,20 +36,22 @@ class TimeWatch:
|
||||
self.end_time = None
|
||||
self.logger.debug("Timer reset.")
|
||||
|
||||
@classmethod
|
||||
def debug(cls, func):
|
||||
def wrapper(*args, **kwargs):
|
||||
instance = cls() # インスタンスを作成
|
||||
instance.start()
|
||||
result = func(*args, **kwargs)
|
||||
instance.stop()
|
||||
instance.elapsed_time()
|
||||
return result
|
||||
return wrapper
|
||||
@staticmethod
|
||||
def debug(logger=None):
|
||||
def decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
timer = TimeWatch(logger=logger)
|
||||
timer.start()
|
||||
result = func(*args, **kwargs)
|
||||
timer.stop()
|
||||
timer.elapsed_time()
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@TimeWatch.debug
|
||||
@TimeWatch.debug()
|
||||
def test_function():
|
||||
time.sleep(2)
|
||||
return "Function executed."
|
219
src/models/firestore_base_model.py
Normal file
219
src/models/firestore_base_model.py
Normal file
@ -0,0 +1,219 @@
|
||||
from typing import Any, Dict, Optional,Type,TypeVar,Tuple,Sequence,Union,List,Iterable
|
||||
from dataclasses import dataclass, field,asdict,fields
|
||||
import google.cloud.firestore as firestore
|
||||
from google.cloud import firestore
|
||||
from google.cloud.firestore import DocumentSnapshot
|
||||
from providers.firestore_provider import FireStoreProvider
|
||||
from lib.custom_logger import get_logger
|
||||
logger = get_logger()
|
||||
|
||||
T = TypeVar("T", bound="FirestoreBaseModel")
|
||||
|
||||
_BATCH_LIMIT = 500
|
||||
|
||||
def _chunked(seq: Iterable[Any], size: int):
|
||||
"""チャンク処理
|
||||
|
||||
seqをsize個ずつに分割するジェネレータ
|
||||
"""
|
||||
buf = []
|
||||
for x in seq:
|
||||
buf.append(x)
|
||||
if len(buf) >= size:
|
||||
yield buf
|
||||
buf = []
|
||||
if buf:
|
||||
yield buf
|
||||
|
||||
Filter = Tuple[str, str, Any]
|
||||
SERVER_TS = firestore.SERVER_TIMESTAMP
|
||||
|
||||
@dataclass
|
||||
class FirestoreBaseModel:
|
||||
collection_name: str = ""
|
||||
_doc_id: Optional[str] = field(default=None, init=False)
|
||||
|
||||
# ====== 基本ユーティリティ ======
|
||||
@classmethod
|
||||
def _col(cls) -> firestore.CollectionReference:
|
||||
"コレクションを取得する"
|
||||
return FireStoreProvider.get_collection_ref(cls.collection_name)
|
||||
|
||||
@classmethod
|
||||
def _doc(cls, doc_id: str) -> firestore.DocumentReference:
|
||||
"ドキュメントリファレンスを取得する"
|
||||
return cls._col().document(doc_id)
|
||||
|
||||
def _as_mutable_dict(self) -> Dict[str, Any]:
|
||||
"""dataclass → dict(id と None は必要に応じて処理)"""
|
||||
data = asdict(self)
|
||||
data.pop("_doc_id", None)
|
||||
data.pop("collection_name", None)
|
||||
return data
|
||||
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
|
||||
"""dict -> モデル(data内の'id'を拾う)"""
|
||||
data = dict(data)
|
||||
_id = data.pop("id", None)
|
||||
obj: T = cls(**data) # type: ignore[arg-type]
|
||||
obj._doc_id = _id
|
||||
return obj
|
||||
|
||||
|
||||
@classmethod
|
||||
def _from_snapshot(cls: Type[T], snap: DocumentSnapshot) -> T:
|
||||
data: Dict[str, Any] = snap.to_dict() or {}
|
||||
obj: T = cls(**data) # ← インスタンスを生成
|
||||
obj._doc_id = snap.id # ← 取得したIDをインスタンスに付与
|
||||
if not obj.collection_name: # お好みでデフォルトのコレクション名
|
||||
obj.collection_name = cls.__name__.lower()
|
||||
return obj
|
||||
|
||||
# ====== CRUD (Instance) ======
|
||||
def save(self, merge: bool = True):
|
||||
if not self.collection_name:
|
||||
self.collection_name = self.__class__.__name__.lower()
|
||||
|
||||
payload = self._as_mutable_dict()
|
||||
if self._doc_id:
|
||||
ref = self._doc(self._doc_id)
|
||||
ref.set(payload, merge=merge)
|
||||
else:
|
||||
_, ref = self._col().add(payload)
|
||||
self._doc_id = ref.id
|
||||
logger.debug(f"[{self.collection_name}] created: {self._doc_id}")
|
||||
|
||||
return self._doc_id
|
||||
|
||||
def update_fields(self, changes: Dict[str, Any], auto_timestamp: bool = True) -> None:
|
||||
"""部分更新(set(merge=True)の糖衣)"""
|
||||
if not self._doc_id:
|
||||
raise ValueError("Cannot update without id.")
|
||||
payload = dict(changes)
|
||||
if auto_timestamp and "updated_at" in {f.name for f in fields(self)}:
|
||||
payload["updated_at"] = SERVER_TS
|
||||
self._doc(self._doc_id).set(payload, merge=True)
|
||||
# ローカルにも反映
|
||||
for k, v in changes.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def delete(self) -> None:
|
||||
if not self._doc_id:
|
||||
raise ValueError("Cannot delete without id.")
|
||||
self._doc(self._doc_id).delete()
|
||||
logger.debug(f"[{self.collection_name}] deleted: {self._doc_id}")
|
||||
|
||||
def refresh(self: T) -> T:
|
||||
"""サーバー状態を取り直して自身を上書き"""
|
||||
if not self._doc_id:
|
||||
raise ValueError("Cannot refresh without id.")
|
||||
snap = self._doc(self._doc_id).get()
|
||||
if not snap.exists:
|
||||
raise ValueError(f"Document not found: {self.collection_name}/{self.id}")
|
||||
fresh = self._from_snapshot(snap)
|
||||
# dataclasses の置換
|
||||
for f in fields(self):
|
||||
setattr(self, f.name, getattr(fresh, f.name))
|
||||
return self
|
||||
|
||||
|
||||
# ====== Class Method ======
|
||||
@classmethod
|
||||
def get(cls: Type[T], doc_id: str) -> Optional[T]:
|
||||
snap = cls._doc(doc_id).get()
|
||||
if not snap.exists:
|
||||
return None
|
||||
return cls._from_snapshot(snap)
|
||||
|
||||
@classmethod
|
||||
def create(cls: Type[T], **data: Any) -> T:
|
||||
inst = cls(**data)
|
||||
inst.save()
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
cls: Type[T],
|
||||
*,
|
||||
filters: Optional[Sequence[Tuple[str, str, Any]]] = None,
|
||||
order_by: Optional[Union[str, Sequence[str]]] = None,
|
||||
limit: Optional[int] = None,
|
||||
start_after: Optional[Union[DocumentSnapshot, Dict[str, Any]]] = None,
|
||||
) -> List[T]:
|
||||
"""FireStoreProvider.list_documents を使った検索 → モデル配列"""
|
||||
rows = FireStoreProvider.list_documents(
|
||||
cls.collection_name,
|
||||
filters=filters,
|
||||
order_by=order_by,
|
||||
limit=limit,
|
||||
start_after=start_after,
|
||||
)
|
||||
return [cls._from_dict(r) for r in rows]
|
||||
|
||||
@classmethod
|
||||
def first(
|
||||
cls: Type[T],
|
||||
*,
|
||||
filters: Optional[Sequence[Tuple[str, str, Any]]] = None,
|
||||
order_by: Optional[Union[str, Sequence[str]]] = None,
|
||||
start_after: Optional[Union[DocumentSnapshot, Dict[str, Any]]] = None,
|
||||
) -> Optional[T]:
|
||||
"""
|
||||
FireStoreProvider.list_documents を使って最初の1件を返す
|
||||
なければ None
|
||||
"""
|
||||
rows = FireStoreProvider.list_documents(
|
||||
cls.collection_name,
|
||||
filters=filters,
|
||||
order_by=order_by,
|
||||
limit=1,
|
||||
start_after=start_after,
|
||||
)
|
||||
if not rows:
|
||||
return None
|
||||
return cls._from_dict(rows[0])
|
||||
|
||||
# ========== bulk process ==========
|
||||
@classmethod
|
||||
def bulk_delete_by_ids(cls, ids: Sequence[str]) -> int:
|
||||
"""
|
||||
IDの配列でドキュメントを一括削除。削除件数を返す。
|
||||
500件超は自動的に分割コミット。
|
||||
"""
|
||||
if not ids:
|
||||
return 0
|
||||
client: firestore.Client = cls._col()._client # firestore.Client
|
||||
total = 0
|
||||
for chunk in _chunked(ids, _BATCH_LIMIT):
|
||||
batch = client.batch()
|
||||
for doc_id in chunk:
|
||||
batch.delete(cls._doc(doc_id))
|
||||
batch.commit()
|
||||
total += len(chunk)
|
||||
return total
|
||||
|
||||
@classmethod
|
||||
def bulk_delete(
|
||||
cls,
|
||||
*,
|
||||
filters: Optional[Sequence[Tuple[str, str, Any]]] = None,
|
||||
order_by: Optional[Union[str, Sequence[str]]] = None,
|
||||
limit: Optional[int] = None,
|
||||
start_after: Optional[Union[DocumentSnapshot, Dict[str, Any]]] = None,
|
||||
) -> int:
|
||||
"""
|
||||
条件でヒットしたドキュメントを一括削除。削除件数を返す。
|
||||
※ list_documents に依存(“複合インデックスなし”ポリシーはそちらの制約に従う)
|
||||
"""
|
||||
from providers.firestore_provider import FireStoreProvider
|
||||
rows = FireStoreProvider.list_documents(
|
||||
cls.collection_name,
|
||||
filters=filters,
|
||||
order_by=order_by,
|
||||
limit=limit,
|
||||
start_after=start_after,
|
||||
)
|
||||
ids = [r["id"] for r in rows]
|
||||
return cls.bulk_delete_by_ids(ids)
|
11
src/models/user.py
Normal file
11
src/models/user.py
Normal file
@ -0,0 +1,11 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, List, Any
|
||||
|
||||
from models.firestore_base_model import FirestoreBaseModel
|
||||
|
||||
@dataclass
|
||||
class UserModel(FirestoreBaseModel):
|
||||
collection_name: str = "users"
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
age: Optional[int] = field(default=None)
|
207
src/providers/firestore_provider.py
Normal file
207
src/providers/firestore_provider.py
Normal file
@ -0,0 +1,207 @@
|
||||
import os
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
from lib.custom_logger import get_logger
|
||||
import firebase_admin
|
||||
from firebase_admin import credentials,firestore
|
||||
import google.cloud.firestore
|
||||
# from google.cloud.firestore import Client
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
Filter = Tuple[str, str, Any] # (field, op, value)
|
||||
|
||||
class FireStoreProvider():
|
||||
"""Firestoreプロバイダクラス"""
|
||||
_db: Optional[google.cloud.firestore.Client] = None
|
||||
_ALLOWED_OPS = {
|
||||
"==", "!=", "<", "<=", ">", ">=", "in", "not-in", "array_contains", "array_contains_any"
|
||||
}
|
||||
|
||||
def __init__(self, cred_path: str = None):
|
||||
"""
|
||||
Firestoreプロバイダの初期化
|
||||
|
||||
Args:
|
||||
cred_path (str): サービスアカウントキーのパス(Noneの場合はデフォルト認証を使用)
|
||||
"""
|
||||
if not firebase_admin._apps:
|
||||
try:
|
||||
logger.debug("Initializing Firestore")
|
||||
if cred_path:
|
||||
cred = credentials.Certificate(cred_path)
|
||||
firebase_admin.initialize_app(cred)
|
||||
logger.info("Firestore initialized with service account key")
|
||||
elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON"):
|
||||
cred_json = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
|
||||
cred = credentials.Certificate(cred_json)
|
||||
firebase_admin.initialize_app(cred)
|
||||
logger.info("Firestore initialized with credentials from environment variable")
|
||||
else:
|
||||
# 環境変数にGOOGLE_APPLICATION_CREDENTIALSが設定されている場合は自動的に認証される
|
||||
# サービスアカウントのJSONキーのファイルパス
|
||||
# Cloud RunやGCE/GKEなどのGCP環境では、環境変数を設定しなくても デフォルトサービスアカウントで認証されます。
|
||||
firebase_admin.initialize_app()
|
||||
logger.info("Firestore initialized with default credentials")
|
||||
except Exception as e:
|
||||
logger.error(f"Firestore initialization failed: {e}")
|
||||
raise
|
||||
|
||||
if FireStoreProvider._db is None:
|
||||
FireStoreProvider._db = firestore.client()
|
||||
|
||||
logger.info("Firestore client created")
|
||||
|
||||
# ============= base accessors =============
|
||||
@classmethod
|
||||
def get_db(cls) -> google.cloud.firestore.Client:
|
||||
if cls._db is None:
|
||||
raise RuntimeError("Firestore client not initialized. Construct FireStoreProvider first.")
|
||||
return cls._db
|
||||
|
||||
@classmethod
|
||||
def get_collection_ref(cls, collection_name: str) -> google.cloud.firestore.CollectionReference:
|
||||
return cls.get_db().collection(collection_name)
|
||||
|
||||
@classmethod
|
||||
def get_doc_ref(cls, collection_name: str, doc_id: str) -> google.cloud.firestore.DocumentReference:
|
||||
return cls.get_collection_ref(collection_name).document(doc_id)
|
||||
|
||||
|
||||
# ============= CRUD / Query =============
|
||||
@classmethod
|
||||
def list_documents(
|
||||
cls,
|
||||
collection_name: str,
|
||||
filters: Optional[Sequence[Filter]] = None,
|
||||
order_by: Optional[Union[str, Sequence[str]]] = None,
|
||||
limit: Optional[int] = None,
|
||||
start_after: Optional[Union[google.cloud.firestore.DocumentSnapshot, Dict[str, Any]]] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
コレクションのドキュメントを取得(フィルタ/並び替え/制限/ページング対応)
|
||||
|
||||
Args:
|
||||
collection_name: コレクション名
|
||||
filters: [(field, op, value), ...]
|
||||
order_by: 並び順のフィールド名 or その配列('-created_at' のように先頭'-'で降順)
|
||||
limit: 取得件数の上限
|
||||
start_after: ドキュメントスナップショット、または order_by で並べた最後の値の辞書
|
||||
|
||||
Returns:
|
||||
List[dict]: それぞれに 'id' を含む dict の配列
|
||||
"""
|
||||
col = cls.get_collection_ref(collection_name)
|
||||
q: Union[google.cloud.firestore.Query, google.cloud.firestore.CollectionReference] = col
|
||||
|
||||
# filters
|
||||
if filters:
|
||||
for field, op, value in filters:
|
||||
if op not in cls._ALLOWED_OPS:
|
||||
raise ValueError(f"Unsupported operator: {op}")
|
||||
q = q.where(field, op, value)
|
||||
|
||||
# order_by
|
||||
if order_by:
|
||||
if isinstance(order_by, str):
|
||||
order_by = [order_by]
|
||||
for key in order_by:
|
||||
if key.startswith("-"):
|
||||
q = q.order_by(key[1:], direction=google.cloud.firestore.Query.DESCENDING)
|
||||
else:
|
||||
q = q.order_by(key, direction=google.cloud.firestore.Query.ASCENDING)
|
||||
|
||||
# limit
|
||||
if limit:
|
||||
q = q.limit(limit)
|
||||
|
||||
# pagination
|
||||
if start_after is not None:
|
||||
if isinstance(start_after, google.cloud.firestore.DocumentSnapshot):
|
||||
q = q.start_after(start_after)
|
||||
elif isinstance(start_after, dict):
|
||||
# order_by が指定されている前提で、そのキー順に値を渡す
|
||||
if not order_by:
|
||||
raise ValueError("start_after as dict requires order_by to be set.")
|
||||
values = []
|
||||
for key in order_by:
|
||||
field_name = key[1:] if key.startswith("-") else key
|
||||
if field_name not in start_after:
|
||||
raise ValueError(f"start_after dict missing field: {field_name}")
|
||||
values.append(start_after[field_name])
|
||||
q = q.start_after(values)
|
||||
else:
|
||||
raise ValueError("start_after must be DocumentSnapshot or dict")
|
||||
|
||||
docs: List[google.cloud.firestore.DocumentSnapshot] = q.stream()
|
||||
results: List[Dict[str, Any]] = []
|
||||
for d in docs:
|
||||
data = d.to_dict() or {}
|
||||
data["id"] = d.id
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_document(cls, collection_name: str, doc_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
ドキュメントをIDで取得。存在しなければ None を返す。
|
||||
"""
|
||||
ref = cls.get_doc_ref(collection_name, doc_id)
|
||||
snap = ref.get()
|
||||
if not snap.exists:
|
||||
return None
|
||||
data = snap.to_dict() or {}
|
||||
data["id"] = snap.id
|
||||
return data
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_document(
|
||||
cls,
|
||||
collection_name: str,
|
||||
data: Dict[str, Any],
|
||||
doc_id: Optional[str] = None,
|
||||
merge: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
ドキュメントを作成。doc_id を渡さない場合は自動採番。
|
||||
|
||||
Returns:
|
||||
str: 作成(または上書き)されたドキュメント
|
||||
"""
|
||||
col = cls.get_collection_ref(collection_name)
|
||||
if doc_id:
|
||||
ref = col.document(doc_id)
|
||||
ref.set(data, merge=merge)
|
||||
logger.info(f"Created/Set document: {collection_name}/{doc_id}")
|
||||
return doc_id
|
||||
else:
|
||||
ref = col.add(data)[1] # add -> (update_time, ref)
|
||||
logger.info(f"Created document: {collection_name}/{ref.id}")
|
||||
return ref.id
|
||||
|
||||
@classmethod
|
||||
def update_document(
|
||||
cls,
|
||||
collection_name: str,
|
||||
doc_id: str,
|
||||
data: Dict[str, Any],
|
||||
merge: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
ドキュメントを更新。merge=True なら部分更新(推奨)。
|
||||
"""
|
||||
ref = cls.get_doc_ref(collection_name, doc_id)
|
||||
if merge:
|
||||
ref.set(data, merge=True)
|
||||
else:
|
||||
# 全置換(存在しないと作成される)
|
||||
ref.set(data, merge=False)
|
||||
logger.info(f"Updated document: {collection_name}/{doc_id} (merge={merge})")
|
||||
|
||||
@classmethod
|
||||
def delete_document(cls, collection_name: str, doc_id: str) -> None:
|
||||
"""ドキュメントを削除"""
|
||||
ref = cls.get_doc_ref(collection_name, doc_id)
|
||||
ref.delete()
|
||||
logger.info(f"Deleted document: {collection_name}/{doc_id}")
|
544
uv.lock
generated
Normal file
544
uv.lock
generated
Normal file
@ -0,0 +1,544 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[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 = "cachecontrol"
|
||||
version = "0.14.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "msgpack" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/3a/0cbeb04ea57d2493f3ec5a069a117ab467f85e4a10017c6d854ddcbff104/cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", size = 28985, upload-time = "2025-04-30T16:45:06.135Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/4c/800b0607b00b3fd20f1087f80ab53d6b4d005515b0f773e4831e37cfa83f/cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae", size = 21802, upload-time = "2025-04-30T16:45:03.863Z" },
|
||||
]
|
||||
|
||||
[[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.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 = "cffi"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "45.0.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firebase-admin"
|
||||
version = "7.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cachecontrol" },
|
||||
{ name = "google-api-core", extra = ["grpc"], marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "google-cloud-firestore", marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "google-cloud-storage" },
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/41/859f78701761bed7abdb8a7c050819c654dad17f96c658dcdf263d5a9641/firebase_admin-7.1.0.tar.gz", hash = "sha256:a163014a4368c43f7efa409948065f8856b51bd2ae5d82c24586e546d5fd4a26", size = 194786, upload-time = "2025-07-31T20:36:39.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/16/eb9bf44cdc7af0317a70ae770ad4170b9fcfbb660ac32806742506be1246/firebase_admin-7.1.0-py3-none-any.whl", hash = "sha256:1913e783b7ad56f891e1aca86e6fdde6a8ec49b7a920dd451da155e8647506c8", size = 137140, upload-time = "2025-07-31T20:36:38.266Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-auth" },
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
grpc = [
|
||||
{ name = "grpcio" },
|
||||
{ name = "grpcio-status" },
|
||||
]
|
||||
|
||||
[[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-cloud-core"
|
||||
version = "2.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core" },
|
||||
{ name = "google-auth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-firestore"
|
||||
version = "2.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core", extra = ["grpc"] },
|
||||
{ name = "google-auth" },
|
||||
{ name = "google-cloud-core" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/9d/027b9bf61a44422bcdcb00a2acc59152065b1cffa1fc89da62277730973e/google_cloud_firestore-2.21.0.tar.gz", hash = "sha256:0c37faa8506297f827eefc38feb155247a6dcb9a541289631015d125f1b003f8", size = 528159, upload-time = "2025-06-03T19:28:27.195Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/03/94755c64a2fb85cba734ac05a4f80096b8c0acfab0508c9d52c57f571687/google_cloud_firestore-2.21.0-py3-none-any.whl", hash = "sha256:bf33ccc38a27afc60748d1f9bb7c46b078d0d39d288636bdfd967611d7b3f17f", size = 368813, upload-time = "2025-06-03T19:28:25.131Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-storage"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core" },
|
||||
{ name = "google-auth" },
|
||||
{ name = "google-cloud-core" },
|
||||
{ name = "google-crc32c" },
|
||||
{ name = "google-resumable-media" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/91/10b9ddd5baacde375dcd7e6716b5024b3f65a22366f74c26926b6aa84e4e/google_cloud_storage-3.3.0.tar.gz", hash = "sha256:ae9d891d53e17d9681d7c4ef1ffeea0cde9bdc53d5b64fa6ff6bf30d1911cf61", size = 7781974, upload-time = "2025-08-12T09:10:36.245Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/41/9d/2814a2c47429dc2e197e176de25a946d4538422b081ade8638e585e4006f/google_cloud_storage-3.3.0-py3-none-any.whl", hash = "sha256:0338ecd6621b3ecacb108f1cf7513ff0d1bca7f1ff4d58e0220b59f3a725ff23", size = 274270, upload-time = "2025-08-12T09:10:34.793Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-crc32c"
|
||||
version = "1.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467, upload-time = "2025-03-26T14:36:06.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309, upload-time = "2025-03-26T15:06:15.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133, upload-time = "2025-03-26T14:41:34.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773, upload-time = "2025-03-26T14:41:35.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475, upload-time = "2025-03-26T14:29:11.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243, upload-time = "2025-03-26T14:41:35.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870, upload-time = "2025-03-26T14:41:37.08Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-resumable-media"
|
||||
version = "2.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-crc32c" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "googleapis-common-protos"
|
||||
version = "1.70.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.74.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/d8/1004a5f468715221450e66b051c839c2ce9a985aa3ee427422061fcbb6aa/grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89", size = 5449488, upload-time = "2025-07-24T18:53:41.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/0e/33731a03f63740d7743dced423846c831d8e6da808fcd02821a4416df7fa/grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01", size = 10974059, upload-time = "2025-07-24T18:53:43.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c6/3d2c14d87771a421205bdca991467cfe473ee4c6a1231c1ede5248c62ab8/grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e", size = 5945647, upload-time = "2025-07-24T18:53:45.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/83/5a354c8aaff58594eef7fffebae41a0f8995a6258bbc6809b800c33d4c13/grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91", size = 6626101, upload-time = "2025-07-24T18:53:47.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ca/4fdc7bf59bf6994aa45cbd4ef1055cd65e2884de6113dbd49f75498ddb08/grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249", size = 6182562, upload-time = "2025-07-24T18:53:48.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/48/2869e5b2c1922583686f7ae674937986807c2f676d08be70d0a541316270/grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362", size = 6303425, upload-time = "2025-07-24T18:53:50.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/0e/bac93147b9a164f759497bc6913e74af1cb632c733c7af62c0336782bd38/grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f", size = 6996533, upload-time = "2025-07-24T18:53:52.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/35/9f6b2503c1fd86d068b46818bbd7329db26a87cdd8c01e0d1a9abea1104c/grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20", size = 6491489, upload-time = "2025-07-24T18:53:55.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/33/a04e99be2a82c4cbc4039eb3a76f6c3632932b9d5d295221389d10ac9ca7/grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa", size = 3805811, upload-time = "2025-07-24T18:53:56.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio-status"
|
||||
version = "1.74.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/22/238c5f01e6837df54494deb08d5c772bc3f5bf5fb80a15dce254892d1a81/grpcio_status-1.74.0.tar.gz", hash = "sha256:c58c1b24aa454e30f1fc6a7e0dbbc194c54a408143971a94b5f4e40bb5831432", size = 13662, upload-time = "2025-07-24T19:01:56.874Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/aa/1b1fe7d8ab699e1ec26d3a36b91d3df9f83a30abc07d4c881d0296b17b67/grpcio_status-1.74.0-py3-none-any.whl", hash = "sha256:52cdbd759a6760fc8f668098a03f208f493dd5c76bf8e02598bbbaf1f6fc2876", size = 14425, upload-time = "2025-07-24T19:01:19.963Z" },
|
||||
]
|
||||
|
||||
[[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 = "h2"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "hpack" },
|
||||
{ name = "hyperframe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
|
||||
]
|
||||
|
||||
[[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.optional-dependencies]
|
||||
http2 = [
|
||||
{ name = "h2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperframe"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
|
||||
]
|
||||
|
||||
[[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 = "msgpack"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proto-plus"
|
||||
version = "1.26.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.32.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/df/fb4a8eeea482eca989b51cffd274aac2ee24e825f0bf3cbce5281fa1567b/protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2", size = 440614, upload-time = "2025-08-14T21:21:25.015Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/18/df8c87da2e47f4f1dcc5153a81cd6bca4e429803f4069a299e236e4dd510/protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741", size = 424409, upload-time = "2025-08-14T21:21:12.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/59/0a820b7310f8139bd8d5a9388e6a38e1786d179d6f33998448609296c229/protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e", size = 435735, upload-time = "2025-08-14T21:21:15.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/5b/0d421533c59c789e9c9894683efac582c06246bf24bb26b753b149bd88e4/protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0", size = 426449, upload-time = "2025-08-14T21:21:16.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/7b/607764ebe6c7a23dcee06e054fd1de3d5841b7648a90fd6def9a3bb58c5e/protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1", size = 322869, upload-time = "2025-08-14T21:21:18.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/01/2e730bd1c25392fc32e3268e02446f0d77cb51a2c3a8486b1798e34d5805/protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c", size = 322009, upload-time = "2025-08-14T21:21:19.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/f2/80ffc4677aac1bc3519b26bc7f7f5de7fce0ee2f7e36e59e27d8beb32dd1/protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783", size = 169287, upload-time = "2025-08-14T21:21:23.515Z" },
|
||||
]
|
||||
|
||||
[[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 = "pycparser"
|
||||
version = "2.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-common-code"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "firebase-admin" },
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "firebase-admin", specifier = ">=7.1.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
||||
]
|
||||
|
||||
[[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 = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[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 = "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 = "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" },
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user