cluefin-openapi 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cluefin_openapi-0.1.0/.gitignore +84 -0
- cluefin_openapi-0.1.0/PKG-INFO +65 -0
- cluefin_openapi-0.1.0/README.md +53 -0
- cluefin_openapi-0.1.0/pyproject.toml +28 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/__init__.py +2 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/__init__.py +31 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_auth.py +96 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_auth_types.py +18 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_cache.py +130 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_client.py +386 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_account.py +1129 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_account_types.py +937 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_chart.py +587 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_chart_types.py +1072 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_credit_order.py +35 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_etf.py +322 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_etf_types.py +188 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_foreign.py +131 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_foreign_types.py +70 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_market_condition.py +909 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_market_condition_types.py +672 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_order.py +262 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_order_types.py +38 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_rank_info.py +1495 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_rank_info_types.py +554 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_realtime.py +22 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_sector.py +244 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_sector_types.py +192 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_stock_info.py +1280 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_stock_info_types.py +752 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_theme.py +101 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_theme_types.py +45 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_exceptions.py +77 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_model.py +28 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_rate_limiter.py +90 -0
- cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/py.typed +0 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_auth.py +25 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_account.py +342 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_chart.py +183 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_etf.py +107 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_foreign.py +67 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_market_condition.py +242 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_order.py +65 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_rank_info.py +351 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_sector.py +218 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_stock_info.py +510 -0
- cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_theme.py +52 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_auth.py +70 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_client_improvements.py +278 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_account.py +1413 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_chart.py +831 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_etf.py +448 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_foreign.py +163 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_market_condition.py +1193 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_order.py +107 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_rank_info.py +1148 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_sector.py +493 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_stock_info.py +1730 -0
- cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_theme.py +131 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
*.pyd
|
|
6
|
+
|
|
7
|
+
# C extensions
|
|
8
|
+
*.c
|
|
9
|
+
*.cpp
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
wheels/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
*.egg
|
|
17
|
+
.eggs/
|
|
18
|
+
MANIFEST
|
|
19
|
+
*.manifest
|
|
20
|
+
*.spec
|
|
21
|
+
|
|
22
|
+
# Installer logs
|
|
23
|
+
pip-log.txt
|
|
24
|
+
pip-delete-this-directory.txt
|
|
25
|
+
|
|
26
|
+
# Unit test / coverage reports
|
|
27
|
+
htmlcov/
|
|
28
|
+
.tox/
|
|
29
|
+
.nox/
|
|
30
|
+
.coverage
|
|
31
|
+
.coverage.*
|
|
32
|
+
.cache
|
|
33
|
+
nosetests.xml
|
|
34
|
+
coverage.xml
|
|
35
|
+
*.cover
|
|
36
|
+
*.py,cover
|
|
37
|
+
.hypothesis/
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
test-results/
|
|
40
|
+
|
|
41
|
+
# Jupyter Notebook
|
|
42
|
+
.ipynb_checkpoints
|
|
43
|
+
|
|
44
|
+
# pyenv
|
|
45
|
+
.python-version
|
|
46
|
+
|
|
47
|
+
# mypy
|
|
48
|
+
.mypy_cache/
|
|
49
|
+
.dmypy.json
|
|
50
|
+
dmypy.json
|
|
51
|
+
|
|
52
|
+
# Pyre type checker
|
|
53
|
+
.pyre/
|
|
54
|
+
|
|
55
|
+
# VS Code
|
|
56
|
+
.vscode/
|
|
57
|
+
.code-workspace
|
|
58
|
+
|
|
59
|
+
# PyCharm
|
|
60
|
+
.idea/
|
|
61
|
+
*.iml
|
|
62
|
+
|
|
63
|
+
# macOS
|
|
64
|
+
.DS_Store
|
|
65
|
+
|
|
66
|
+
# Virtual environments
|
|
67
|
+
.venv/
|
|
68
|
+
venv/
|
|
69
|
+
ENV/
|
|
70
|
+
env/
|
|
71
|
+
env.bak/
|
|
72
|
+
venv.bak/
|
|
73
|
+
|
|
74
|
+
# Lock files
|
|
75
|
+
Pipfile.lock
|
|
76
|
+
poetry.lock
|
|
77
|
+
uv.lock
|
|
78
|
+
|
|
79
|
+
# Project-specific
|
|
80
|
+
packages/*/src/*/__pycache__/
|
|
81
|
+
packages/*/src/*/*.py[cod]
|
|
82
|
+
|
|
83
|
+
# Environment variables
|
|
84
|
+
.env.*
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cluefin-openapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenAPI Client for Cluefin
|
|
5
|
+
Author-email: Hangoo Kang <kgcrom@hotmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: loguru>=0.7.3
|
|
8
|
+
Requires-Dist: pydantic>=2.11.7
|
|
9
|
+
Requires-Dist: requests>=2.32.4
|
|
10
|
+
Provides-Extra: kiwoom
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# cluefin-openapi
|
|
14
|
+
|
|
15
|
+
> **cluefin-openapi**: A Python client for Kiwoom Securities investment REST API.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
- Account information retrieval
|
|
21
|
+
- Domestic/foreign stock info
|
|
22
|
+
- Chart data and analytics
|
|
23
|
+
- ETF, sector, theme, and market condition support
|
|
24
|
+
- Order management and real-time updates
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
```bash
|
|
28
|
+
$> pip install cluefin-openapi
|
|
29
|
+
or
|
|
30
|
+
$> pip install cluefin-openapi[kiwoom]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from cluefin_openapi.kiwoom._auth import Auth
|
|
35
|
+
from cluefin_openapi.kiwoom._client import Client
|
|
36
|
+
|
|
37
|
+
auth = Auth(
|
|
38
|
+
app_key=os.getenv("APP_KEY"),
|
|
39
|
+
secret_key=os.getenv("SECRET_KEY"),
|
|
40
|
+
env="dev",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
token = auth.generate_token()
|
|
44
|
+
client = Client(token=token.token, env="dev")
|
|
45
|
+
|
|
46
|
+
response = client.account.get_daily_stock_realized_profit_loss_by_date("005930", "20250630")
|
|
47
|
+
print(response.headers)
|
|
48
|
+
print(response.body)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Why cluefin-openapi?
|
|
52
|
+
Easily access investment data and trading features from Kiwoom Securities, DART, KRX, and more—all through a unified Python interface.
|
|
53
|
+
Save time integrating with multiple financial APIs and focus on building your investment tools.
|
|
54
|
+
|
|
55
|
+
## Getting Started
|
|
56
|
+
1. Install via pip
|
|
57
|
+
2. Obtain your Kiwoom REST API credentials
|
|
58
|
+
3. See [examples](./test/integration/kiwoom/) for more usage
|
|
59
|
+
|
|
60
|
+
## Contributing
|
|
61
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
*Invest responsibly. This project is not affiliated with Kiwoom Securities.*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# cluefin-openapi
|
|
2
|
+
|
|
3
|
+
> **cluefin-openapi**: A Python client for Kiwoom Securities investment REST API.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- Account information retrieval
|
|
9
|
+
- Domestic/foreign stock info
|
|
10
|
+
- Chart data and analytics
|
|
11
|
+
- ETF, sector, theme, and market condition support
|
|
12
|
+
- Order management and real-time updates
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
```bash
|
|
16
|
+
$> pip install cluefin-openapi
|
|
17
|
+
or
|
|
18
|
+
$> pip install cluefin-openapi[kiwoom]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from cluefin_openapi.kiwoom._auth import Auth
|
|
23
|
+
from cluefin_openapi.kiwoom._client import Client
|
|
24
|
+
|
|
25
|
+
auth = Auth(
|
|
26
|
+
app_key=os.getenv("APP_KEY"),
|
|
27
|
+
secret_key=os.getenv("SECRET_KEY"),
|
|
28
|
+
env="dev",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
token = auth.generate_token()
|
|
32
|
+
client = Client(token=token.token, env="dev")
|
|
33
|
+
|
|
34
|
+
response = client.account.get_daily_stock_realized_profit_loss_by_date("005930", "20250630")
|
|
35
|
+
print(response.headers)
|
|
36
|
+
print(response.body)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Why cluefin-openapi?
|
|
40
|
+
Easily access investment data and trading features from Kiwoom Securities, DART, KRX, and more—all through a unified Python interface.
|
|
41
|
+
Save time integrating with multiple financial APIs and focus on building your investment tools.
|
|
42
|
+
|
|
43
|
+
## Getting Started
|
|
44
|
+
1. Install via pip
|
|
45
|
+
2. Obtain your Kiwoom REST API credentials
|
|
46
|
+
3. See [examples](./test/integration/kiwoom/) for more usage
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
*Invest responsibly. This project is not affiliated with Kiwoom Securities.*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cluefin-openapi"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "OpenAPI Client for Cluefin"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Hangoo Kang", email = "kgcrom@hotmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"loguru>=0.7.3",
|
|
12
|
+
"pydantic>=2.11.7",
|
|
13
|
+
"requests>=2.32.4",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
kiwoom = []
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["hatchling"]
|
|
21
|
+
build-backend = "hatchling.build"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["src/cluefin_openapi"]
|
|
25
|
+
|
|
26
|
+
[tool.ruff]
|
|
27
|
+
extend = "../../pyproject.toml"
|
|
28
|
+
include = ["src/**", "tests/**"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Cluefin Kiwoom API Client Package."""
|
|
2
|
+
|
|
3
|
+
from ._auth import Auth
|
|
4
|
+
from ._client import Client
|
|
5
|
+
from ._exceptions import (
|
|
6
|
+
KiwoomAPIError,
|
|
7
|
+
KiwoomAuthenticationError,
|
|
8
|
+
KiwoomAuthorizationError,
|
|
9
|
+
KiwoomNetworkError,
|
|
10
|
+
KiwoomRateLimitError,
|
|
11
|
+
KiwoomServerError,
|
|
12
|
+
KiwoomTimeoutError,
|
|
13
|
+
KiwoomValidationError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Auth",
|
|
18
|
+
"Client",
|
|
19
|
+
"KiwoomAPIError",
|
|
20
|
+
"KiwoomAuthenticationError",
|
|
21
|
+
"KiwoomAuthorizationError",
|
|
22
|
+
"KiwoomNetworkError",
|
|
23
|
+
"KiwoomRateLimitError",
|
|
24
|
+
"KiwoomServerError",
|
|
25
|
+
"KiwoomTimeoutError",
|
|
26
|
+
"KiwoomValidationError",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def hello() -> str:
|
|
31
|
+
return "Hello from cluefin-kiwoom!"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Authentication module for Kiwoom API.
|
|
2
|
+
|
|
3
|
+
This module provides functionality for generating and revoking API tokens
|
|
4
|
+
using client credentials authentication flow.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from ._auth_types import TokenResponse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Auth:
|
|
15
|
+
"""Initialize the Auth client.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
app_key: The application key provided by Kiwoom.
|
|
19
|
+
secret_key: The secret key provided by Kiwoom.
|
|
20
|
+
env: The environment to use. Either "dev" or "prod".
|
|
21
|
+
Defaults to "dev".
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If an invalid environment is provided.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, app_key: str, secret_key: str, env: Literal["dev", "prod"] = "dev") -> None:
|
|
28
|
+
self.app_key = app_key
|
|
29
|
+
self.secret_key = secret_key
|
|
30
|
+
|
|
31
|
+
if env == "dev":
|
|
32
|
+
self.url = "https://mockapi.kiwoom.com"
|
|
33
|
+
elif env == "prod":
|
|
34
|
+
self.url = "https://api.kiwoom.com"
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError("Invalid environment. Must be either 'dev' or 'prod'.")
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def token(self) -> TokenResponse:
|
|
40
|
+
"""Get the current token data.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
TokenResponse: The current token data.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
AttributeError: If no token has been generated yet.
|
|
47
|
+
"""
|
|
48
|
+
return self._token_data
|
|
49
|
+
|
|
50
|
+
def generate_token(self) -> TokenResponse:
|
|
51
|
+
"""Generate a new access token.
|
|
52
|
+
|
|
53
|
+
Calls the Kiwoom OAuth2 token endpoint to generate a new access token
|
|
54
|
+
using the client credentials flow.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
TokenResponse: The generated token data including access token
|
|
58
|
+
and expiration.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
requests.exceptions.HTTPError: If the API request fails.
|
|
62
|
+
"""
|
|
63
|
+
headers = {
|
|
64
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
65
|
+
}
|
|
66
|
+
data = {"grant_type": "client_credentials", "appkey": self.app_key, "secretkey": self.secret_key}
|
|
67
|
+
|
|
68
|
+
response = requests.post(f"{self.url}/oauth2/token", headers=headers, json=data)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
|
|
71
|
+
token_data = TokenResponse(**response.json())
|
|
72
|
+
self._token_data = token_data
|
|
73
|
+
|
|
74
|
+
return self._token_data
|
|
75
|
+
|
|
76
|
+
def revoke_token(self, token: str) -> bool:
|
|
77
|
+
"""Revoke an access token.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
token: The token to revoke.
|
|
81
|
+
Returns:
|
|
82
|
+
bool: True if the token was successfully revoked.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
requests.exceptions.HTTPError: If the API request fails.
|
|
86
|
+
"""
|
|
87
|
+
headers = {
|
|
88
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
data = {"appkey": self.app_key, "secretkey": self.secret_key, "token": token}
|
|
92
|
+
|
|
93
|
+
response = requests.post(f"{self.url}/oauth2/revoke", headers=headers, json=data)
|
|
94
|
+
response.raise_for_status()
|
|
95
|
+
|
|
96
|
+
return True
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, field_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TokenResponse(BaseModel):
|
|
7
|
+
expires_dt: datetime
|
|
8
|
+
token_type: str
|
|
9
|
+
token: str
|
|
10
|
+
|
|
11
|
+
@field_validator("expires_dt", mode="before")
|
|
12
|
+
def parse_expires_dt(cls, v):
|
|
13
|
+
if isinstance(v, str):
|
|
14
|
+
try:
|
|
15
|
+
return datetime.strptime(v, "%Y%m%d%H%M%S")
|
|
16
|
+
except ValueError:
|
|
17
|
+
pass # 다른 형식도 추가 가능
|
|
18
|
+
return v
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Caching utilities for Kiwoom API client."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimpleCache:
|
|
9
|
+
"""Simple in-memory cache with TTL support."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, default_ttl: int = 300):
|
|
12
|
+
"""
|
|
13
|
+
Initialize cache.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
default_ttl: Default time-to-live in seconds (default: 5 minutes)
|
|
17
|
+
"""
|
|
18
|
+
self.default_ttl = default_ttl
|
|
19
|
+
self._cache: Dict[str, Dict[str, Any]] = {}
|
|
20
|
+
|
|
21
|
+
def get(self, key: str) -> Optional[Any]:
|
|
22
|
+
"""
|
|
23
|
+
Get value from cache.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
key: Cache key
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Cached value or None if not found/expired
|
|
30
|
+
"""
|
|
31
|
+
if key in self._cache:
|
|
32
|
+
entry = self._cache[key]
|
|
33
|
+
if time.time() < entry["expires_at"]:
|
|
34
|
+
return entry["value"]
|
|
35
|
+
else:
|
|
36
|
+
# Remove expired entry
|
|
37
|
+
del self._cache[key]
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Set value in cache.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: Cache key
|
|
46
|
+
value: Value to cache
|
|
47
|
+
ttl: Time-to-live in seconds (uses default if None)
|
|
48
|
+
"""
|
|
49
|
+
if ttl is None:
|
|
50
|
+
ttl = self.default_ttl
|
|
51
|
+
|
|
52
|
+
self._cache[key] = {
|
|
53
|
+
"value": value,
|
|
54
|
+
"expires_at": time.time() + ttl,
|
|
55
|
+
"created_at": time.time(),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def delete(self, key: str) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Delete key from cache.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key: Cache key
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if key was deleted, False if not found
|
|
67
|
+
"""
|
|
68
|
+
if key in self._cache:
|
|
69
|
+
del self._cache[key]
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def clear(self) -> None:
|
|
74
|
+
"""Clear all cached entries."""
|
|
75
|
+
self._cache.clear()
|
|
76
|
+
|
|
77
|
+
def cleanup_expired(self) -> int:
|
|
78
|
+
"""
|
|
79
|
+
Remove expired entries from cache.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Number of entries removed
|
|
83
|
+
"""
|
|
84
|
+
current_time = time.time()
|
|
85
|
+
expired_keys = [key for key, entry in self._cache.items() if current_time >= entry["expires_at"]]
|
|
86
|
+
|
|
87
|
+
for key in expired_keys:
|
|
88
|
+
del self._cache[key]
|
|
89
|
+
|
|
90
|
+
return len(expired_keys)
|
|
91
|
+
|
|
92
|
+
def cache_info(self) -> Dict[str, Any]:
|
|
93
|
+
"""Get cache statistics."""
|
|
94
|
+
current_time = time.time()
|
|
95
|
+
valid_entries = sum(1 for entry in self._cache.values() if current_time < entry["expires_at"])
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"total_entries": len(self._cache),
|
|
99
|
+
"valid_entries": valid_entries,
|
|
100
|
+
"expired_entries": len(self._cache) - valid_entries,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_cache_key(url: str, headers: Dict[str, str], body: Dict[str, Any]) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Create a cache key from request parameters.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
url: Request URL
|
|
110
|
+
headers: Request headers (excluding auth and dynamic headers)
|
|
111
|
+
body: Request body
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Cache key string
|
|
115
|
+
"""
|
|
116
|
+
# Filter out dynamic headers that shouldn't affect caching
|
|
117
|
+
cacheable_headers = {
|
|
118
|
+
k: v for k, v in headers.items() if k.lower() not in ["authorization", "user-agent", "content-length"]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Create a deterministic string representation
|
|
122
|
+
cache_data = {
|
|
123
|
+
"url": url,
|
|
124
|
+
"headers": sorted(cacheable_headers.items()),
|
|
125
|
+
"body": sorted(body.items()) if isinstance(body, dict) else body,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Create hash of the cache data
|
|
129
|
+
cache_string = str(cache_data)
|
|
130
|
+
return hashlib.sha256(cache_string.encode()).hexdigest()[:16] # First 16 chars for brevity
|