Skip to content

Commit 34f471e

Browse files
authored
Merge pull request #44 from universal-tool-calling-protocol/dev
1.0.0
2 parents fcb5243 + ed55a82 commit 34f471e

File tree

130 files changed

+8468
-11727
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+8468
-11727
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ jobs:
2626
- name: Install dependencies
2727
run: |
2828
python -m pip install --upgrade pip
29-
pip install -e ".[dev]"
29+
pip install -e "core[dev]"
30+
pip install -e plugins/communication_protocols/cli[dev]
31+
pip install -e plugins/communication_protocols/http[dev]
32+
pip install -e plugins/communication_protocols/mcp[dev]
33+
pip install -e plugins/communication_protocols/text[dev]
3034
3135
- name: Run tests with pytest
3236
run: |
33-
pytest tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=src/utcp --cov-report=xml --cov-report=html
37+
pytest core/tests/ plugins/communication_protocols/cli/tests/ plugins/communication_protocols/http/tests/ plugins/communication_protocols/mcp/tests/ plugins/communication_protocols/text/tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=core/src/utcp --cov-report=xml --cov-report=html
3438
3539
- name: Upload coverage reports to Codecov
3640
uses: codecov/codecov-action@v3

README.md

Lines changed: 319 additions & 690 deletions
Large diffs are not rendered by default.

core/pyproject.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "utcp"
7+
version = "1.0.0"
8+
authors = [
9+
{ name = "UTCP Contributors" },
10+
]
11+
description = "Universal Tool Calling Protocol (UTCP) client library for Python"
12+
readme = "README.md"
13+
requires-python = ">=3.10"
14+
dependencies = [
15+
"pydantic>=2.0",
16+
"python-dotenv>=1.0",
17+
"tomli>=2.0",
18+
]
19+
classifiers = [
20+
"Development Status :: 4 - Beta",
21+
"Intended Audience :: Developers",
22+
"Programming Language :: Python :: 3",
23+
"Operating System :: OS Independent",
24+
]
25+
license = "MPL-2.0"
26+
27+
[project.optional-dependencies]
28+
dev = [
29+
"build",
30+
"pytest",
31+
"pytest-asyncio",
32+
"pytest-cov",
33+
"coverage",
34+
"twine",
35+
]
36+
37+
[project.urls]
38+
Homepage = "https://utcp.io"
39+
Source = "https://github.com/universal-tool-calling-protocol/python-utcp"
40+
Issues = "https://github.com/universal-tool-calling-protocol/python-utcp/issues"

core/src/utcp/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import logging
2+
import sys
3+
4+
logger = logging.getLogger("utcp")
5+
6+
if not logger.handlers: # Only add default handler if user didn't configure logging
7+
handler = logging.StreamHandler(sys.stderr)
8+
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s"))
9+
logger.addHandler(handler)
10+
logger.setLevel(logging.INFO)
File renamed without changes.

core/src/utcp/data/auth.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Authentication schemes for UTCP providers.
2+
3+
This module defines the authentication models supported by UTCP providers,
4+
including API key authentication, basic authentication, and OAuth2.
5+
"""
6+
7+
from abc import ABC
8+
from pydantic import BaseModel
9+
from utcp.interfaces.serializer import Serializer
10+
from utcp.exceptions import UtcpSerializerValidationError
11+
import traceback
12+
13+
class Auth(BaseModel, ABC):
14+
"""Authentication details for a provider.
15+
16+
Attributes:
17+
auth_type: The authentication type identifier.
18+
"""
19+
auth_type: str
20+
21+
class AuthSerializer(Serializer[Auth]):
22+
auth_serializers: dict[str, Serializer[Auth]] = {}
23+
24+
def to_dict(self, obj: Auth) -> dict:
25+
return AuthSerializer.auth_serializers[obj.auth_type].to_dict(obj)
26+
27+
def validate_dict(self, obj: dict) -> Auth:
28+
try:
29+
return AuthSerializer.auth_serializers[obj["auth_type"]].validate_dict(obj)
30+
except KeyError:
31+
raise ValueError(f"Invalid auth type: {obj['auth_type']}")
32+
except Exception as e:
33+
raise UtcpSerializerValidationError("Invalid Auth: " + traceback.format_exc()) from e
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from utcp.data.auth_implementations.api_key_auth import ApiKeyAuth, ApiKeyAuthSerializer
2+
from utcp.data.auth_implementations.basic_auth import BasicAuth, BasicAuthSerializer
3+
from utcp.data.auth_implementations.oauth2_auth import OAuth2Auth, OAuth2AuthSerializer
4+
5+
__all__ = [
6+
"ApiKeyAuth",
7+
"BasicAuth",
8+
"OAuth2Auth",
9+
"ApiKeyAuthSerializer",
10+
"BasicAuthSerializer",
11+
"OAuth2AuthSerializer"
12+
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from utcp.data.auth import Auth
2+
from utcp.interfaces.serializer import Serializer
3+
from pydantic import Field, ValidationError
4+
from typing import Literal
5+
from utcp.exceptions import UtcpSerializerValidationError
6+
7+
class ApiKeyAuth(Auth):
8+
"""Authentication using an API key.
9+
10+
The key can be provided directly or sourced from an environment variable.
11+
Supports placement in headers, query parameters, or cookies.
12+
13+
Attributes:
14+
auth_type: The authentication type identifier, always "api_key".
15+
api_key: The API key for authentication. Values starting with '$' or formatted as '${}' are
16+
treated as an injected variable from environment or configuration.
17+
var_name: The name of the header, query parameter, or cookie that
18+
contains the API key.
19+
location: Where to include the API key (header, query parameter, or cookie).
20+
"""
21+
22+
auth_type: Literal["api_key"] = "api_key"
23+
api_key: str = Field(..., description="The API key for authentication. Values starting with '$' or formatted as '${}' are treated as an injected variable from environment or configuration. This is the recommended way to provide API keys.")
24+
var_name: str = Field(
25+
"X-Api-Key", description="The name of the header, query parameter, cookie or other container for the API key."
26+
)
27+
location: Literal["header", "query", "cookie"] = Field(
28+
"header", description="Where to include the API key (header, query parameter, or cookie)."
29+
)
30+
31+
32+
class ApiKeyAuthSerializer(Serializer[ApiKeyAuth]):
33+
def to_dict(self, obj: ApiKeyAuth) -> dict:
34+
return obj.model_dump()
35+
36+
def validate_dict(self, obj: dict) -> ApiKeyAuth:
37+
try:
38+
return ApiKeyAuth.model_validate(obj)
39+
except ValidationError as e:
40+
raise UtcpSerializerValidationError(f"Invalid ApiKeyAuth: {e}") from e
41+
except Exception as e:
42+
raise UtcpSerializerValidationError("An unexpected error occurred during ApiKeyAuth validation.") from e
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from utcp.data.auth import Auth
2+
from utcp.interfaces.serializer import Serializer
3+
from pydantic import Field, ValidationError
4+
from typing import Literal
5+
from utcp.exceptions import UtcpSerializerValidationError
6+
7+
class BasicAuth(Auth):
8+
"""Authentication using HTTP Basic Authentication.
9+
10+
Uses the standard HTTP Basic Authentication scheme with username and password
11+
encoded in the Authorization header.
12+
13+
Attributes:
14+
auth_type: The authentication type identifier, always "basic".
15+
username: The username for basic authentication. Recommended to use injected variables.
16+
password: The password for basic authentication. Recommended to use injected variables.
17+
"""
18+
19+
auth_type: Literal["basic"] = "basic"
20+
username: str = Field(..., description="The username for basic authentication.")
21+
password: str = Field(..., description="The password for basic authentication.")
22+
23+
24+
class BasicAuthSerializer(Serializer[BasicAuth]):
25+
def to_dict(self, obj: BasicAuth) -> dict:
26+
return obj.model_dump()
27+
28+
def validate_dict(self, obj: dict) -> BasicAuth:
29+
try:
30+
return BasicAuth.model_validate(obj)
31+
except ValidationError as e:
32+
raise UtcpSerializerValidationError(f"Invalid BasicAuth: {e}") from e
33+
except Exception as e:
34+
raise UtcpSerializerValidationError("An unexpected error occurred during BasicAuth validation.") from e
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from utcp.data.auth import Auth
2+
from utcp.interfaces.serializer import Serializer
3+
from utcp.exceptions import UtcpSerializerValidationError
4+
from pydantic import Field, ValidationError
5+
from typing import Literal, Optional
6+
7+
8+
class OAuth2Auth(Auth):
9+
"""Authentication using OAuth2 client credentials flow.
10+
11+
Implements the OAuth2 client credentials grant type for machine-to-machine
12+
authentication. The client automatically handles token acquisition and refresh.
13+
14+
Attributes:
15+
auth_type: The authentication type identifier, always "oauth2".
16+
token_url: The URL endpoint to fetch the OAuth2 access token from. Recommended to use injected variables.
17+
client_id: The OAuth2 client identifier. Recommended to use injected variables.
18+
client_secret: The OAuth2 client secret. Recommended to use injected variables.
19+
scope: Optional scope parameter to limit the access token's permissions.
20+
"""
21+
22+
auth_type: Literal["oauth2"] = "oauth2"
23+
token_url: str = Field(..., description="The URL to fetch the OAuth2 token from.")
24+
client_id: str = Field(..., description="The OAuth2 client ID.")
25+
client_secret: str = Field(..., description="The OAuth2 client secret.")
26+
scope: Optional[str] = Field(None, description="The OAuth2 scope.")
27+
28+
29+
class OAuth2AuthSerializer(Serializer[OAuth2Auth]):
30+
def to_dict(self, obj: OAuth2Auth) -> dict:
31+
return obj.model_dump()
32+
33+
def validate_dict(self, obj: dict) -> OAuth2Auth:
34+
try:
35+
return OAuth2Auth.model_validate(obj)
36+
except ValidationError as e:
37+
raise UtcpSerializerValidationError(f"Invalid OAuth2Auth: {e}") from e
38+
except Exception as e:
39+
raise UtcpSerializerValidationError("An unexpected error occurred during OAuth2Auth validation.") from e

0 commit comments

Comments
 (0)