pyrestkit 0.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.
- pyrestkit/__init__.py +35 -0
- pyrestkit/ai/__init__.py +17 -0
- pyrestkit/ai/analyzer.py +137 -0
- pyrestkit/ai/client.py +101 -0
- pyrestkit/ai/config/__init__.py +5 -0
- pyrestkit/ai/config/ai_config.py +200 -0
- pyrestkit/ai/exceptions.py +22 -0
- pyrestkit/ai/generators/__init__.py +0 -0
- pyrestkit/ai/models.py +44 -0
- pyrestkit/ai/parsers/__init__.py +0 -0
- pyrestkit/ai/prompts/failure_analysis.md +21 -0
- pyrestkit/ai/provider.py +58 -0
- pyrestkit/ai/providers/__init__.py +21 -0
- pyrestkit/ai/providers/anthropic.py +85 -0
- pyrestkit/ai/providers/azure_openai.py +84 -0
- pyrestkit/ai/providers/base.py +39 -0
- pyrestkit/ai/providers/bedrock.py +70 -0
- pyrestkit/ai/providers/cohere.py +82 -0
- pyrestkit/ai/providers/gemini.py +113 -0
- pyrestkit/ai/providers/groq.py +81 -0
- pyrestkit/ai/providers/mistral.py +88 -0
- pyrestkit/ai/providers/ollama.py +82 -0
- pyrestkit/ai/providers/openai.py +124 -0
- pyrestkit/ai/utils/__init__.py +0 -0
- pyrestkit/ai/utils/prompt_loader.py +52 -0
- pyrestkit/assertions/__init__.py +7 -0
- pyrestkit/assertions/assertion_exception.py +4 -0
- pyrestkit/assertions/response_assertions.py +181 -0
- pyrestkit/auth/__init__.py +11 -0
- pyrestkit/auth/auth_strategy.py +14 -0
- pyrestkit/auth/authentication_manager.py +35 -0
- pyrestkit/auth/strategies/__init__.py +5 -0
- pyrestkit/auth/strategies/api_key_auth.py +18 -0
- pyrestkit/auth/strategies/basic_auth.py +24 -0
- pyrestkit/auth/strategies/bearer_auth.py +17 -0
- pyrestkit/auth/token_cache.py +44 -0
- pyrestkit/auth/token_manager.py +32 -0
- pyrestkit/auth/token_provider.py +12 -0
- pyrestkit/auth/token_response.py +13 -0
- pyrestkit/builder/__init__.py +5 -0
- pyrestkit/builder/fluent_request_builder.py +167 -0
- pyrestkit/clients/__init__.py +7 -0
- pyrestkit/clients/base_client.py +68 -0
- pyrestkit/clients/user_client.py +66 -0
- pyrestkit/config/__init__.py +5 -0
- pyrestkit/config/config.py +97 -0
- pyrestkit/constants/__init__.py +0 -0
- pyrestkit/constants/content_types.py +0 -0
- pyrestkit/constants/headers.py +0 -0
- pyrestkit/constants/status_codes.py +0 -0
- pyrestkit/core/__init__.py +13 -0
- pyrestkit/core/api_client.py +129 -0
- pyrestkit/core/logger.py +41 -0
- pyrestkit/core/request_builder.py +45 -0
- pyrestkit/core/request_executor.py +64 -0
- pyrestkit/core/request_logger.py +0 -0
- pyrestkit/core/response_logger.py +0 -0
- pyrestkit/core/session_manager.py +19 -0
- pyrestkit/database/__init__.py +0 -0
- pyrestkit/endpoints/__init__.py +5 -0
- pyrestkit/endpoints/base_endpoints.py +32 -0
- pyrestkit/endpoints/order_endpoints.py +9 -0
- pyrestkit/endpoints/payment_endpoints.py +5 -0
- pyrestkit/endpoints/user_endpoints.py +48 -0
- pyrestkit/exceptions/__init__.py +21 -0
- pyrestkit/exceptions/api_exception.py +8 -0
- pyrestkit/exceptions/authentication_exception.py +10 -0
- pyrestkit/exceptions/configuration_exception.py +10 -0
- pyrestkit/exceptions/exception_mapper.py +32 -0
- pyrestkit/exceptions/network_exception.py +10 -0
- pyrestkit/exceptions/response_exception.py +10 -0
- pyrestkit/exceptions/serialization_exception.py +10 -0
- pyrestkit/exceptions/validation_exception.py +10 -0
- pyrestkit/factories/__init__.py +5 -0
- pyrestkit/factories/base_factory.py +25 -0
- pyrestkit/factories/user_factory.py +37 -0
- pyrestkit/hooks/__init__.py +5 -0
- pyrestkit/hooks/hook.py +27 -0
- pyrestkit/hooks/hook_manager.py +39 -0
- pyrestkit/hooks/request_hook.py +18 -0
- pyrestkit/hooks/response_hook.py +17 -0
- pyrestkit/hooks/timing_hook.py +32 -0
- pyrestkit/models/__init__.py +8 -0
- pyrestkit/models/base_response.py +11 -0
- pyrestkit/models/request/__init__.py +7 -0
- pyrestkit/models/request/create_user_request.py +11 -0
- pyrestkit/models/request/update_user_request.py +10 -0
- pyrestkit/models/response/__init__.py +5 -0
- pyrestkit/models/response/create_user_response.py +26 -0
- pyrestkit/models/response/get_user_response.py +28 -0
- pyrestkit/models/response/user_response.py +12 -0
- pyrestkit/pipeline/__init__.py +5 -0
- pyrestkit/pipeline/middleware.py +18 -0
- pyrestkit/pipeline/middleware_chain.py +11 -0
- pyrestkit/pipeline/pipeline.py +27 -0
- pyrestkit/pipeline/request_context.py +26 -0
- pyrestkit/response/__init__.py +8 -0
- pyrestkit/response/framework_response.py +271 -0
- pyrestkit/response/response_body.py +124 -0
- pyrestkit/retry/__init__.py +5 -0
- pyrestkit/retry/backoff.py +32 -0
- pyrestkit/retry/retry_handler.py +52 -0
- pyrestkit/retry/retry_policy.py +33 -0
- pyrestkit/serializers/__init__.py +0 -0
- pyrestkit/serializers/response_mapper.py +25 -0
- pyrestkit/types/__init__.py +0 -0
- pyrestkit/types/model_protocol.py +17 -0
- pyrestkit/utils/__init__.py +0 -0
- pyrestkit/validators/__init__.py +7 -0
- pyrestkit/validators/response_validator.py +57 -0
- pyrestkit/validators/schema_validator.py +33 -0
- pyrestkit-0.0.0.dist-info/METADATA +741 -0
- pyrestkit-0.0.0.dist-info/RECORD +115 -0
- pyrestkit-0.0.0.dist-info/WHEEL +5 -0
- pyrestkit-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pyrestkit.core.api_client import APIClient
|
|
6
|
+
from pyrestkit.response.framework_response import FrameworkResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FluentRequestBuilder:
|
|
10
|
+
"""
|
|
11
|
+
Fluent API for building and sending HTTP requests.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
|
|
15
|
+
response = (
|
|
16
|
+
api.request()
|
|
17
|
+
.get("/users")
|
|
18
|
+
.query(page=2)
|
|
19
|
+
.header("Authorization", token)
|
|
20
|
+
.send()
|
|
21
|
+
)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
client: APIClient,
|
|
27
|
+
) -> None:
|
|
28
|
+
self._client = client
|
|
29
|
+
|
|
30
|
+
self._method = ""
|
|
31
|
+
self._endpoint = ""
|
|
32
|
+
|
|
33
|
+
self._kwargs: dict[str, Any] = {}
|
|
34
|
+
|
|
35
|
+
def get(
|
|
36
|
+
self,
|
|
37
|
+
endpoint: str,
|
|
38
|
+
) -> FluentRequestBuilder:
|
|
39
|
+
self._method = "GET"
|
|
40
|
+
self._endpoint = endpoint
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def post(
|
|
44
|
+
self,
|
|
45
|
+
endpoint: str,
|
|
46
|
+
) -> FluentRequestBuilder:
|
|
47
|
+
self._method = "POST"
|
|
48
|
+
self._endpoint = endpoint
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def put(
|
|
52
|
+
self,
|
|
53
|
+
endpoint: str,
|
|
54
|
+
) -> FluentRequestBuilder:
|
|
55
|
+
self._method = "PUT"
|
|
56
|
+
self._endpoint = endpoint
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def patch(
|
|
60
|
+
self,
|
|
61
|
+
endpoint: str,
|
|
62
|
+
) -> FluentRequestBuilder:
|
|
63
|
+
self._method = "PATCH"
|
|
64
|
+
self._endpoint = endpoint
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def delete(
|
|
68
|
+
self,
|
|
69
|
+
endpoint: str,
|
|
70
|
+
) -> FluentRequestBuilder:
|
|
71
|
+
self._method = "DELETE"
|
|
72
|
+
self._endpoint = endpoint
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def query(
|
|
76
|
+
self,
|
|
77
|
+
**params: Any,
|
|
78
|
+
) -> FluentRequestBuilder:
|
|
79
|
+
self._kwargs["params"] = params
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def header(
|
|
83
|
+
self,
|
|
84
|
+
name: str,
|
|
85
|
+
value: str,
|
|
86
|
+
) -> FluentRequestBuilder:
|
|
87
|
+
headers = self._kwargs.setdefault(
|
|
88
|
+
"headers",
|
|
89
|
+
{},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
headers[name] = value
|
|
93
|
+
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def headers(
|
|
97
|
+
self,
|
|
98
|
+
headers: dict[str, str],
|
|
99
|
+
) -> FluentRequestBuilder:
|
|
100
|
+
current = self._kwargs.setdefault(
|
|
101
|
+
"headers",
|
|
102
|
+
{},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
current.update(headers)
|
|
106
|
+
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def json(
|
|
110
|
+
self,
|
|
111
|
+
body: Any,
|
|
112
|
+
) -> FluentRequestBuilder:
|
|
113
|
+
self._kwargs["json"] = body
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def data(
|
|
117
|
+
self,
|
|
118
|
+
body: Any,
|
|
119
|
+
) -> FluentRequestBuilder:
|
|
120
|
+
self._kwargs["data"] = body
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def timeout(
|
|
124
|
+
self,
|
|
125
|
+
seconds: int,
|
|
126
|
+
) -> FluentRequestBuilder:
|
|
127
|
+
self._kwargs["timeout"] = seconds
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def send(
|
|
131
|
+
self,
|
|
132
|
+
) -> FrameworkResponse:
|
|
133
|
+
method = self._method.upper()
|
|
134
|
+
|
|
135
|
+
if method == "GET":
|
|
136
|
+
return self._client.get(
|
|
137
|
+
self._endpoint,
|
|
138
|
+
**self._kwargs,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if method == "POST":
|
|
142
|
+
return self._client.post(
|
|
143
|
+
self._endpoint,
|
|
144
|
+
**self._kwargs,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if method == "PUT":
|
|
148
|
+
return self._client.put(
|
|
149
|
+
self._endpoint,
|
|
150
|
+
**self._kwargs,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if method == "PATCH":
|
|
154
|
+
return self._client.patch(
|
|
155
|
+
self._endpoint,
|
|
156
|
+
**self._kwargs,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if method == "DELETE":
|
|
160
|
+
return self._client.delete(
|
|
161
|
+
self._endpoint,
|
|
162
|
+
**self._kwargs,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"HTTP method not selected.",
|
|
167
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pyrestkit.core.api_client import APIClient
|
|
6
|
+
from pyrestkit.response.framework_response import FrameworkResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseClient:
|
|
10
|
+
"""
|
|
11
|
+
Base class for all endpoint clients.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
api_client: APIClient,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._api_client = api_client
|
|
19
|
+
|
|
20
|
+
def get(
|
|
21
|
+
self,
|
|
22
|
+
endpoint: str,
|
|
23
|
+
**kwargs: Any,
|
|
24
|
+
) -> FrameworkResponse:
|
|
25
|
+
return self._api_client.get(
|
|
26
|
+
endpoint,
|
|
27
|
+
**kwargs,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def post(
|
|
31
|
+
self,
|
|
32
|
+
endpoint: str,
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
) -> FrameworkResponse:
|
|
35
|
+
return self._api_client.post(
|
|
36
|
+
endpoint,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def put(
|
|
41
|
+
self,
|
|
42
|
+
endpoint: str,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> FrameworkResponse:
|
|
45
|
+
return self._api_client.put(
|
|
46
|
+
endpoint,
|
|
47
|
+
**kwargs,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def patch(
|
|
51
|
+
self,
|
|
52
|
+
endpoint: str,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> FrameworkResponse:
|
|
55
|
+
return self._api_client.patch(
|
|
56
|
+
endpoint,
|
|
57
|
+
**kwargs,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def delete(
|
|
61
|
+
self,
|
|
62
|
+
endpoint: str,
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> FrameworkResponse:
|
|
65
|
+
return self._api_client.delete(
|
|
66
|
+
endpoint,
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
|
|
5
|
+
from pyrestkit.clients.base_client import BaseClient
|
|
6
|
+
from pyrestkit.core.api_client import APIClient
|
|
7
|
+
from pyrestkit.endpoints.user_endpoints import UserEndpoints
|
|
8
|
+
from pyrestkit.models.request.create_user_request import CreateUserRequest
|
|
9
|
+
from pyrestkit.models.request.update_user_request import UpdateUserRequest
|
|
10
|
+
from pyrestkit.response.framework_response import FrameworkResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserClient(BaseClient):
|
|
14
|
+
"""
|
|
15
|
+
Business client responsible for User APIs.
|
|
16
|
+
|
|
17
|
+
This client delegates HTTP operations to BaseClient.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
api_client: APIClient,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(api_client)
|
|
25
|
+
|
|
26
|
+
def list_users(
|
|
27
|
+
self,
|
|
28
|
+
) -> FrameworkResponse:
|
|
29
|
+
return self.get(
|
|
30
|
+
UserEndpoints.list_users(),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def get_user(
|
|
34
|
+
self,
|
|
35
|
+
user_id: int,
|
|
36
|
+
) -> FrameworkResponse:
|
|
37
|
+
return self.get(
|
|
38
|
+
UserEndpoints.get_user(user_id),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def create_user(
|
|
42
|
+
self,
|
|
43
|
+
request: CreateUserRequest,
|
|
44
|
+
) -> FrameworkResponse:
|
|
45
|
+
return self.post(
|
|
46
|
+
UserEndpoints.create_user(),
|
|
47
|
+
json=asdict(request),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def update_user(
|
|
51
|
+
self,
|
|
52
|
+
user_id: int,
|
|
53
|
+
request: UpdateUserRequest,
|
|
54
|
+
) -> FrameworkResponse:
|
|
55
|
+
return self.put(
|
|
56
|
+
UserEndpoints.update_user(user_id),
|
|
57
|
+
json=asdict(request),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def delete_user(
|
|
61
|
+
self,
|
|
62
|
+
user_id: int,
|
|
63
|
+
) -> FrameworkResponse:
|
|
64
|
+
return self.delete(
|
|
65
|
+
UserEndpoints.delete_user(user_id),
|
|
66
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, cast
|
|
6
|
+
|
|
7
|
+
from pyrestkit.exceptions.configuration_exception import ConfigurationException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigManager:
|
|
11
|
+
"""
|
|
12
|
+
Loads environment configuration from JSON files.
|
|
13
|
+
|
|
14
|
+
Why Path(__file__).parent?
|
|
15
|
+
|
|
16
|
+
Path(__file__).parent locates the directory containing
|
|
17
|
+
config.py and appends the selected environment JSON
|
|
18
|
+
file. This keeps the implementation portable across
|
|
19
|
+
operating systems.
|
|
20
|
+
|
|
21
|
+
Why use @property?
|
|
22
|
+
|
|
23
|
+
It allows callers to access configuration values like
|
|
24
|
+
attributes while keeping the flexibility to validate or
|
|
25
|
+
compute values internally in the future.
|
|
26
|
+
|
|
27
|
+
Why validate the file?
|
|
28
|
+
|
|
29
|
+
If a user runs:
|
|
30
|
+
|
|
31
|
+
pytest --env=production
|
|
32
|
+
|
|
33
|
+
and production.json does not exist, the framework fails
|
|
34
|
+
immediately with a clear error message.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
environment: str = "dev",
|
|
40
|
+
) -> None:
|
|
41
|
+
self.environment = environment.lower()
|
|
42
|
+
|
|
43
|
+
config_file = Path(__file__).parent / f"{self.environment}.json"
|
|
44
|
+
|
|
45
|
+
if not config_file.exists():
|
|
46
|
+
raise ConfigurationException(
|
|
47
|
+
f"Configuration file '{self.environment}' not found.",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
with config_file.open(
|
|
51
|
+
encoding="utf-8",
|
|
52
|
+
) as file:
|
|
53
|
+
self.config: dict[str, Any] = json.load(file)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def base_url(self) -> str:
|
|
57
|
+
return cast(
|
|
58
|
+
str,
|
|
59
|
+
self.config["base_url"],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def timeout(self) -> int:
|
|
64
|
+
return cast(
|
|
65
|
+
int,
|
|
66
|
+
self.config.get(
|
|
67
|
+
"timeout",
|
|
68
|
+
30,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def headers(self) -> dict[str, str]:
|
|
74
|
+
return cast(
|
|
75
|
+
dict[str, str],
|
|
76
|
+
self.config.get(
|
|
77
|
+
"headers",
|
|
78
|
+
{},
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def auto_raise_exceptions(self) -> bool:
|
|
84
|
+
return cast(
|
|
85
|
+
bool,
|
|
86
|
+
self.config.get(
|
|
87
|
+
"auto_raise_exceptions",
|
|
88
|
+
True,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def environment_name(self) -> str:
|
|
94
|
+
return cast(
|
|
95
|
+
str,
|
|
96
|
+
self.config["environment"],
|
|
97
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .api_client import APIClient
|
|
2
|
+
from .logger import FrameworkLogger
|
|
3
|
+
from .request_builder import RequestBuilder
|
|
4
|
+
from .request_executor import RequestExecutor
|
|
5
|
+
from .session_manager import SessionManager
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"APIClient",
|
|
9
|
+
"FrameworkLogger",
|
|
10
|
+
"RequestBuilder",
|
|
11
|
+
"RequestExecutor",
|
|
12
|
+
"SessionManager",
|
|
13
|
+
]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from pyrestkit.auth.auth_strategy import AuthenticationStrategy
|
|
6
|
+
from pyrestkit.config.config import ConfigManager
|
|
7
|
+
from pyrestkit.core.request_builder import RequestBuilder
|
|
8
|
+
from pyrestkit.core.request_executor import RequestExecutor
|
|
9
|
+
from pyrestkit.core.session_manager import SessionManager
|
|
10
|
+
from pyrestkit.hooks.hook_manager import HookManager
|
|
11
|
+
from pyrestkit.response.framework_response import FrameworkResponse
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pyrestkit.builder.fluent_request_builder import FluentRequestBuilder
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class APIClient:
|
|
18
|
+
"""
|
|
19
|
+
Generic HTTP client for the automation framework.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
config: ConfigManager,
|
|
25
|
+
session_manager: SessionManager,
|
|
26
|
+
auth_strategy: AuthenticationStrategy | None = None,
|
|
27
|
+
hook_manager: HookManager | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._builder = RequestBuilder(
|
|
30
|
+
config=config,
|
|
31
|
+
auth_strategy=auth_strategy,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self._executor = RequestExecutor(
|
|
35
|
+
config=config,
|
|
36
|
+
session_manager=session_manager,
|
|
37
|
+
hook_manager=hook_manager,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def request(self) -> FluentRequestBuilder:
|
|
41
|
+
"""
|
|
42
|
+
Creates a fluent request builder.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
|
|
46
|
+
response = (
|
|
47
|
+
api.request()
|
|
48
|
+
.get("/users")
|
|
49
|
+
.query(page=2)
|
|
50
|
+
.send()
|
|
51
|
+
)
|
|
52
|
+
"""
|
|
53
|
+
from pyrestkit.builder.fluent_request_builder import (
|
|
54
|
+
FluentRequestBuilder,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return FluentRequestBuilder(self)
|
|
58
|
+
|
|
59
|
+
def _send_request(
|
|
60
|
+
self,
|
|
61
|
+
method: str,
|
|
62
|
+
endpoint: str,
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> FrameworkResponse:
|
|
65
|
+
url, kwargs = self._builder.build(
|
|
66
|
+
endpoint,
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return self._executor.execute(
|
|
71
|
+
method=method,
|
|
72
|
+
url=url,
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def get(
|
|
77
|
+
self,
|
|
78
|
+
endpoint: str,
|
|
79
|
+
**kwargs: Any,
|
|
80
|
+
) -> FrameworkResponse:
|
|
81
|
+
return self._send_request(
|
|
82
|
+
"GET",
|
|
83
|
+
endpoint,
|
|
84
|
+
**kwargs,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def post(
|
|
88
|
+
self,
|
|
89
|
+
endpoint: str,
|
|
90
|
+
**kwargs: Any,
|
|
91
|
+
) -> FrameworkResponse:
|
|
92
|
+
return self._send_request(
|
|
93
|
+
"POST",
|
|
94
|
+
endpoint,
|
|
95
|
+
**kwargs,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def put(
|
|
99
|
+
self,
|
|
100
|
+
endpoint: str,
|
|
101
|
+
**kwargs: Any,
|
|
102
|
+
) -> FrameworkResponse:
|
|
103
|
+
return self._send_request(
|
|
104
|
+
"PUT",
|
|
105
|
+
endpoint,
|
|
106
|
+
**kwargs,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def patch(
|
|
110
|
+
self,
|
|
111
|
+
endpoint: str,
|
|
112
|
+
**kwargs: Any,
|
|
113
|
+
) -> FrameworkResponse:
|
|
114
|
+
return self._send_request(
|
|
115
|
+
"PATCH",
|
|
116
|
+
endpoint,
|
|
117
|
+
**kwargs,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def delete(
|
|
121
|
+
self,
|
|
122
|
+
endpoint: str,
|
|
123
|
+
**kwargs: Any,
|
|
124
|
+
) -> FrameworkResponse:
|
|
125
|
+
return self._send_request(
|
|
126
|
+
"DELETE",
|
|
127
|
+
endpoint,
|
|
128
|
+
**kwargs,
|
|
129
|
+
)
|
pyrestkit/core/logger.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FrameworkLogger:
|
|
6
|
+
"""
|
|
7
|
+
Singleton logger for the automation framework.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
_logger = None
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def get_logger(cls) -> logging.Logger:
|
|
14
|
+
if cls._logger:
|
|
15
|
+
return cls._logger
|
|
16
|
+
|
|
17
|
+
log_directory = Path("logs")
|
|
18
|
+
log_directory.mkdir(exist_ok=True)
|
|
19
|
+
|
|
20
|
+
log_file = log_directory / "framework.log"
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("api-framework")
|
|
23
|
+
logger.setLevel(logging.INFO)
|
|
24
|
+
|
|
25
|
+
if logger.handlers:
|
|
26
|
+
cls._logger = logger
|
|
27
|
+
return logger
|
|
28
|
+
|
|
29
|
+
formatter = logging.Formatter("%(asctime)s | %(levelname)-8s | %(message)s")
|
|
30
|
+
|
|
31
|
+
file_handler = logging.FileHandler(log_file)
|
|
32
|
+
file_handler.setFormatter(formatter)
|
|
33
|
+
|
|
34
|
+
console_handler = logging.StreamHandler()
|
|
35
|
+
console_handler.setFormatter(formatter)
|
|
36
|
+
|
|
37
|
+
logger.addHandler(file_handler)
|
|
38
|
+
logger.addHandler(console_handler)
|
|
39
|
+
|
|
40
|
+
cls._logger = logger
|
|
41
|
+
return logger
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pyrestkit.auth.auth_strategy import AuthenticationStrategy
|
|
6
|
+
from pyrestkit.config.config import ConfigManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RequestBuilder:
|
|
10
|
+
"""
|
|
11
|
+
Builds request arguments for the API client.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
config: ConfigManager,
|
|
17
|
+
auth_strategy: AuthenticationStrategy | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
self._config = config
|
|
20
|
+
self._auth_strategy = auth_strategy
|
|
21
|
+
|
|
22
|
+
def build(
|
|
23
|
+
self,
|
|
24
|
+
endpoint: str,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> tuple[str, dict[str, Any]]:
|
|
27
|
+
url = f"{self._config.base_url}{endpoint}"
|
|
28
|
+
|
|
29
|
+
headers = self._config.headers.copy()
|
|
30
|
+
|
|
31
|
+
if self._auth_strategy is not None:
|
|
32
|
+
headers.update(self._auth_strategy.get_headers())
|
|
33
|
+
|
|
34
|
+
request_headers = kwargs.pop("headers", {})
|
|
35
|
+
headers.update(request_headers)
|
|
36
|
+
|
|
37
|
+
timeout = kwargs.pop(
|
|
38
|
+
"timeout",
|
|
39
|
+
self._config.timeout,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
kwargs["headers"] = headers
|
|
43
|
+
kwargs["timeout"] = timeout
|
|
44
|
+
|
|
45
|
+
return url, kwargs
|