arcade-core 2.0.0__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.
- arcade_core/__init__.py +2 -0
- arcade_core/annotations.py +8 -0
- arcade_core/auth.py +177 -0
- arcade_core/catalog.py +894 -0
- arcade_core/config.py +23 -0
- arcade_core/config_model.py +146 -0
- arcade_core/errors.py +103 -0
- arcade_core/executor.py +129 -0
- arcade_core/output.py +64 -0
- arcade_core/parse.py +63 -0
- arcade_core/py.typed +0 -0
- arcade_core/schema.py +441 -0
- arcade_core/telemetry.py +130 -0
- arcade_core/toolkit.py +155 -0
- arcade_core/utils.py +99 -0
- arcade_core/version.py +1 -0
- arcade_core-2.0.0.dist-info/METADATA +77 -0
- arcade_core-2.0.0.dist-info/RECORD +19 -0
- arcade_core-2.0.0.dist-info/WHEEL +4 -0
arcade_core/toolkit.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
import importlib.util
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import types
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
10
|
+
|
|
11
|
+
from arcade_core.errors import ToolkitLoadError
|
|
12
|
+
from arcade_core.parse import get_tools_from_file
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Toolkit(BaseModel):
|
|
18
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
19
|
+
|
|
20
|
+
name: str
|
|
21
|
+
"""Name of the toolkit"""
|
|
22
|
+
|
|
23
|
+
package_name: str
|
|
24
|
+
"""Name of the package holding the toolkit"""
|
|
25
|
+
|
|
26
|
+
tools: dict[str, list[str]] = defaultdict(list)
|
|
27
|
+
"""Mapping of module names to tools"""
|
|
28
|
+
|
|
29
|
+
# Other python package metadata
|
|
30
|
+
version: str
|
|
31
|
+
description: str
|
|
32
|
+
author: list[str] = []
|
|
33
|
+
repository: str | None = None
|
|
34
|
+
homepage: str | None = None
|
|
35
|
+
|
|
36
|
+
@field_validator("name", mode="before")
|
|
37
|
+
def strip_arcade_prefix(cls, value: str) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Validator to strip the 'arcade_' prefix from the name if it exists.
|
|
40
|
+
"""
|
|
41
|
+
if value.startswith("arcade_"):
|
|
42
|
+
return value[len("arcade_") :]
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_module(cls, module: types.ModuleType) -> "Toolkit":
|
|
47
|
+
"""
|
|
48
|
+
Load a toolkit from an imported python module
|
|
49
|
+
|
|
50
|
+
>>> import arcade_math
|
|
51
|
+
>>> toolkit = Toolkit.from_module(arcade_math)
|
|
52
|
+
"""
|
|
53
|
+
return cls.from_package(module.__name__)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_package(cls, package: str) -> "Toolkit":
|
|
57
|
+
"""
|
|
58
|
+
Load a Toolkit from a Python package
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
metadata = importlib.metadata.metadata(package)
|
|
62
|
+
name = metadata["Name"]
|
|
63
|
+
package_name = package
|
|
64
|
+
version = metadata["Version"]
|
|
65
|
+
description = metadata.get("Summary", "") # type: ignore[attr-defined]
|
|
66
|
+
author = metadata.get_all("Author-email")
|
|
67
|
+
homepage = metadata.get("Home-page", None) # type: ignore[attr-defined]
|
|
68
|
+
repo = metadata.get("Repository", None) # type: ignore[attr-defined]
|
|
69
|
+
|
|
70
|
+
except importlib.metadata.PackageNotFoundError as e:
|
|
71
|
+
raise ToolkitLoadError(f"Package {package} not found.") from e
|
|
72
|
+
except KeyError as e:
|
|
73
|
+
raise ToolkitLoadError(f"Metadata key error for package {package}.") from e
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise ToolkitLoadError(f"Failed to load metadata for package {package}.") from e
|
|
76
|
+
|
|
77
|
+
# Get the package directory
|
|
78
|
+
try:
|
|
79
|
+
package_dir = Path(get_package_directory(package))
|
|
80
|
+
except (ImportError, AttributeError) as e:
|
|
81
|
+
raise ToolkitLoadError(f"Failed to locate package directory for {package}.") from e
|
|
82
|
+
|
|
83
|
+
# Get all python files in the package directory
|
|
84
|
+
try:
|
|
85
|
+
modules = [f for f in package_dir.glob("**/*.py") if f.is_file()]
|
|
86
|
+
except OSError as e:
|
|
87
|
+
raise ToolkitLoadError(
|
|
88
|
+
f"Failed to locate Python files in package directory for {package}."
|
|
89
|
+
) from e
|
|
90
|
+
|
|
91
|
+
toolkit = cls(
|
|
92
|
+
name=name,
|
|
93
|
+
package_name=package_name,
|
|
94
|
+
version=version,
|
|
95
|
+
description=description,
|
|
96
|
+
author=author if author else [],
|
|
97
|
+
homepage=homepage,
|
|
98
|
+
repository=repo,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
for module_path in modules:
|
|
102
|
+
relative_path = module_path.relative_to(package_dir)
|
|
103
|
+
import_path = ".".join(relative_path.with_suffix("").parts)
|
|
104
|
+
import_path = f"{package_name}.{import_path}"
|
|
105
|
+
toolkit.tools[import_path] = get_tools_from_file(str(module_path))
|
|
106
|
+
|
|
107
|
+
if not toolkit.tools:
|
|
108
|
+
raise ToolkitLoadError(f"No tools found in package {package}")
|
|
109
|
+
|
|
110
|
+
return toolkit
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def find_all_arcade_toolkits(cls) -> list["Toolkit"]:
|
|
114
|
+
"""
|
|
115
|
+
Find all installed packages prefixed with 'arcade_' in the current
|
|
116
|
+
Python interpreter's environment and load them as Toolkits.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List[Toolkit]: A list of Toolkit instances.
|
|
120
|
+
"""
|
|
121
|
+
import sysconfig
|
|
122
|
+
|
|
123
|
+
# Get the site-packages directory of the current interpreter
|
|
124
|
+
site_packages_dir = sysconfig.get_paths()["purelib"]
|
|
125
|
+
arcade_packages = [
|
|
126
|
+
dist.metadata["Name"]
|
|
127
|
+
for dist in importlib.metadata.distributions(path=[site_packages_dir])
|
|
128
|
+
if dist.metadata["Name"].startswith("arcade_")
|
|
129
|
+
]
|
|
130
|
+
toolkits = []
|
|
131
|
+
for package in arcade_packages:
|
|
132
|
+
try:
|
|
133
|
+
toolkits.append(cls.from_package(package))
|
|
134
|
+
except ToolkitLoadError as e:
|
|
135
|
+
logger.warning(f"Warning: {e} Skipping toolkit {package}")
|
|
136
|
+
return toolkits
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_package_directory(package_name: str) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Get the directory of a Python package
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
spec = importlib.util.find_spec(package_name)
|
|
145
|
+
if spec is None:
|
|
146
|
+
raise ImportError(f"Cannot find package named {package_name}")
|
|
147
|
+
|
|
148
|
+
if spec.origin:
|
|
149
|
+
# If the package has an origin, return the directory of the origin
|
|
150
|
+
return os.path.dirname(spec.origin)
|
|
151
|
+
elif spec.submodule_search_locations:
|
|
152
|
+
# If the package is a namespace package, return the first search location
|
|
153
|
+
return spec.submodule_search_locations[0]
|
|
154
|
+
else:
|
|
155
|
+
raise ImportError(f"Package {package_name} does not have a file path associated with it")
|
arcade_core/utils.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from types import UnionType
|
|
8
|
+
from typing import Any, Callable, Literal, TypeVar, Union, get_args, get_origin
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def first_or_none(_type: type[T], iterable: Iterable[Any]) -> T | None:
|
|
14
|
+
"""
|
|
15
|
+
Returns the first item in the iterable that is an instance of the given type, or None if no such item is found.
|
|
16
|
+
"""
|
|
17
|
+
for item in iterable:
|
|
18
|
+
if isinstance(item, _type):
|
|
19
|
+
return item
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pascal_to_snake_case(name: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Converts a PascalCase name to snake_case.
|
|
26
|
+
"""
|
|
27
|
+
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
28
|
+
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def snake_to_pascal_case(name: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Converts a snake_case name to PascalCase.
|
|
34
|
+
"""
|
|
35
|
+
if "_" in name:
|
|
36
|
+
return "".join(x.capitalize() or "_" for x in name.split("_"))
|
|
37
|
+
# check if first letter is uppercase
|
|
38
|
+
if name[0].isupper():
|
|
39
|
+
return name
|
|
40
|
+
return name.capitalize()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_string_literal(_type: type) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Returns True if the given type is a string literal, i.e. a Literal[str] or Literal[str, str, ...] etc.
|
|
46
|
+
"""
|
|
47
|
+
return get_origin(_type) is Literal and all(isinstance(arg, str) for arg in get_args(_type))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_union(_type: type) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Returns True if the given type is a union, i.e. a Union[T1, T2, ...] or T1 | T2 | ... etc.
|
|
53
|
+
"""
|
|
54
|
+
return get_origin(_type) in {Union, UnionType}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_strict_optional(_type: type) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Returns True if the given type is a strict optional type, i.e. a union with exactly two types
|
|
60
|
+
where one type is None. This covers Optional[T], Union[T, None] and T | None
|
|
61
|
+
"""
|
|
62
|
+
return is_union(_type) and len(get_args(_type)) == 2 and type(None) in get_args(_type)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def does_function_return_value(func: Callable) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Returns True if the given function returns a value, i.e. if it has a return statement with a value.
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
source: str | None = inspect.getsource(func)
|
|
71
|
+
except OSError:
|
|
72
|
+
# Workaround for parameterized unit tests that use a dynamically-generated function
|
|
73
|
+
source = getattr(func, "__source__", None)
|
|
74
|
+
|
|
75
|
+
if source is None:
|
|
76
|
+
raise ValueError("Source code not found")
|
|
77
|
+
|
|
78
|
+
tree = ast.parse(source)
|
|
79
|
+
|
|
80
|
+
class ReturnVisitor(ast.NodeVisitor):
|
|
81
|
+
def __init__(self) -> None:
|
|
82
|
+
self.returns_value = False
|
|
83
|
+
|
|
84
|
+
def visit_Return(self, node: ast.Return) -> None:
|
|
85
|
+
if node.value is not None:
|
|
86
|
+
self.returns_value = True
|
|
87
|
+
|
|
88
|
+
visitor = ReturnVisitor()
|
|
89
|
+
visitor.visit(tree)
|
|
90
|
+
return visitor.returns_value
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def coerce_empty_list_to_none(lst: list[Any] | None) -> list[Any] | None:
|
|
94
|
+
"""
|
|
95
|
+
Coerces empty lists to None, otherwise returns the list unchanged.
|
|
96
|
+
"""
|
|
97
|
+
if isinstance(lst, list) and len(lst) == 0:
|
|
98
|
+
return None
|
|
99
|
+
return lst
|
arcade_core/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.1.0"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arcade-core
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Arcade Core - Core library for Arcade platform
|
|
5
|
+
Author-email: Arcade <dev@arcade.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: loguru>=0.7.0
|
|
17
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.28.2
|
|
18
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.28.2
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi==0.49b2
|
|
20
|
+
Requires-Dist: packaging>=24.1
|
|
21
|
+
Requires-Dist: pydantic>=2.7.0
|
|
22
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: toml>=0.10.2
|
|
25
|
+
Requires-Dist: types-python-dateutil==2.9.0.20241003
|
|
26
|
+
Requires-Dist: types-pytz==2024.2.0.20241003
|
|
27
|
+
Requires-Dist: types-toml==0.10.8.20240310
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.5.1; extra == 'dev'
|
|
30
|
+
Requires-Dist: pre-commit>=3.4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23.7; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.1.2; extra == 'dev'
|
|
34
|
+
Requires-Dist: types-python-dateutil>=2.8.2; extra == 'dev'
|
|
35
|
+
Requires-Dist: types-pytz>=2024.1; extra == 'dev'
|
|
36
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Arcade Core
|
|
40
|
+
|
|
41
|
+
Core library for the Arcade platform providing foundational components and utilities.
|
|
42
|
+
|
|
43
|
+
## Overview
|
|
44
|
+
|
|
45
|
+
Arcade Core provides the essential building blocks for the Arcade platform:
|
|
46
|
+
|
|
47
|
+
- **Tool Catalog & Toolkit Management**: Core classes for managing and organizing tools
|
|
48
|
+
- **Configuration & Schema Handling**: Configuration management and validation
|
|
49
|
+
- **Authentication & Authorization**: Auth providers and security utilities
|
|
50
|
+
- **Error Handling**: Comprehensive error types and handling
|
|
51
|
+
- **Telemetry & Observability**: Monitoring and tracing capabilities
|
|
52
|
+
- **Utilities**: Common helper functions and validators
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install arcade-core
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from arcade_core import ToolCatalog, Toolkit, ArcadeConfig
|
|
64
|
+
|
|
65
|
+
# Create a tool catalog
|
|
66
|
+
catalog = ToolCatalog()
|
|
67
|
+
|
|
68
|
+
# Load a toolkit
|
|
69
|
+
toolkit = Toolkit.from_directory("path/to/toolkit")
|
|
70
|
+
|
|
71
|
+
# Configure Arcade
|
|
72
|
+
config = ArcadeConfig.from_file("config.yaml")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
arcade_core/__init__.py,sha256=1heu3AROAjpistehPzY2H-2nkj_IjQEh-vVlVOCRF1E,88
|
|
2
|
+
arcade_core/annotations.py,sha256=Nst6aejLWXlpTu7GwzWETu1gQCG1XVAUR_qcFbNvyRc,198
|
|
3
|
+
arcade_core/auth.py,sha256=vlbI_QLiFcBXL4odt56CfL4kz0sOuk_MZujaK-cxN68,5347
|
|
4
|
+
arcade_core/catalog.py,sha256=MH-o-NMvUoK_hg8Ibr8dKoF4Yu0z53D5pfUR--BhPCA,31432
|
|
5
|
+
arcade_core/config.py,sha256=e98XQAkYySGW9T_yrJg54BB8Wuq06GPVHp7xqe2d1vU,572
|
|
6
|
+
arcade_core/config_model.py,sha256=GYO37yKi7ih6EYKPpX1Kl-K1XwM2JyEJguyaJ7j9TY8,4260
|
|
7
|
+
arcade_core/errors.py,sha256=h4H1ck4TP-CDKPuRA5EVtRnplWwk9ofwFWE5h4AuMyg,2043
|
|
8
|
+
arcade_core/executor.py,sha256=tEDAM-4d-LMZJc0xAemjoL73kKCyLxTa79NK6vjWqmw,4444
|
|
9
|
+
arcade_core/output.py,sha256=0v0Y47NVQ40-vl9a47XZT7T-cZqcrVZrvO3xYJlcZPs,1852
|
|
10
|
+
arcade_core/parse.py,sha256=SURNI-B9xHCIprxTRTAR0AMT9hIJpQqHjOmrENzFBVI,1899
|
|
11
|
+
arcade_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
arcade_core/schema.py,sha256=DgVqrRDZMhndhXcb-CvLEnAtVWkseisvtAwcei5Qgmc,14358
|
|
13
|
+
arcade_core/telemetry.py,sha256=qDv8T-wO8nFi0Qh93WKaPH1b6asfoJoyyfA7ZOxPnbA,5566
|
|
14
|
+
arcade_core/toolkit.py,sha256=ufxkyRN2Qmu-TW4GHYuXrsMav3lrTusUqYf_wL-WZdw,5349
|
|
15
|
+
arcade_core/utils.py,sha256=Gg4na-85pY21e5Ab-yxoRlzTQu3FhlP5xQ9G1BhfrI8,2980
|
|
16
|
+
arcade_core/version.py,sha256=CpXi3jGlx23RvRyU7iytOMZrnspdWw4yofS8lpP1AJU,18
|
|
17
|
+
arcade_core-2.0.0.dist-info/METADATA,sha256=g093vgoWqzSNgBs0uExT7dlassVKXEPXc39-9zB9PhA,2542
|
|
18
|
+
arcade_core-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
19
|
+
arcade_core-2.0.0.dist-info/RECORD,,
|