unique_toolkit 0.8.43__tar.gz → 0.8.44__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.
Files changed (140) hide show
  1. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/CHANGELOG.md +3 -0
  2. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/PKG-INFO +6 -3
  3. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/pyproject.toml +2 -1
  4. unique_toolkit-0.8.44/unique_toolkit/_common/endpoint_builder.py +221 -0
  5. unique_toolkit-0.8.44/unique_toolkit/_common/endpoint_requestor.py +186 -0
  6. unique_toolkit-0.8.43/unique_toolkit/_common/endpoint_builder.py +0 -145
  7. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/LICENSE +0 -0
  8. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/README.md +0 -0
  9. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/__init__.py +0 -0
  10. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/_base_service.py +0 -0
  11. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/_time_utils.py +0 -0
  12. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/base_model_type_attribute.py +0 -0
  13. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/chunk_relevancy_sorter/config.py +0 -0
  14. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/chunk_relevancy_sorter/exception.py +0 -0
  15. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +0 -0
  16. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/chunk_relevancy_sorter/service.py +0 -0
  17. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +0 -0
  18. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/default_language_model.py +0 -0
  19. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/exception.py +0 -0
  20. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/feature_flags/schema.py +0 -0
  21. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/pydantic_helpers.py +0 -0
  22. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/token/image_token_counting.py +0 -0
  23. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/token/token_counting.py +0 -0
  24. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/utils/structured_output/schema.py +0 -0
  25. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/validate_required_values.py +0 -0
  26. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/_common/validators.py +0 -0
  27. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/__init__.py +0 -0
  28. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/dev_util.py +0 -0
  29. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/init_logging.py +0 -0
  30. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/init_sdk.py +0 -0
  31. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/performance/async_tasks.py +0 -0
  32. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/performance/async_wrapper.py +0 -0
  33. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/schemas.py +0 -0
  34. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/unique_settings.py +0 -0
  35. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/app/verification.py +0 -0
  36. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/__init__.py +0 -0
  37. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/constants.py +0 -0
  38. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/functions.py +0 -0
  39. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/schemas.py +0 -0
  40. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/service.py +0 -0
  41. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/state.py +0 -0
  42. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/chat/utils.py +0 -0
  43. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/__init__.py +0 -0
  44. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/constants.py +0 -0
  45. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/functions.py +0 -0
  46. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/schemas.py +0 -0
  47. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/service.py +0 -0
  48. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/content/utils.py +0 -0
  49. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/debug_info_manager/debug_info_manager.py +0 -0
  50. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/__init__.py +0 -0
  51. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/constants.py +0 -0
  52. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/functions.py +0 -0
  53. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/schemas.py +0 -0
  54. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/service.py +0 -0
  55. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/embedding/utils.py +0 -0
  56. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/config.py +0 -0
  57. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/context_relevancy/prompts.py +0 -0
  58. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/context_relevancy/schema.py +0 -0
  59. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/context_relevancy/service.py +0 -0
  60. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/evaluation_manager.py +0 -0
  61. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/exception.py +0 -0
  62. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/hallucination/constants.py +0 -0
  63. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/hallucination/hallucination_evaluation.py +0 -0
  64. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/hallucination/prompts.py +0 -0
  65. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/hallucination/service.py +0 -0
  66. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/hallucination/utils.py +0 -0
  67. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/output_parser.py +0 -0
  68. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/schemas.py +0 -0
  69. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/tests/test_context_relevancy_service.py +0 -0
  70. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evals/tests/test_output_parser.py +0 -0
  71. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/__init__.py +0 -0
  72. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/config.py +0 -0
  73. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/constants.py +0 -0
  74. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/context_relevancy/constants.py +0 -0
  75. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/context_relevancy/prompts.py +0 -0
  76. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/context_relevancy/service.py +0 -0
  77. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/context_relevancy/utils.py +0 -0
  78. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/exception.py +0 -0
  79. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/hallucination/constants.py +0 -0
  80. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/hallucination/prompts.py +0 -0
  81. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/hallucination/service.py +0 -0
  82. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/hallucination/utils.py +0 -0
  83. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/output_parser.py +0 -0
  84. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/evaluators/schemas.py +0 -0
  85. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/__init__.py +0 -0
  86. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/langchain/client.py +0 -0
  87. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/langchain/history.py +0 -0
  88. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/openai/__init__.py +0 -0
  89. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/openai/client.py +0 -0
  90. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
  91. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/framework_utilities/utils.py +0 -0
  92. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/history_manager/history_construction_with_contents.py +0 -0
  93. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/history_manager/history_manager.py +0 -0
  94. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/history_manager/loop_token_reducer.py +0 -0
  95. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/history_manager/utils.py +0 -0
  96. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/__init__.py +0 -0
  97. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/builder.py +0 -0
  98. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/constants.py +0 -0
  99. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/functions.py +0 -0
  100. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/infos.py +0 -0
  101. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/prompt.py +0 -0
  102. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/reference.py +0 -0
  103. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/schemas.py +0 -0
  104. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/service.py +0 -0
  105. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/language_model/utils.py +0 -0
  106. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/postprocessor/postprocessor_manager.py +0 -0
  107. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/protocols/support.py +0 -0
  108. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/reference_manager/reference_manager.py +0 -0
  109. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/__init__.py +0 -0
  110. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/constants.py +0 -0
  111. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/functions.py +0 -0
  112. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/persistent_short_term_memory_manager.py +0 -0
  113. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/schemas.py +0 -0
  114. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/short_term_memory/service.py +0 -0
  115. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/smart_rules/__init__.py +0 -0
  116. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/smart_rules/compile.py +0 -0
  117. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/thinking_manager/thinking_manager.py +0 -0
  118. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/__init__.py +0 -0
  119. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/config.py +0 -0
  120. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/manager.py +0 -0
  121. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/memory.py +0 -0
  122. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/schema.py +0 -0
  123. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/a2a/service.py +0 -0
  124. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/agent_chunks_hanlder.py +0 -0
  125. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/config.py +0 -0
  126. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/factory.py +0 -0
  127. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/mcp/__init__.py +0 -0
  128. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/mcp/manager.py +0 -0
  129. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/mcp/models.py +0 -0
  130. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/mcp/tool_wrapper.py +0 -0
  131. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/schemas.py +0 -0
  132. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/test/test_mcp_manager.py +0 -0
  133. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/test/test_tool_progress_reporter.py +0 -0
  134. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/tool.py +0 -0
  135. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/tool_manager.py +0 -0
  136. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/tool_progress_reporter.py +0 -0
  137. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/utils/execution/execution.py +0 -0
  138. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/utils/source_handling/schema.py +0 -0
  139. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/utils/source_handling/source_formatting.py +0 -0
  140. {unique_toolkit-0.8.43 → unique_toolkit-0.8.44}/unique_toolkit/tools/utils/source_handling/tests/test_source_formatting.py +0 -0
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.44] - 2025-09-03
9
+ - Refine `EndpointClass` and create `EndpointRequestor`
10
+
8
11
  ## [0.8.43] - 2025-09-03
9
12
  - Add alias for `UniqueSettings` api base `API_BASE`
10
13
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 0.8.43
3
+ Version: 0.8.44
4
4
  Summary:
5
5
  License: Proprietary
6
- Author: Martin Fadler
7
- Author-email: martin.fadler@unique.ch
6
+ Author: Cedric Klinkert
7
+ Author-email: cedric.klinkert@unique.ch
8
8
  Requires-Python: >=3.11,<4.0
9
9
  Classifier: License :: Other/Proprietary License
10
10
  Classifier: Programming Language :: Python :: 3
@@ -117,6 +117,9 @@ All notable changes to this project will be documented in this file.
117
117
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
118
118
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
119
119
 
120
+ ## [0.8.44] - 2025-09-03
121
+ - Refine `EndpointClass` and create `EndpointRequestor`
122
+
120
123
  ## [0.8.43] - 2025-09-03
121
124
  - Add alias for `UniqueSettings` api base `API_BASE`
122
125
 
@@ -1,8 +1,9 @@
1
1
  [tool.poetry]
2
2
  name = "unique_toolkit"
3
- version = "0.8.43"
3
+ version = "0.8.44"
4
4
  description = ""
5
5
  authors = [
6
+ "Cedric Klinkert <cedric.klinkert@unique.ch>",
6
7
  "Martin Fadler <martin.fadler@unique.ch>",
7
8
  "Sadique Sheik <sadique@unique.ch>",
8
9
  "Fabian Schläpfer <fabian@unique.ch>",
@@ -0,0 +1,221 @@
1
+ """
2
+ This module provides a minimal framework for building endpoint classes such that a client can use
3
+ the endpoints without having to know the details of the endpoints.
4
+ """
5
+
6
+ from enum import StrEnum
7
+ from string import Formatter, Template
8
+ from typing import (
9
+ Any,
10
+ Callable,
11
+ Generic,
12
+ ParamSpec,
13
+ Protocol,
14
+ TypeVar,
15
+ )
16
+
17
+ from pydantic import BaseModel
18
+
19
+ # Paramspecs
20
+ PayloadParamSpec = ParamSpec("PayloadParamSpec")
21
+ PathParamsSpec = ParamSpec("PathParamsSpec")
22
+
23
+ # Type variables
24
+ ResponseType = TypeVar("ResponseType", bound=BaseModel, covariant=True)
25
+ PathParamsType = TypeVar("PathParamsType", bound=BaseModel)
26
+ PayloadType = TypeVar("PayloadType", bound=BaseModel)
27
+
28
+ # Helper type to extract constructor parameters
29
+
30
+ # Type for the constructor of a Pydantic model
31
+ ModelConstructor = Callable[..., BaseModel]
32
+
33
+
34
+ class EndpointMethods(StrEnum):
35
+ GET = "GET"
36
+ POST = "POST"
37
+ PUT = "PUT"
38
+ DELETE = "DELETE"
39
+ PATCH = "PATCH"
40
+ OPTIONS = "OPTIONS"
41
+ HEAD = "HEAD"
42
+
43
+
44
+ # Necessary for typing of make_endpoint_class
45
+ class EndpointClassProtocol(
46
+ Protocol,
47
+ Generic[
48
+ PathParamsSpec,
49
+ PathParamsType,
50
+ PayloadParamSpec,
51
+ PayloadType,
52
+ ResponseType,
53
+ ],
54
+ ):
55
+ path_params_model: type[PathParamsType]
56
+ payload_model: type[PayloadType]
57
+ response_model: type[ResponseType]
58
+
59
+ @staticmethod
60
+ def create_url(
61
+ *args: PathParamsSpec.args, **kwargs: PathParamsSpec.kwargs
62
+ ) -> str: ...
63
+
64
+ @staticmethod
65
+ def create_url_from_model(path_params: PathParamsType) -> str: ...
66
+
67
+ @staticmethod
68
+ def create_payload(
69
+ *args: PayloadParamSpec.args, **kwargs: PayloadParamSpec.kwargs
70
+ ) -> dict[str, Any]: ...
71
+
72
+ @staticmethod
73
+ def create_payload_from_model(payload: PayloadType) -> dict[str, Any]: ...
74
+
75
+ @staticmethod
76
+ def handle_response(response: dict[str, Any]) -> ResponseType: ...
77
+
78
+ @staticmethod
79
+ def request_method() -> EndpointMethods: ...
80
+
81
+
82
+ # Model for any client to implement
83
+ def build_endpoint_class(
84
+ *,
85
+ method: EndpointMethods,
86
+ url_template: Template,
87
+ path_params_constructor: Callable[PathParamsSpec, PathParamsType],
88
+ payload_constructor: Callable[PayloadParamSpec, PayloadType],
89
+ response_model_type: type[ResponseType],
90
+ dump_options: dict | None = None,
91
+ ) -> type[
92
+ EndpointClassProtocol[
93
+ PathParamsSpec,
94
+ PathParamsType,
95
+ PayloadParamSpec,
96
+ PayloadType,
97
+ ResponseType,
98
+ ]
99
+ ]:
100
+ """Generate a class with static methods for endpoint handling.
101
+
102
+ Uses separate models for path parameters and request body for clean API design.
103
+
104
+ Returns a class with static methods:
105
+ - create_url: Creates URL from path parameters
106
+ - create_payload: Creates request body payload
107
+ """
108
+
109
+ # Verify that the path_params_constructor and payload_constructor are valid pydantic models
110
+ if not isinstance(path_params_constructor, type(BaseModel)):
111
+ raise ValueError("path_params_constructor must be a pydantic model")
112
+ if not isinstance(payload_constructor, type(BaseModel)):
113
+ raise ValueError("payload_constructor must be a pydantic model")
114
+
115
+ if not dump_options:
116
+ dump_options = {
117
+ "exclude_unset": True,
118
+ "by_alias": True,
119
+ "exclude_defaults": True,
120
+ }
121
+
122
+ class EndpointClass(EndpointClassProtocol):
123
+ path_params_model = path_params_constructor # type: ignore
124
+ payload_model = payload_constructor # type: ignore
125
+ response_model = response_model_type
126
+
127
+ @staticmethod
128
+ def create_url(
129
+ *args: PathParamsSpec.args,
130
+ **kwargs: PathParamsSpec.kwargs,
131
+ ) -> str:
132
+ """Create URL from path parameters."""
133
+ path_model = EndpointClass.path_params_model(*args, **kwargs)
134
+ path_dict = path_model.model_dump(**dump_options)
135
+
136
+ # Extract expected path parameters from template
137
+ template_params = [
138
+ fname
139
+ for _, fname, _, _ in Formatter().parse(url_template.template)
140
+ if fname is not None
141
+ ]
142
+
143
+ # Verify all required path parameters are present
144
+ missing_params = [
145
+ param for param in template_params if param not in path_dict
146
+ ]
147
+ if missing_params:
148
+ raise ValueError(f"Missing path parameters: {missing_params}")
149
+
150
+ return url_template.substitute(**path_dict)
151
+
152
+ @staticmethod
153
+ def create_url_from_model(path_params: PathParamsType) -> str:
154
+ return url_template.substitute(**path_params.model_dump(**dump_options))
155
+
156
+ @staticmethod
157
+ def create_payload(
158
+ *args: PayloadParamSpec.args, **kwargs: PayloadParamSpec.kwargs
159
+ ) -> dict[str, Any]:
160
+ """Create request body payload."""
161
+ request_model = EndpointClass.payload_model(*args, **kwargs)
162
+ return request_model.model_dump(**dump_options)
163
+
164
+ @staticmethod
165
+ def create_payload_from_model(payload: PayloadType) -> dict[str, Any]:
166
+ return payload.model_dump(**dump_options)
167
+
168
+ @staticmethod
169
+ def handle_response(response: dict[str, Any]) -> ResponseType:
170
+ return EndpointClass.response_model.model_validate(response)
171
+
172
+ @staticmethod
173
+ def request_method() -> EndpointMethods:
174
+ return method
175
+
176
+ return EndpointClass
177
+
178
+
179
+ if __name__ == "__main__":
180
+ # Example models
181
+ class GetUserPathParams(BaseModel):
182
+ """Path parameters for the user endpoint."""
183
+
184
+ user_id: int
185
+
186
+ class GetUserRequestBody(BaseModel):
187
+ """Request body/query parameters for the user endpoint."""
188
+
189
+ include_profile: bool = False
190
+
191
+ class UserResponse(BaseModel):
192
+ """Response model for user data."""
193
+
194
+ id: int
195
+ name: str
196
+
197
+ # Example usage of make_endpoint_class
198
+ UserEndpoint = build_endpoint_class(
199
+ url_template=Template("/users/${user_id}"),
200
+ path_params_constructor=GetUserPathParams,
201
+ payload_constructor=GetUserRequestBody,
202
+ response_model_type=UserResponse,
203
+ method=EndpointMethods.GET,
204
+ )
205
+
206
+ # Create URL from path parameters
207
+ url = UserEndpoint.create_url(user_id=123)
208
+ print(f"URL: {url}")
209
+
210
+ # Create payload from request body parameters
211
+ payload = UserEndpoint.create_payload(include_profile=True)
212
+ print(f"Payload: {payload}")
213
+
214
+ # Create response from endpoint
215
+ response = UserEndpoint.handle_response(
216
+ {
217
+ "id": 123,
218
+ "name": "John Doe",
219
+ }
220
+ )
221
+ print(f"Response: {response}")
@@ -0,0 +1,186 @@
1
+ from enum import StrEnum
2
+ from typing import Any, Callable, Generic, Protocol, TypeVar
3
+
4
+ from pydantic import BaseModel
5
+ from typing_extensions import ParamSpec
6
+
7
+ from unique_toolkit._common.endpoint_builder import (
8
+ EndpointClassProtocol,
9
+ EndpointMethods,
10
+ PathParamsSpec,
11
+ PathParamsType,
12
+ PayloadParamSpec,
13
+ PayloadType,
14
+ ResponseType,
15
+ )
16
+
17
+ # Paramspecs
18
+ CombinedParamsSpec = ParamSpec("CombinedParamsSpec")
19
+
20
+ # Type variables
21
+ CombinedParamsType = TypeVar("CombinedParamsType", bound=BaseModel)
22
+
23
+
24
+ class EndpointRequestorProtocol(Protocol, Generic[CombinedParamsSpec, ResponseType]):
25
+ @classmethod
26
+ def request(
27
+ cls,
28
+ headers: dict[str, str],
29
+ *args: CombinedParamsSpec.args,
30
+ **kwargs: CombinedParamsSpec.kwargs,
31
+ ) -> ResponseType: ...
32
+
33
+
34
+ def build_fake_requestor(
35
+ endpoint_type: type[
36
+ EndpointClassProtocol[
37
+ PathParamsSpec,
38
+ PathParamsType,
39
+ PayloadParamSpec,
40
+ PayloadType,
41
+ ResponseType,
42
+ ]
43
+ ],
44
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
45
+ return_value: dict[str, Any],
46
+ ) -> type[EndpointRequestorProtocol[CombinedParamsSpec, ResponseType]]:
47
+ class FakeRequestor(EndpointRequestorProtocol):
48
+ _endpoint = endpoint_type
49
+
50
+ @classmethod
51
+ def request(
52
+ cls,
53
+ headers: dict[str, str],
54
+ *args: CombinedParamsSpec.args,
55
+ **kwargs: CombinedParamsSpec.kwargs,
56
+ ) -> ResponseType:
57
+ try:
58
+ combined_model(*args, **kwargs)
59
+ except Exception as e:
60
+ raise ValueError(
61
+ f"Invalid parameters passed to combined model {combined_model.__name__}: {e}"
62
+ )
63
+
64
+ return cls._endpoint.handle_response(return_value)
65
+
66
+ return FakeRequestor
67
+
68
+
69
+ def build_request_requestor(
70
+ endpoint_type: type[
71
+ EndpointClassProtocol[
72
+ PathParamsSpec,
73
+ PathParamsType,
74
+ PayloadParamSpec,
75
+ PayloadType,
76
+ ResponseType,
77
+ ]
78
+ ],
79
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
80
+ ) -> type[EndpointRequestorProtocol]:
81
+ import requests
82
+
83
+ class RequestRequestor(EndpointRequestorProtocol):
84
+ _endpoint = endpoint_type
85
+
86
+ @classmethod
87
+ def request(
88
+ cls,
89
+ headers: dict[str, str],
90
+ *args: CombinedParamsSpec.args,
91
+ **kwargs: CombinedParamsSpec.kwargs,
92
+ ) -> ResponseType:
93
+ url = cls._endpoint.create_url_from_model(combined_model(*args, **kwargs))
94
+ payload = cls._endpoint.create_payload_from_model(
95
+ combined_model(*args, **kwargs)
96
+ )
97
+
98
+ response = requests.request(
99
+ method=cls._endpoint.request_method(),
100
+ url=url,
101
+ headers=headers,
102
+ json=payload,
103
+ )
104
+ return cls._endpoint.handle_response(response.json())
105
+
106
+ return RequestRequestor
107
+
108
+
109
+ class RequestorType(StrEnum):
110
+ REQUESTS = "requests"
111
+ FAKE = "fake"
112
+
113
+
114
+ def build_requestor(
115
+ requestor_type: RequestorType,
116
+ endpoint_type: type[
117
+ EndpointClassProtocol[
118
+ PathParamsSpec,
119
+ PathParamsType,
120
+ PayloadParamSpec,
121
+ PayloadType,
122
+ ResponseType,
123
+ ]
124
+ ],
125
+ combined_model: Callable[CombinedParamsSpec, CombinedParamsType],
126
+ return_value: dict[str, Any] | None = None,
127
+ **kwargs: Any,
128
+ ) -> type[EndpointRequestorProtocol]:
129
+ match requestor_type:
130
+ case RequestorType.REQUESTS:
131
+ return build_request_requestor(
132
+ endpoint_type=endpoint_type, combined_model=combined_model
133
+ )
134
+ case RequestorType.FAKE:
135
+ if return_value is None:
136
+ raise ValueError("return_value is required for fake requestor")
137
+ return build_fake_requestor(
138
+ endpoint_type=endpoint_type,
139
+ combined_model=combined_model,
140
+ return_value=return_value,
141
+ )
142
+
143
+
144
+ if __name__ == "__main__":
145
+ from string import Template
146
+
147
+ from unique_toolkit._common.endpoint_builder import build_endpoint_class
148
+
149
+ class GetUserPathParams(BaseModel):
150
+ user_id: int
151
+
152
+ class GetUserRequestBody(BaseModel):
153
+ include_profile: bool = False
154
+
155
+ class UserResponse(BaseModel):
156
+ id: int
157
+ name: str
158
+
159
+ class CombinedParams(GetUserPathParams, GetUserRequestBody):
160
+ pass
161
+
162
+ UserEndpoint = build_endpoint_class(
163
+ method=EndpointMethods.GET,
164
+ url_template=Template("https://api.example.com/users/{user_id}"),
165
+ path_params_constructor=GetUserPathParams,
166
+ payload_constructor=GetUserRequestBody,
167
+ response_model_type=UserResponse,
168
+ )
169
+
170
+ FakeUserRequestor = build_fake_requestor(
171
+ endpoint_type=UserEndpoint,
172
+ combined_model=CombinedParams,
173
+ return_value={"id": 100, "name": "John Doe"},
174
+ )
175
+
176
+ # Note that the return value is a pydantic UserResponse object
177
+ response = FakeUserRequestor().request(
178
+ headers={"a": "b"},
179
+ user_id=123,
180
+ include_profile=True,
181
+ )
182
+
183
+ print(response.model_dump())
184
+ print(response.model_json_schema())
185
+ print(response.id)
186
+ print(response.name)
@@ -1,145 +0,0 @@
1
- """
2
- This module provides a minimal framework for building endpoint classes such that a client can use
3
- the endpoints without having to know the details of the endpoints.
4
- """
5
-
6
- from collections.abc import Callable
7
- from string import Formatter, Template
8
- from typing import (
9
- Any,
10
- Generic,
11
- ParamSpec,
12
- Protocol,
13
- TypeVar,
14
- )
15
-
16
- from pydantic import BaseModel
17
-
18
- # Type variables
19
- ResponseType = TypeVar("ResponseType", bound=BaseModel)
20
- PathParamsType = TypeVar("PathParamsType", bound=BaseModel)
21
- RequestBodyType = TypeVar("RequestBodyType", bound=BaseModel)
22
-
23
- # ParamSpecs for function signatures
24
- RequestConstructorSpec = ParamSpec("RequestConstructorSpec")
25
- PathParamsSpec = ParamSpec("PathParamsSpec")
26
- RequestBodySpec = ParamSpec("RequestBodySpec")
27
-
28
-
29
- # Necessary for typing of make_endpoint_class
30
- class EndpointClassProtocol(Protocol, Generic[PathParamsSpec, RequestBodySpec]):
31
- @staticmethod
32
- def create_url(
33
- *args: PathParamsSpec.args, **kwargs: PathParamsSpec.kwargs
34
- ) -> str: ...
35
-
36
- @staticmethod
37
- def create_payload(
38
- *args: RequestBodySpec.args, **kwargs: RequestBodySpec.kwargs
39
- ) -> dict[str, Any]: ...
40
-
41
-
42
- # Model for any client to implement
43
- class Client(Protocol):
44
- def request(
45
- self,
46
- endpoint: EndpointClassProtocol,
47
- ) -> dict[str, Any]: ...
48
-
49
-
50
- def build_endpoint_class(
51
- *,
52
- url_template: Template,
53
- path_params_model: Callable[PathParamsSpec, PathParamsType],
54
- payload_model: Callable[RequestBodySpec, RequestBodyType],
55
- response_model: type[ResponseType],
56
- dump_options: dict | None = None,
57
- ) -> type[EndpointClassProtocol[PathParamsSpec, RequestBodySpec]]:
58
- """Generate a class with static methods for endpoint handling.
59
-
60
- Uses separate models for path parameters and request body for clean API design.
61
-
62
- Returns a class with static methods:
63
- - create_url: Creates URL from path parameters
64
- - create_payload: Creates request body payload
65
- """
66
- if not dump_options:
67
- dump_options = {
68
- "exclude_unset": True,
69
- "by_alias": True,
70
- "exclude_defaults": True,
71
- }
72
-
73
- class EndpointClass(EndpointClassProtocol):
74
- @staticmethod
75
- def create_url(
76
- *args: PathParamsSpec.args, **kwargs: PathParamsSpec.kwargs
77
- ) -> str:
78
- """Create URL from path parameters."""
79
- path_model = path_params_model(*args, **kwargs)
80
- path_dict = path_model.model_dump(**dump_options)
81
-
82
- # Extract expected path parameters from template
83
- template_params = [
84
- fname
85
- for _, fname, _, _ in Formatter().parse(url_template.template)
86
- if fname is not None
87
- ]
88
-
89
- # Verify all required path parameters are present
90
- missing_params = [
91
- param for param in template_params if param not in path_dict
92
- ]
93
- if missing_params:
94
- raise ValueError(f"Missing path parameters: {missing_params}")
95
-
96
- return url_template.substitute(**path_dict)
97
-
98
- @staticmethod
99
- def create_payload(
100
- *args: RequestBodySpec.args, **kwargs: RequestBodySpec.kwargs
101
- ) -> dict[str, Any]:
102
- """Create request body payload."""
103
- request_model = payload_model(*args, **kwargs)
104
- return request_model.model_dump(**dump_options)
105
-
106
- @staticmethod
107
- def handle_response(response: dict[str, Any]) -> ResponseType:
108
- return response_model.model_validate(response)
109
-
110
- return EndpointClass
111
-
112
-
113
- if __name__ == "__main__":
114
- # Example models
115
- class GetUserPathParams(BaseModel):
116
- """Path parameters for the user endpoint."""
117
-
118
- user_id: int
119
-
120
- class GetUserRequestBody(BaseModel):
121
- """Request body/query parameters for the user endpoint."""
122
-
123
- include_profile: bool = False
124
-
125
- class UserResponse(BaseModel):
126
- """Response model for user data."""
127
-
128
- id: int
129
- name: str
130
-
131
- # Example usage of make_endpoint_class
132
- UserEndpoint = build_endpoint_class(
133
- url_template=Template("/users/${user_id}"),
134
- path_params_model=GetUserPathParams,
135
- payload_model=GetUserRequestBody,
136
- response_model=UserResponse,
137
- )
138
-
139
- # Create URL from path parameters
140
- url = UserEndpoint.create_url(user_id=123)
141
- print(f"URL: {url}")
142
-
143
- # Create payload from request body parameters
144
- payload = UserEndpoint.create_payload(include_profile=True)
145
- print(f"Payload: {payload}")
File without changes