crypticorn-utils 0.1.0rc1__py3-none-any.whl → 1.0.0rc1__py3-none-any.whl
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.
- crypticorn_utils/__init__.py +50 -16
- crypticorn_utils/_migration.py +2 -1
- crypticorn_utils/ansi_colors.py +1 -4
- crypticorn_utils/auth.py +112 -54
- crypticorn_utils/enums.py +3 -123
- crypticorn_utils/errors.py +3 -893
- crypticorn_utils/exceptions.py +244 -139
- crypticorn_utils/logging.py +5 -19
- crypticorn_utils/metrics.py +1 -2
- crypticorn_utils/middleware.py +7 -40
- crypticorn_utils/utils.py +1 -52
- crypticorn_utils/warnings.py +0 -28
- {crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/METADATA +20 -5
- crypticorn_utils-1.0.0rc1.dist-info/RECORD +21 -0
- {crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/licenses/LICENSE +1 -1
- crypticorn_utils/cli/__init__.py +0 -4
- crypticorn_utils/cli/__main__.py +0 -17
- crypticorn_utils/cli/init.py +0 -127
- crypticorn_utils/cli/templates/auth.py +0 -33
- crypticorn_utils/cli/version.py +0 -8
- crypticorn_utils/mixins.py +0 -68
- crypticorn_utils/openapi.py +0 -10
- crypticorn_utils/router/admin_router.py +0 -117
- crypticorn_utils/router/status_router.py +0 -36
- crypticorn_utils-0.1.0rc1.dist-info/RECORD +0 -30
- /crypticorn_utils/{cli/templates/__init__.py → py.typed} +0 -0
- {crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/WHEEL +0 -0
- {crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/entry_points.txt +0 -0
- {crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: crypticorn_utils
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0rc1
|
4
4
|
Summary: Shared utilities for the Crypticorn APIs
|
5
5
|
Author-email: Crypticorn <timon@crypticorn.com>
|
6
6
|
License-Expression: MIT
|
@@ -11,20 +11,17 @@ Classifier: Topic :: Scientific/Engineering
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
12
12
|
Classifier: Intended Audience :: Developers
|
13
13
|
Classifier: Operating System :: OS Independent
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
16
14
|
Classifier: Programming Language :: Python :: 3.11
|
17
15
|
Classifier: Programming Language :: Python :: 3.12
|
18
16
|
Classifier: Programming Language :: Python :: 3.13
|
19
17
|
Classifier: Typing :: Typed
|
20
|
-
Requires-Python: >=3.
|
18
|
+
Requires-Python: >=3.11
|
21
19
|
Description-Content-Type: text/markdown
|
22
20
|
License-File: LICENSE
|
23
21
|
Requires-Dist: fastapi<1.0.0,>=0.115.0
|
24
22
|
Requires-Dist: click<9.0.0,>=8.0.0
|
25
23
|
Requires-Dist: psutil<8.0.0,>=7.0.0
|
26
24
|
Requires-Dist: setuptools<81.0.0,>=80.0.0
|
27
|
-
Requires-Dist: strenum
|
28
25
|
Requires-Dist: prometheus-client<1.0.0,>=0.22.0
|
29
26
|
Provides-Extra: dev
|
30
27
|
Requires-Dist: build; extra == "dev"
|
@@ -68,6 +65,24 @@ This module serves as a central place for providing utilities for our python bac
|
|
68
65
|
|
69
66
|
<!-- changelog-insertion -->
|
70
67
|
|
68
|
+
## v1.0.0-rc.1 (2025-07-17)
|
69
|
+
|
70
|
+
### Build System
|
71
|
+
|
72
|
+
- Deployment config for v1 branches
|
73
|
+
([`b94d9e7`](https://github.com/crypticorn-ai/util-libraries/commit/b94d9e72616e398760993f6ebb1a6fd876a95802))
|
74
|
+
|
75
|
+
BREAKING CHANGE: - removed: mixins, openapi and both router modules; CLI; Scope Enum class;
|
76
|
+
`throw_if_none` and `throw_if_falsy`; all deprecation warnings - reworked: exceptions and error
|
77
|
+
modules
|
78
|
+
|
79
|
+
- Mark packaage as typed
|
80
|
+
([`69544a8`](https://github.com/crypticorn-ai/util-libraries/commit/69544a8709f4d55850e107031b82d91c28334b3c))
|
81
|
+
|
82
|
+
- Remove support for python versions 3.9 and 3.10
|
83
|
+
([`80b8543`](https://github.com/crypticorn-ai/util-libraries/commit/80b8543ed5559a0de421aef4e2382193e930751a))
|
84
|
+
|
85
|
+
|
71
86
|
## v0.1.0-rc.1 (2025-06-23)
|
72
87
|
|
73
88
|
### Documentation
|
@@ -0,0 +1,21 @@
|
|
1
|
+
crypticorn_utils/__init__.py,sha256=hZhTmMU8-1oY382nJH-l4B7jH8nQzxFeMaLzEXC-1hc,1406
|
2
|
+
crypticorn_utils/_migration.py,sha256=2WfApGi5w46SDT0GjUA0Iz8KuDTSwrPNGJCyG9AbLb4,519
|
3
|
+
crypticorn_utils/ansi_colors.py,sha256=ts49UtfTy-c0uvlGwb3wE-jE_GbXvSBSfzwrDlNi0HE,1331
|
4
|
+
crypticorn_utils/auth.py,sha256=23b3oj-A0h44wucGSC6n1R44ytoNalW9jcjE-nhP2OU,14518
|
5
|
+
crypticorn_utils/decorators.py,sha256=chsbF27_q3hC0egBaZLfv2vcqUcBSfQXRkLi3Ewb-20,1063
|
6
|
+
crypticorn_utils/enums.py,sha256=QftU5ebtdGD9b7mBPoWDVkuaJEoxiFgt9ew_-PcvCZc,1330
|
7
|
+
crypticorn_utils/errors.py,sha256=PYYB-qeL9PCE8m5XyRXfkGGjsPgX4Vc_4Ii1-7yeW50,771
|
8
|
+
crypticorn_utils/exceptions.py,sha256=Rv1OYZp4CsK6VAtc4nhpyL28fK2aVWkFYSdECmJK4VE,9305
|
9
|
+
crypticorn_utils/logging.py,sha256=ORt-uZ47kqnbgtp6rJfcVvxy_KYlfHTyuWM4UF755s4,3968
|
10
|
+
crypticorn_utils/metrics.py,sha256=3eMt1DMFIIm835YYqOl41cpzeRA8nxl3nHcnQcTgZeA,866
|
11
|
+
crypticorn_utils/middleware.py,sha256=CPp_vPwm7LXNt71uFBReVwUrqJqur6xUErWurMx18cw,2902
|
12
|
+
crypticorn_utils/pagination.py,sha256=06shym28tfD-Cc9EPk2W7K2GHqUj2Kk4ShQ0BrKCDWY,11432
|
13
|
+
crypticorn_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
crypticorn_utils/utils.py,sha256=tFFgMjkitY4AtHkyfyb0Nu87u9gIAPwga6SML3skBZk,1459
|
15
|
+
crypticorn_utils/warnings.py,sha256=52gLJs-1YJiJebahY2hDkyN_reCY21YXzZfIOCHBYWg,1775
|
16
|
+
crypticorn_utils-1.0.0rc1.dist-info/licenses/LICENSE,sha256=DEU3BF944yFmGthgeH70d9TA3lB9hGfvwTtu6VDYPO0,1857
|
17
|
+
crypticorn_utils-1.0.0rc1.dist-info/METADATA,sha256=p1fcztHRmMjaUFoA5QguWqC5NhGHSxuJjVMjm9gTWSw,4578
|
18
|
+
crypticorn_utils-1.0.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
19
|
+
crypticorn_utils-1.0.0rc1.dist-info/entry_points.txt,sha256=G4yWDll7v_Kb4uemqI-_qTKXHvs10R4mD7hNuD-biv4,71
|
20
|
+
crypticorn_utils-1.0.0rc1.dist-info/top_level.txt,sha256=jLPvdxnI36RPf8TX3aZhl35OSd721xEYyFSEeQKF6Ic,17
|
21
|
+
crypticorn_utils-1.0.0rc1.dist-info/RECORD,,
|
{crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/licenses/LICENSE
RENAMED
@@ -12,4 +12,4 @@ Changes to this agreement may be made without notice, and it is the user's respo
|
|
12
12
|
|
13
13
|
If any provision of this license agreement is held to be invalid or unenforceable under applicable law, the remaining provisions will continue in full force and effect. Crypticorn's failure to enforce any provision of this agreement does not constitute a waiver of its right to do so in the future.
|
14
14
|
|
15
|
-
This agreement is written in English. In the event of any discrepancy between the English version and any translation, the English version shall prevail.
|
15
|
+
This agreement is written in English. In the event of any discrepancy between the English version and any translation, the English version shall prevail.
|
crypticorn_utils/cli/__init__.py
DELETED
crypticorn_utils/cli/__main__.py
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# crypticorn/cli.py
|
2
|
-
|
3
|
-
import click
|
4
|
-
from crypticorn.cli import init_group, version
|
5
|
-
|
6
|
-
|
7
|
-
@click.group()
|
8
|
-
def cli():
|
9
|
-
"""🧙 Crypticorn CLI — magic for our microservices."""
|
10
|
-
pass
|
11
|
-
|
12
|
-
|
13
|
-
cli.add_command(init_group, name="init")
|
14
|
-
cli.add_command(version, name="version")
|
15
|
-
|
16
|
-
if __name__ == "__main__":
|
17
|
-
cli()
|
crypticorn_utils/cli/init.py
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
import click
|
2
|
-
from pathlib import Path
|
3
|
-
import subprocess
|
4
|
-
import importlib.resources
|
5
|
-
import crypticorn.cli.templates as templates
|
6
|
-
|
7
|
-
|
8
|
-
def get_git_root() -> Path:
|
9
|
-
"""Get the root directory of the git repository."""
|
10
|
-
try:
|
11
|
-
return Path(
|
12
|
-
subprocess.check_output(
|
13
|
-
["git", "rev-parse", "--show-toplevel"], text=True
|
14
|
-
).strip()
|
15
|
-
)
|
16
|
-
except Exception:
|
17
|
-
return Path.cwd()
|
18
|
-
|
19
|
-
|
20
|
-
def copy_template(template_name: str, target_path: Path):
|
21
|
-
"""Copy a template file to the target path."""
|
22
|
-
with importlib.resources.files(templates).joinpath(template_name).open(
|
23
|
-
"r"
|
24
|
-
) as template_file:
|
25
|
-
content = template_file.read()
|
26
|
-
|
27
|
-
target_path.parent.mkdir(parents=True, exist_ok=True)
|
28
|
-
with target_path.open("w") as f:
|
29
|
-
f.write(content)
|
30
|
-
|
31
|
-
click.secho(f"✅ Created: {target_path}", fg="green")
|
32
|
-
|
33
|
-
|
34
|
-
def check_file_exists(path: Path, force: bool):
|
35
|
-
if path.exists() and not force:
|
36
|
-
click.secho("File already exists, use --force / -f to overwrite", fg="red")
|
37
|
-
return False
|
38
|
-
return True
|
39
|
-
|
40
|
-
|
41
|
-
@click.group()
|
42
|
-
def init_group():
|
43
|
-
"""Initialize files like CI configs, linters, etc."""
|
44
|
-
pass
|
45
|
-
|
46
|
-
|
47
|
-
@init_group.command("ruff")
|
48
|
-
@click.option("-f", "--force", is_flag=True, help="Force overwrite the ruff.yml")
|
49
|
-
def init_ruff(force):
|
50
|
-
"""Add .github/workflows/ruff.yml"""
|
51
|
-
root = get_git_root()
|
52
|
-
target = root / ".github/workflows/ruff.yml"
|
53
|
-
if target.exists() and not force:
|
54
|
-
click.secho("File already exists, use --force / -f to overwrite", fg="red")
|
55
|
-
return
|
56
|
-
copy_template("ruff.yml", target)
|
57
|
-
|
58
|
-
|
59
|
-
@init_group.command("docker")
|
60
|
-
@click.option(
|
61
|
-
"-o", "--output", type=click.Path(), help="Custom output path for the Dockerfile"
|
62
|
-
)
|
63
|
-
@click.option("-f", "--force", is_flag=True, help="Force overwrite the Dockerfile")
|
64
|
-
def init_docker(output, force):
|
65
|
-
"""Add Dockerfile"""
|
66
|
-
root = get_git_root()
|
67
|
-
if output and Path(output).is_file():
|
68
|
-
click.secho("Output path is a file, please provide a directory path", fg="red")
|
69
|
-
return
|
70
|
-
target = (Path(output) if output else root) / "Dockerfile"
|
71
|
-
if not check_file_exists(target, force):
|
72
|
-
return
|
73
|
-
copy_template("Dockerfile", target)
|
74
|
-
click.secho("Make sure to update the Dockerfile", fg="yellow")
|
75
|
-
|
76
|
-
|
77
|
-
@init_group.command("auth")
|
78
|
-
@click.option(
|
79
|
-
"-o", "--output", type=click.Path(), help="Custom output path for the auth handler"
|
80
|
-
)
|
81
|
-
@click.option("-f", "--force", is_flag=True, help="Force overwrite the auth handler")
|
82
|
-
def init_auth(output, force):
|
83
|
-
"""Add auth.py with auth handler. Everything you need to start using the auth service."""
|
84
|
-
root = get_git_root()
|
85
|
-
if output and Path(output).is_file():
|
86
|
-
click.secho("Output path is a file, please provide a directory path", fg="red")
|
87
|
-
return
|
88
|
-
target = (Path(output) if output else root) / "auth.py"
|
89
|
-
if not check_file_exists(target, force):
|
90
|
-
return
|
91
|
-
copy_template("auth.py", target)
|
92
|
-
click.secho(
|
93
|
-
"""
|
94
|
-
Make sure to update the .env and .env.example files with:
|
95
|
-
IS_DOCKER=0
|
96
|
-
API_ENV=local
|
97
|
-
and the docker-compose.yml file with:
|
98
|
-
environment:
|
99
|
-
- IS_DOCKER=1
|
100
|
-
and the .github/workflows/main.yaml file with:
|
101
|
-
env:
|
102
|
-
API_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
|
103
|
-
""",
|
104
|
-
fg="yellow",
|
105
|
-
)
|
106
|
-
|
107
|
-
|
108
|
-
@init_group.command("dependabot")
|
109
|
-
@click.option("-f", "--force", is_flag=True, help="Force overwrite the dependabot.yml")
|
110
|
-
def init_dependabot(force):
|
111
|
-
"""Add dependabot.yml"""
|
112
|
-
root = get_git_root()
|
113
|
-
target = root / ".github/dependabot.yml"
|
114
|
-
if not check_file_exists(target, force):
|
115
|
-
return
|
116
|
-
copy_template("dependabot.yml", target)
|
117
|
-
|
118
|
-
|
119
|
-
@init_group.command("merge-env")
|
120
|
-
@click.option("-f", "--force", is_flag=True, help="Force overwrite the .env file")
|
121
|
-
def init_merge_env(force):
|
122
|
-
"""Add script to merge environment and file variables into .env"""
|
123
|
-
root = get_git_root()
|
124
|
-
target = root / "scripts/merge-env.sh"
|
125
|
-
if not check_file_exists(target, force):
|
126
|
-
return
|
127
|
-
copy_template("merge-env.sh", target)
|
@@ -1,33 +0,0 @@
|
|
1
|
-
from crypticorn.common import (
|
2
|
-
AuthHandler as AuthHandler,
|
3
|
-
Scope as Scope,
|
4
|
-
Verify200Response as Verify200Response,
|
5
|
-
BaseUrl as BaseUrl,
|
6
|
-
ApiEnv as ApiEnv,
|
7
|
-
)
|
8
|
-
from fastapi import Security as Security
|
9
|
-
import os
|
10
|
-
import dotenv
|
11
|
-
import logging
|
12
|
-
|
13
|
-
dotenv.load_dotenv()
|
14
|
-
|
15
|
-
logger = logging.getLogger(__name__)
|
16
|
-
|
17
|
-
DOCKER_ENV = os.getenv("IS_DOCKER", "0")
|
18
|
-
API_ENV = os.getenv("API_ENV")
|
19
|
-
|
20
|
-
if not API_ENV:
|
21
|
-
raise ValueError(
|
22
|
-
"API_ENV is not set. Please set it to 'prod', 'dev' or 'local' in .env (of type ApiEnv)."
|
23
|
-
)
|
24
|
-
|
25
|
-
if DOCKER_ENV == "0":
|
26
|
-
logger.info(f"Using {API_ENV} environment")
|
27
|
-
base_url = BaseUrl.from_env(ApiEnv(API_ENV))
|
28
|
-
else:
|
29
|
-
base_url = BaseUrl.DOCKER
|
30
|
-
logger.info("Using docker environment")
|
31
|
-
|
32
|
-
auth_handler = AuthHandler(base_url=base_url)
|
33
|
-
logger.info(f"Auth URL: {auth_handler.client.config.host}")
|
crypticorn_utils/cli/version.py
DELETED
crypticorn_utils/mixins.py
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import warnings
|
3
|
-
from enum import EnumMeta
|
4
|
-
|
5
|
-
from crypticorn_utils.warnings import CrypticornDeprecatedSince28
|
6
|
-
|
7
|
-
_logger = logging.getLogger("crypticorn")
|
8
|
-
|
9
|
-
|
10
|
-
class ValidateEnumMixin:
|
11
|
-
"""
|
12
|
-
Mixin for validating enum values manually.
|
13
|
-
|
14
|
-
⚠️ Note:
|
15
|
-
This does NOT enforce validation automatically on enum creation.
|
16
|
-
It's up to the developer to call `Class.validate(value)` where needed.
|
17
|
-
|
18
|
-
Usage:
|
19
|
-
>>> class Color(ValidateEnumMixin, StrEnum):
|
20
|
-
>>> RED = "red"
|
21
|
-
>>> GREEN = "green"
|
22
|
-
|
23
|
-
>>> Color.validate("red") # True
|
24
|
-
>>> Color.validate("yellow") # False
|
25
|
-
|
26
|
-
Order of inheritance matters — the mixin must come first.
|
27
|
-
"""
|
28
|
-
|
29
|
-
@classmethod
|
30
|
-
def validate(cls, value) -> bool:
|
31
|
-
"""Validate if a value is in the enum. True if so, False otherwise."""
|
32
|
-
try:
|
33
|
-
cls(value)
|
34
|
-
return True
|
35
|
-
except ValueError:
|
36
|
-
return False
|
37
|
-
|
38
|
-
|
39
|
-
# This Mixin will be removed in a future version. And has no effect from now on
|
40
|
-
class ExcludeEnumMixin:
|
41
|
-
"""(deprecated) Mixin to exclude enum from OpenAPI schema. We use this to avoid duplicating enums when generating client code from the openapi spec."""
|
42
|
-
|
43
|
-
def __init_subclass__(cls, **kwargs):
|
44
|
-
super().__init_subclass__(**kwargs)
|
45
|
-
if cls.__name__.startswith("ExcludeEnumMixin"):
|
46
|
-
warnings.warn(
|
47
|
-
"The `ExcludeEnumMixin` class is deprecated. Should be removed from enums inheriting this class.",
|
48
|
-
category=CrypticornDeprecatedSince28,
|
49
|
-
)
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def __get_pydantic_json_schema__(cls, core_schema, handler):
|
53
|
-
schema = handler(core_schema)
|
54
|
-
# schema.pop("enum", None)
|
55
|
-
return schema
|
56
|
-
|
57
|
-
|
58
|
-
class ApiErrorFallback(EnumMeta):
|
59
|
-
"""Fallback for enum members that are not yet published to PyPI."""
|
60
|
-
|
61
|
-
def __getattr__(cls, name):
|
62
|
-
# Let Pydantic/internal stuff pass silently ! fragile
|
63
|
-
if name.startswith("__") or name.startswith("_pytest"):
|
64
|
-
raise AttributeError(name)
|
65
|
-
_logger.warning(
|
66
|
-
f"Unknown enum member '{name}' - update crypticorn package or check for typos"
|
67
|
-
)
|
68
|
-
return cls.UNKNOWN_ERROR
|
crypticorn_utils/openapi.py
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains the admin router for the API.
|
3
|
-
It provides endpoints for monitoring the server and getting information about the environment.
|
4
|
-
ONLY ALLOW ACCESS TO THIS ROUTER WITH ADMIN SCOPES.
|
5
|
-
>>> app.include_router(admin_router, dependencies=[Security(auth_handler.full_auth, scopes=[Scope.READ_ADMIN, Scope.WRITE_ADMIN])])
|
6
|
-
"""
|
7
|
-
|
8
|
-
import importlib.metadata
|
9
|
-
import logging
|
10
|
-
import os
|
11
|
-
import re
|
12
|
-
import threading
|
13
|
-
import time
|
14
|
-
from typing import Literal
|
15
|
-
|
16
|
-
import psutil
|
17
|
-
from crypticorn_utils.logging import LogLevel
|
18
|
-
from crypticorn_utils.metrics import registry
|
19
|
-
from fastapi import APIRouter, Query, Response
|
20
|
-
from prometheus_client import CONTENT_TYPE_LATEST, generate_latest
|
21
|
-
|
22
|
-
router = APIRouter(tags=["Admin"], prefix="/admin")
|
23
|
-
|
24
|
-
START_TIME = time.time()
|
25
|
-
|
26
|
-
|
27
|
-
@router.get("/log-level", status_code=200, operation_id="getLogLevel", deprecated=True)
|
28
|
-
async def get_logging_level() -> LogLevel:
|
29
|
-
"""
|
30
|
-
Get the log level of the server logger. Will be removed in a future release.
|
31
|
-
"""
|
32
|
-
return LogLevel.get_name(logging.getLogger().level)
|
33
|
-
|
34
|
-
|
35
|
-
@router.get("/uptime", operation_id="getUptime", status_code=200)
|
36
|
-
def get_uptime(type: Literal["seconds", "human"] = "seconds") -> str:
|
37
|
-
"""Return the server uptime in seconds or human-readable form."""
|
38
|
-
uptime_seconds = int(time.time() - START_TIME)
|
39
|
-
if type == "seconds":
|
40
|
-
return str(uptime_seconds)
|
41
|
-
elif type == "human":
|
42
|
-
return time.strftime("%H:%M:%S", time.gmtime(uptime_seconds))
|
43
|
-
|
44
|
-
|
45
|
-
@router.get("/memory", operation_id="getMemoryUsage", status_code=200)
|
46
|
-
def get_memory_usage() -> float:
|
47
|
-
"""
|
48
|
-
Resident Set Size (RSS) in MB — the actual memory used by the process in RAM.
|
49
|
-
Represents the physical memory footprint. Important for monitoring real usage.
|
50
|
-
"""
|
51
|
-
process = psutil.Process(os.getpid())
|
52
|
-
mem_info = process.memory_info()
|
53
|
-
return round(mem_info.rss / (1024 * 1024), 2)
|
54
|
-
|
55
|
-
|
56
|
-
@router.get("/threads", operation_id="getThreads", status_code=200)
|
57
|
-
def get_threads() -> dict:
|
58
|
-
"""Return count and names of active threads."""
|
59
|
-
threads = threading.enumerate()
|
60
|
-
return {
|
61
|
-
"count": len(threads),
|
62
|
-
"threads": [t.name for t in threads],
|
63
|
-
}
|
64
|
-
|
65
|
-
|
66
|
-
@router.get("/limits", operation_id="getContainerLimits", status_code=200)
|
67
|
-
def get_container_limits() -> dict:
|
68
|
-
"""Return container resource limits from cgroup."""
|
69
|
-
limits = {}
|
70
|
-
try:
|
71
|
-
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes") as f:
|
72
|
-
limits["memory_limit_MB"] = int(f.read().strip()) / 1024 / 1024
|
73
|
-
except Exception:
|
74
|
-
limits["memory_limit_MB"] = "N/A"
|
75
|
-
|
76
|
-
try:
|
77
|
-
with open("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") as f1, open(
|
78
|
-
"/sys/fs/cgroup/cpu/cpu.cfs_period_us"
|
79
|
-
) as f2:
|
80
|
-
quota = int(f1.read().strip())
|
81
|
-
period = int(f2.read().strip())
|
82
|
-
limits["cpu_limit_cores"] = quota / period if quota > 0 else "N/A"
|
83
|
-
except Exception:
|
84
|
-
limits["cpu_limit_cores"] = "N/A"
|
85
|
-
|
86
|
-
return limits
|
87
|
-
|
88
|
-
|
89
|
-
@router.get("/dependencies", operation_id="getDependencies", status_code=200)
|
90
|
-
def list_installed_packages(
|
91
|
-
include: list[str] = Query(
|
92
|
-
default=None,
|
93
|
-
description="List of regex patterns to match against package names. If not provided, all installed packages will be returned.",
|
94
|
-
)
|
95
|
-
) -> dict[str, str]:
|
96
|
-
"""Return a list of installed packages and versions.
|
97
|
-
|
98
|
-
The include parameter accepts regex patterns to match against package names.
|
99
|
-
For example:
|
100
|
-
- crypticorn.* will match all packages starting with 'crypticorn'
|
101
|
-
- .*tic.* will match all packages containing 'tic' in their name
|
102
|
-
"""
|
103
|
-
packages = {
|
104
|
-
dist.metadata["Name"]: dist.version
|
105
|
-
for dist in importlib.metadata.distributions()
|
106
|
-
if include is None
|
107
|
-
or any(re.match(pattern, dist.metadata["Name"]) for pattern in include)
|
108
|
-
}
|
109
|
-
return dict(sorted(packages.items()))
|
110
|
-
|
111
|
-
|
112
|
-
@router.get("/metrics", operation_id="getMetrics")
|
113
|
-
def metrics():
|
114
|
-
"""
|
115
|
-
Get Prometheus metrics for the application. Returns plain text.
|
116
|
-
"""
|
117
|
-
return Response(generate_latest(registry), media_type=CONTENT_TYPE_LATEST)
|
@@ -1,36 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains the status router for the API.
|
3
|
-
It provides endpoints for checking the status of the API and get the server's time.
|
4
|
-
SHOULD ALLOW ACCESS TO THIS ROUTER WITHOUT AUTH.
|
5
|
-
To enable metrics, pass enable_metrics=True and the auth_handler to the router.
|
6
|
-
>>> status_router.enable_metrics = True
|
7
|
-
>>> status_router.auth_handler = auth_handler
|
8
|
-
Then include the router in the FastAPI app.
|
9
|
-
>>> app.include_router(status_router)
|
10
|
-
"""
|
11
|
-
|
12
|
-
from datetime import datetime
|
13
|
-
from typing import Literal
|
14
|
-
|
15
|
-
from fastapi import APIRouter, Request
|
16
|
-
|
17
|
-
router = APIRouter(tags=["Status"], prefix="")
|
18
|
-
|
19
|
-
|
20
|
-
@router.get("/", operation_id="ping")
|
21
|
-
async def ping(request: Request) -> str:
|
22
|
-
"""
|
23
|
-
Returns 'OK' if the API is running.
|
24
|
-
"""
|
25
|
-
return "OK"
|
26
|
-
|
27
|
-
|
28
|
-
@router.get("/time", operation_id="getTime")
|
29
|
-
async def time(type: Literal["iso", "unix"] = "iso") -> str:
|
30
|
-
"""
|
31
|
-
Returns the current time in either ISO or Unix timestamp (seconds) format.
|
32
|
-
"""
|
33
|
-
if type == "iso":
|
34
|
-
return datetime.now().isoformat()
|
35
|
-
elif type == "unix":
|
36
|
-
return str(int(datetime.now().timestamp()))
|
@@ -1,30 +0,0 @@
|
|
1
|
-
crypticorn_utils/__init__.py,sha256=PlxMppwUMmbNR9Jr1z9DNCYAYnTuWzW9IFyGTwV5slA,700
|
2
|
-
crypticorn_utils/_migration.py,sha256=YPVEDVIz9Lt3ntwrVmUtavQxsJVjtSqQeQ0A_qydiaY,445
|
3
|
-
crypticorn_utils/ansi_colors.py,sha256=-tMlUTE8NI7TPv7uj0kGRe-SI2hGaUNPKBFI_dfiZy0,1392
|
4
|
-
crypticorn_utils/auth.py,sha256=oJORAuYBD0qiAmUpbMe0JSvp_gsof3oQMNpqay8L6Ek,13018
|
5
|
-
crypticorn_utils/decorators.py,sha256=chsbF27_q3hC0egBaZLfv2vcqUcBSfQXRkLi3Ewb-20,1063
|
6
|
-
crypticorn_utils/enums.py,sha256=hr6QfOwl8AnfPDtxBDc-v7TA9hzKkPhFq0ZdREb6Hzc,5059
|
7
|
-
crypticorn_utils/errors.py,sha256=tR1yZCfI7DL0k7mKHfsRdWNMDU8hNB5v91YLShikWoY,30429
|
8
|
-
crypticorn_utils/exceptions.py,sha256=iHJOtEBrqgzZVa3ZC60TVYpEfUnhxzos_A9QefmVXT8,6633
|
9
|
-
crypticorn_utils/logging.py,sha256=510DSQr4g5UbweSvZAxjdUKtmj-LU3Dw0clARaAvsZA,4439
|
10
|
-
crypticorn_utils/metrics.py,sha256=WjnNs3RE6A_LzO7m_2hp6EQHvOw1r_nWa2Okhm1OGB0,867
|
11
|
-
crypticorn_utils/middleware.py,sha256=tr6IOti_xrZPA5EoFJpEFqLJ2VuxSnXHkCjsFLBtp6w,4130
|
12
|
-
crypticorn_utils/mixins.py,sha256=bN55jStSXk9XGskChh1OE80Oj4B6Wg8LDpxFkP0vw5I,2206
|
13
|
-
crypticorn_utils/openapi.py,sha256=D8bCpCVVzYQptHrJ7SYOgCxI3R_d0cjW9KMOBq-x0xk,279
|
14
|
-
crypticorn_utils/pagination.py,sha256=06shym28tfD-Cc9EPk2W7K2GHqUj2Kk4ShQ0BrKCDWY,11432
|
15
|
-
crypticorn_utils/utils.py,sha256=DXIOjJCdrDqoNCLAtohWYrFnEd1nvmg9NXdL07yWAgk,3137
|
16
|
-
crypticorn_utils/warnings.py,sha256=ErEB108UVZgV5ykSuFkjQgDTb9sb_T9yKq-h753Kw64,3085
|
17
|
-
crypticorn_utils/cli/__init__.py,sha256=5qIQ_WXKSoJk3tGU6n0XwmSX8a2cDRSM6dncaU1qOZ4,123
|
18
|
-
crypticorn_utils/cli/__main__.py,sha256=q_3MdBGUJPukr_xexup3TICf8kLL7Tp4JxLWB5XCESs,303
|
19
|
-
crypticorn_utils/cli/init.py,sha256=Ubu0eOwUqVweb4EzqsceV4K5VMN2qz6_Riy9h2Pjb64,4099
|
20
|
-
crypticorn_utils/cli/version.py,sha256=OVDxeL80eMgZsFgw2cDSzFfuaRToDfnYAVOQTpkoMWs,206
|
21
|
-
crypticorn_utils/cli/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
crypticorn_utils/cli/templates/auth.py,sha256=i27-Ts-Eiyv6_WRshOp7NV5OYUNbw2-kiN5Ll0k2UOA,839
|
23
|
-
crypticorn_utils/router/admin_router.py,sha256=B8s0hqOFe6CFTTsCxtwMUZSdb5EbRsFeKPixH9CXwOU,4090
|
24
|
-
crypticorn_utils/router/status_router.py,sha256=tqrTXq4ZWCxiTXinQoK-2tadWu2jUFzzzkN_Dft0P8g,1084
|
25
|
-
crypticorn_utils-0.1.0rc1.dist-info/licenses/LICENSE,sha256=HonAVvzFXkP2C1d7D3ByIKPwjGH8NcHTAQvKH7uvOHQ,1856
|
26
|
-
crypticorn_utils-0.1.0rc1.dist-info/METADATA,sha256=HflK4V5Gk-AlTWBT5HtDTQLK3MWZBiPrrbk8gQZcoyk,3999
|
27
|
-
crypticorn_utils-0.1.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
-
crypticorn_utils-0.1.0rc1.dist-info/entry_points.txt,sha256=G4yWDll7v_Kb4uemqI-_qTKXHvs10R4mD7hNuD-biv4,71
|
29
|
-
crypticorn_utils-0.1.0rc1.dist-info/top_level.txt,sha256=jLPvdxnI36RPf8TX3aZhl35OSd721xEYyFSEeQKF6Ic,17
|
30
|
-
crypticorn_utils-0.1.0rc1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{crypticorn_utils-0.1.0rc1.dist-info → crypticorn_utils-1.0.0rc1.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|