hammad-python 0.0.14__py3-none-any.whl → 0.0.15__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.
Files changed (101) hide show
  1. hammad_python-0.0.15.dist-info/METADATA +184 -0
  2. hammad_python-0.0.15.dist-info/RECORD +4 -0
  3. hammad/__init__.py +0 -1
  4. hammad/ai/__init__.py +0 -1
  5. hammad/ai/_utils.py +0 -142
  6. hammad/ai/completions/__init__.py +0 -45
  7. hammad/ai/completions/client.py +0 -684
  8. hammad/ai/completions/create.py +0 -710
  9. hammad/ai/completions/settings.py +0 -100
  10. hammad/ai/completions/types.py +0 -792
  11. hammad/ai/completions/utils.py +0 -486
  12. hammad/ai/embeddings/__init__.py +0 -35
  13. hammad/ai/embeddings/client/__init__.py +0 -1
  14. hammad/ai/embeddings/client/base_embeddings_client.py +0 -26
  15. hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +0 -200
  16. hammad/ai/embeddings/client/litellm_embeddings_client.py +0 -288
  17. hammad/ai/embeddings/create.py +0 -159
  18. hammad/ai/embeddings/types.py +0 -69
  19. hammad/cache/__init__.py +0 -40
  20. hammad/cache/base_cache.py +0 -181
  21. hammad/cache/cache.py +0 -169
  22. hammad/cache/decorators.py +0 -261
  23. hammad/cache/file_cache.py +0 -80
  24. hammad/cache/ttl_cache.py +0 -74
  25. hammad/cli/__init__.py +0 -33
  26. hammad/cli/animations.py +0 -573
  27. hammad/cli/plugins.py +0 -781
  28. hammad/cli/styles/__init__.py +0 -55
  29. hammad/cli/styles/settings.py +0 -139
  30. hammad/cli/styles/types.py +0 -358
  31. hammad/cli/styles/utils.py +0 -480
  32. hammad/data/__init__.py +0 -56
  33. hammad/data/collections/__init__.py +0 -34
  34. hammad/data/collections/base_collection.py +0 -58
  35. hammad/data/collections/collection.py +0 -452
  36. hammad/data/collections/searchable_collection.py +0 -556
  37. hammad/data/collections/vector_collection.py +0 -596
  38. hammad/data/configurations/__init__.py +0 -35
  39. hammad/data/configurations/configuration.py +0 -564
  40. hammad/data/databases/__init__.py +0 -21
  41. hammad/data/databases/database.py +0 -902
  42. hammad/data/models/__init__.py +0 -44
  43. hammad/data/models/base/__init__.py +0 -35
  44. hammad/data/models/base/fields.py +0 -546
  45. hammad/data/models/base/model.py +0 -1078
  46. hammad/data/models/base/utils.py +0 -280
  47. hammad/data/models/pydantic/__init__.py +0 -55
  48. hammad/data/models/pydantic/converters.py +0 -632
  49. hammad/data/models/pydantic/models/__init__.py +0 -28
  50. hammad/data/models/pydantic/models/arbitrary_model.py +0 -46
  51. hammad/data/models/pydantic/models/cacheable_model.py +0 -79
  52. hammad/data/models/pydantic/models/fast_model.py +0 -318
  53. hammad/data/models/pydantic/models/function_model.py +0 -176
  54. hammad/data/models/pydantic/models/subscriptable_model.py +0 -63
  55. hammad/data/types/__init__.py +0 -41
  56. hammad/data/types/file.py +0 -358
  57. hammad/data/types/multimodal/__init__.py +0 -24
  58. hammad/data/types/multimodal/audio.py +0 -96
  59. hammad/data/types/multimodal/image.py +0 -80
  60. hammad/data/types/text.py +0 -1066
  61. hammad/formatting/__init__.py +0 -38
  62. hammad/formatting/json/__init__.py +0 -21
  63. hammad/formatting/json/converters.py +0 -152
  64. hammad/formatting/text/__init__.py +0 -63
  65. hammad/formatting/text/converters.py +0 -723
  66. hammad/formatting/text/markdown.py +0 -131
  67. hammad/formatting/yaml/__init__.py +0 -26
  68. hammad/formatting/yaml/converters.py +0 -5
  69. hammad/logging/__init__.py +0 -35
  70. hammad/logging/decorators.py +0 -834
  71. hammad/logging/logger.py +0 -954
  72. hammad/mcp/__init__.py +0 -50
  73. hammad/mcp/client/__init__.py +0 -1
  74. hammad/mcp/client/client.py +0 -523
  75. hammad/mcp/client/client_service.py +0 -393
  76. hammad/mcp/client/settings.py +0 -178
  77. hammad/mcp/servers/__init__.py +0 -1
  78. hammad/mcp/servers/launcher.py +0 -1161
  79. hammad/performance/__init__.py +0 -36
  80. hammad/performance/imports.py +0 -231
  81. hammad/performance/runtime/__init__.py +0 -32
  82. hammad/performance/runtime/decorators.py +0 -142
  83. hammad/performance/runtime/run.py +0 -299
  84. hammad/py.typed +0 -0
  85. hammad/service/__init__.py +0 -49
  86. hammad/service/create.py +0 -532
  87. hammad/service/decorators.py +0 -285
  88. hammad/typing/__init__.py +0 -407
  89. hammad/web/__init__.py +0 -43
  90. hammad/web/http/__init__.py +0 -1
  91. hammad/web/http/client.py +0 -944
  92. hammad/web/models.py +0 -245
  93. hammad/web/openapi/__init__.py +0 -1
  94. hammad/web/openapi/client.py +0 -740
  95. hammad/web/search/__init__.py +0 -1
  96. hammad/web/search/client.py +0 -988
  97. hammad/web/utils.py +0 -472
  98. hammad_python-0.0.14.dist-info/METADATA +0 -70
  99. hammad_python-0.0.14.dist-info/RECORD +0 -99
  100. {hammad_python-0.0.14.dist-info → hammad_python-0.0.15.dist-info}/WHEEL +0 -0
  101. {hammad_python-0.0.14.dist-info → hammad_python-0.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,285 +0,0 @@
1
- """hammad.service.decorators"""
2
-
3
- from typing import (
4
- Any,
5
- Callable,
6
- Dict,
7
- List,
8
- Literal,
9
- Optional,
10
- Type,
11
- Union,
12
- ParamSpec,
13
- TypeVar,
14
- )
15
-
16
-
17
- ServiceFunctionParams = ParamSpec("ServiceFunctionParams")
18
- ServiceFunctionReturn = TypeVar("ServiceFunctionReturn")
19
-
20
-
21
- def serve(
22
- func: Optional[Callable[ServiceFunctionParams, ServiceFunctionReturn]] = None,
23
- *,
24
- # Overrides
25
- name: Optional[str] = None,
26
- method: Literal[
27
- "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"
28
- ] = "POST",
29
- path: str = "/",
30
- # Server configuration
31
- host: str = "0.0.0.0",
32
- port: int = 8000,
33
- log_level: str = "info",
34
- reload: bool = False,
35
- workers: int = 1,
36
- timeout_keep_alive: int = 5,
37
- access_log: bool = True,
38
- use_colors: bool = True,
39
- auto_start: bool = True,
40
- # FastAPI
41
- include_in_schema: bool = True,
42
- dependencies: Optional[List[Callable[..., Any]]] = None,
43
- tags: Optional[List[str]] = None,
44
- description: Optional[str] = None,
45
- ) -> Union[Callable[ServiceFunctionParams, ServiceFunctionReturn], Callable]:
46
- """Decorator to serve a function as a FastAPI endpoint.
47
-
48
- Can be used as a decorator (@serve) or as a function (serve(func)).
49
-
50
- Args:
51
- func: Function to serve (when used as decorator, this is None initially)
52
- name: Service name (defaults to function name)
53
- method: HTTP method to use
54
- path: API endpoint path
55
- host: Host to bind to
56
- port: Port to bind to
57
- log_level: Uvicorn log level
58
- reload: Enable auto-reload
59
- workers: Number of worker processes
60
- timeout_keep_alive: Keep-alive timeout
61
- access_log: Enable access logging
62
- use_colors: Use colored logs
63
- auto_start: Automatically start the server
64
- include_in_schema: Include in OpenAPI schema
65
- dependencies: FastAPI dependencies
66
- tags: API tags
67
- description: API description
68
-
69
- Returns:
70
- The original function (when used as decorator)
71
- """
72
- from .create import create_service
73
- from ..mcp.servers.launcher import find_next_free_port
74
-
75
- def decorator(
76
- f: Callable[ServiceFunctionParams, ServiceFunctionReturn],
77
- ) -> Callable[ServiceFunctionParams, ServiceFunctionReturn]:
78
- # Find next available port if auto_start is True
79
- actual_port = port
80
- if auto_start:
81
- actual_port = find_next_free_port(port, host)
82
-
83
- # Handle dependencies - convert raw functions to FastAPI Depends
84
- processed_dependencies = None
85
- if dependencies is not None:
86
- from fastapi import Depends
87
-
88
- processed_dependencies = [
89
- Depends(dep) if callable(dep) else dep for dep in dependencies
90
- ]
91
-
92
- # Store the service configuration on the function
93
- f._service_config = {
94
- "name": name or f.__name__,
95
- "method": method,
96
- "path": path,
97
- "host": host,
98
- "port": actual_port,
99
- "log_level": log_level,
100
- "reload": reload,
101
- "workers": workers,
102
- "timeout_keep_alive": timeout_keep_alive,
103
- "access_log": access_log,
104
- "use_colors": use_colors,
105
- "auto_start": auto_start,
106
- "include_in_schema": include_in_schema,
107
- "dependencies": processed_dependencies,
108
- "tags": tags,
109
- "description": description,
110
- }
111
-
112
- # Create and start the service immediately if auto_start is True
113
- if auto_start:
114
- create_service(f, **f._service_config)
115
-
116
- return f
117
-
118
- if func is None:
119
- # Called as @serve(...)
120
- return decorator
121
- else:
122
- # Called as @serve (without parentheses)
123
- return decorator(func)
124
-
125
-
126
- def serve_mcp(
127
- func_or_funcs: Optional[Union[Callable, List[Callable]]] = None,
128
- *,
129
- # MCP Server configuration
130
- name: Optional[str] = None,
131
- instructions: Optional[str] = None,
132
- transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
133
- # Server settings (for sse/http transports)
134
- host: str = "127.0.0.1",
135
- port: int = 8000,
136
- mount_path: str = "/",
137
- sse_path: str = "/sse",
138
- message_path: str = "/messages/",
139
- streamable_http_path: str = "/mcp",
140
- json_response: bool = False,
141
- stateless_http: bool = False,
142
- warn_on_duplicate_resources: bool = True,
143
- warn_on_duplicate_tools: bool = True,
144
- # FastMCP settings
145
- dependencies: Optional[List[str]] = None,
146
- log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
147
- debug_mode: bool = False,
148
- cwd: Optional[str] = None,
149
- # Launch settings
150
- auto_restart: bool = False,
151
- check_interval: float = 1.0,
152
- # Function-specific parameters (only when single function)
153
- single_func_name: Optional[str] = None,
154
- single_func_description: Optional[str] = None,
155
- ) -> Union[Callable, List[Callable]]:
156
- """Decorator/function to serve functions as MCP server tools.
157
-
158
- Can be used in multiple ways:
159
- 1. As a decorator: @serve_mcp
160
- 2. As a decorator with params: @serve_mcp(name="MyServer")
161
- 3. As a function with single function: serve_mcp(my_func)
162
- 4. As a function with multiple functions: serve_mcp([func1, func2])
163
-
164
- Args:
165
- func_or_funcs: Function or list of functions to serve
166
- name: MCP server name
167
- instructions: Server instructions
168
- transport: Transport type (stdio, sse, streamable-http)
169
- host: Host for HTTP transports
170
- port: Starting port for HTTP transports
171
- mount_path: Mount path for HTTP servers
172
- sse_path: SSE endpoint path
173
- message_path: Message endpoint path
174
- streamable_http_path: StreamableHTTP endpoint path
175
- json_response: Use JSON responses for HTTP
176
- stateless_http: Use stateless HTTP mode
177
- warn_on_duplicate_resources: Warn on duplicate resources
178
- warn_on_duplicate_tools: Warn on duplicate tools
179
- dependencies: FastMCP dependencies
180
- log_level: Logging level
181
- debug_mode: Enable debug mode
182
- cwd: Working directory
183
- auto_restart: Automatically restart failed servers
184
- check_interval: Health check interval in seconds
185
- single_func_name: Name override for single function
186
- single_func_description: Description for single function
187
-
188
- Returns:
189
- Original function(s) unchanged
190
- """
191
- from ..mcp.servers.launcher import (
192
- launch_mcp_servers,
193
- MCPServerStdioSettings,
194
- MCPServerSseSettings,
195
- MCPServerStreamableHttpSettings,
196
- )
197
-
198
- def _create_server_config(
199
- tools: List[Callable], server_name: str, server_instructions: Optional[str]
200
- ):
201
- """Create the appropriate server configuration based on transport type."""
202
- base_config = {
203
- "name": server_name,
204
- "instructions": server_instructions,
205
- "tools": tools,
206
- "dependencies": dependencies or [],
207
- "log_level": log_level,
208
- "debug_mode": debug_mode,
209
- "cwd": cwd,
210
- }
211
-
212
- if transport == "stdio":
213
- return MCPServerStdioSettings(**base_config)
214
- elif transport == "sse":
215
- return MCPServerSseSettings(
216
- **base_config,
217
- host=host,
218
- start_port=port,
219
- mount_path=mount_path,
220
- sse_path=sse_path,
221
- message_path=message_path,
222
- json_response=json_response,
223
- stateless_http=stateless_http,
224
- warn_on_duplicate_resources=warn_on_duplicate_resources,
225
- warn_on_duplicate_tools=warn_on_duplicate_tools,
226
- )
227
- elif transport == "streamable-http":
228
- return MCPServerStreamableHttpSettings(
229
- **base_config,
230
- host=host,
231
- start_port=port,
232
- mount_path=mount_path,
233
- streamable_http_path=streamable_http_path,
234
- json_response=json_response,
235
- stateless_http=stateless_http,
236
- warn_on_duplicate_resources=warn_on_duplicate_resources,
237
- warn_on_duplicate_tools=warn_on_duplicate_tools,
238
- )
239
- else:
240
- raise ValueError(f"Unsupported transport: {transport}")
241
-
242
- def _launch_server(server_config):
243
- """Launch the MCP server with the given configuration."""
244
- launch_mcp_servers(
245
- servers=[server_config],
246
- check_interval=check_interval,
247
- auto_restart=auto_restart,
248
- )
249
-
250
- def decorator(f: Callable) -> Callable:
251
- """Decorator for single function."""
252
- func_name = single_func_name or name or f.__name__
253
- func_instructions = single_func_description or instructions or f.__doc__
254
-
255
- # Create server configuration and launch
256
- server_config = _create_server_config([f], func_name, func_instructions)
257
- _launch_server(server_config)
258
-
259
- return f
260
-
261
- def handle_multiple_functions(funcs: List[Callable]) -> List[Callable]:
262
- """Handle multiple functions."""
263
- server_name = name or "MCPServer"
264
- server_instructions = instructions or f"MCP server with {len(funcs)} tools"
265
-
266
- # Create server configuration and launch
267
- server_config = _create_server_config(funcs, server_name, server_instructions)
268
- _launch_server(server_config)
269
-
270
- return funcs
271
-
272
- # Handle different call patterns
273
- if func_or_funcs is None:
274
- # Called as @serve_mcp(...) - return decorator
275
- return decorator
276
- elif callable(func_or_funcs):
277
- # Called as @serve_mcp (no parentheses) or serve_mcp(single_func)
278
- return decorator(func_or_funcs)
279
- elif isinstance(func_or_funcs, list):
280
- # Called as serve_mcp([func1, func2, ...])
281
- return handle_multiple_functions(func_or_funcs)
282
- else:
283
- raise TypeError(
284
- f"Expected callable or list of callables, got {type(func_or_funcs)}"
285
- )
hammad/typing/__init__.py DELETED
@@ -1,407 +0,0 @@
1
- """hammad.typing
2
-
3
- 'Namespace' package extension for various **CORE** typing resources and
4
- types. This is not a collection of built types, rather resources from the
5
- core `typing` module, `typing_extensions`, `typing_inspect` and other
6
- resources."""
7
-
8
- from typing import Any, TYPE_CHECKING
9
- import typing_inspect as inspection
10
-
11
- try:
12
- from typing_extensions import *
13
- except ImportError:
14
- from typing import *
15
-
16
- from typing_inspect import (
17
- is_callable_type,
18
- is_classvar,
19
- is_final_type,
20
- is_forward_ref,
21
- is_generic_type,
22
- is_literal_type,
23
- is_new_type,
24
- is_optional_type,
25
- is_union_type,
26
- is_typevar,
27
- is_tuple_type,
28
- get_origin,
29
- get_args,
30
- get_last_args,
31
- get_last_origin,
32
- get_generic_bases,
33
- typed_dict_keys as get_typed_dict_keys,
34
- )
35
- from typing_inspection.introspection import (
36
- is_union_origin,
37
- inspect_annotation,
38
- get_literal_values,
39
- )
40
- from dataclasses import is_dataclass
41
-
42
- __all__ = (
43
- # Super-special typing primitives.
44
- "Any",
45
- "ClassVar",
46
- "Concatenate",
47
- "Final",
48
- "LiteralString",
49
- "ParamSpec",
50
- "ParamSpecArgs",
51
- "ParamSpecKwargs",
52
- "Self",
53
- "Type",
54
- "TypeVar",
55
- "TypeVarTuple",
56
- "Unpack",
57
- # ABCs (from collections.abc).
58
- "Awaitable",
59
- "AsyncIterator",
60
- "AsyncIterable",
61
- "Coroutine",
62
- "AsyncGenerator",
63
- "AsyncContextManager",
64
- "Buffer",
65
- "ChainMap",
66
- # Concrete collection types.
67
- "ContextManager",
68
- "Counter",
69
- "Deque",
70
- "DefaultDict",
71
- "NamedTuple",
72
- "OrderedDict",
73
- "TypedDict",
74
- # Structural checks, a.k.a. protocols.
75
- "SupportsAbs",
76
- "SupportsBytes",
77
- "SupportsComplex",
78
- "SupportsFloat",
79
- "SupportsIndex",
80
- "SupportsInt",
81
- "SupportsRound",
82
- "Reader",
83
- "Writer",
84
- # One-off things.
85
- "Annotated",
86
- "assert_never",
87
- "assert_type",
88
- "clear_overloads",
89
- "dataclass_transform",
90
- "deprecated",
91
- "Doc",
92
- "evaluate_forward_ref",
93
- "get_overloads",
94
- "final",
95
- "Format",
96
- "get_annotations",
97
- "get_args",
98
- "get_origin",
99
- "get_original_bases",
100
- "get_protocol_members",
101
- "get_type_hints",
102
- "IntVar",
103
- "is_protocol",
104
- "is_typeddict",
105
- "Literal",
106
- "NewType",
107
- "overload",
108
- "override",
109
- "Protocol",
110
- "Sentinel",
111
- "reveal_type",
112
- "runtime",
113
- "runtime_checkable",
114
- "Text",
115
- "TypeAlias",
116
- "TypeAliasType",
117
- "TypeForm",
118
- "TypeGuard",
119
- "TypeIs",
120
- "TYPE_CHECKING",
121
- "Never",
122
- "NoReturn",
123
- "ReadOnly",
124
- "Required",
125
- "NotRequired",
126
- "NoDefault",
127
- "NoExtraItems",
128
- # Pure aliases, have always been in typing
129
- "AbstractSet",
130
- "AnyStr",
131
- "BinaryIO",
132
- "Callable",
133
- "Collection",
134
- "Container",
135
- "Dict",
136
- "ForwardRef",
137
- "FrozenSet",
138
- "Generator",
139
- "Generic",
140
- "Hashable",
141
- "IO",
142
- "ItemsView",
143
- "Iterable",
144
- "Iterator",
145
- "KeysView",
146
- "List",
147
- "Mapping",
148
- "MappingView",
149
- "Match",
150
- "MutableMapping",
151
- "MutableSequence",
152
- "MutableSet",
153
- "Optional",
154
- "Pattern",
155
- "Reversible",
156
- "Sequence",
157
- "Set",
158
- "Sized",
159
- "TextIO",
160
- "Tuple",
161
- "Union",
162
- "ValuesView",
163
- "cast",
164
- "no_type_check",
165
- "no_type_check_decorator",
166
- "TypingError",
167
- "get_type_description",
168
- "inspection",
169
- "is_pydantic_basemodel",
170
- "is_pydantic_basemodel_instance",
171
- "is_msgspec_struct",
172
- "is_dataclass",
173
- "is_callable_type",
174
- "is_classvar",
175
- "is_final_type",
176
- "is_forward_ref",
177
- "is_generic_type",
178
- "is_literal_type",
179
- "is_new_type",
180
- "is_optional_type",
181
- "is_union_type",
182
- "is_typevar",
183
- "is_tuple_type",
184
- "get_origin",
185
- "get_args",
186
- "is_union_origin",
187
- "inspect_annotation",
188
- "get_literal_values",
189
- "get_last_args",
190
- "get_last_origin",
191
- "get_generic_bases",
192
- "get_typed_dict_keys",
193
- )
194
-
195
-
196
- class TypingError(Exception):
197
- """An exception raised when a type utility raises an error."""
198
-
199
-
200
- # ------------------------------------------------------------------------
201
- # Inspection Extensions
202
- # ------------------------------------------------------------------------
203
-
204
-
205
- def is_pydantic_basemodel(t: "Any") -> bool:
206
- """Check if an object is a Pydantic BaseModel class or instance using duck typing.
207
-
208
- This function uses duck typing to identify Pydantic BaseModel objects by checking
209
- for the presence of characteristic attributes (`model_fields` and `model_dump`)
210
- without requiring direct imports of Pydantic.
211
-
212
- Args:
213
- t: The object to check. Can be a class, instance, or any other type.
214
-
215
- Returns:
216
- True if the object appears to be a Pydantic BaseModel (class or instance),
217
- False otherwise.
218
-
219
- Example:
220
- >>> from pydantic import BaseModel
221
- >>> class User(BaseModel):
222
- ... name: str
223
- >>> is_pydantic_basemodel(User)
224
- True
225
- >>> is_pydantic_basemodel(User(name="John"))
226
- True
227
- >>> is_pydantic_basemodel(dict)
228
- False
229
- """
230
- # Check if it's a class first
231
- if isinstance(t, type):
232
- return (
233
- hasattr(t, "model_fields")
234
- and hasattr(t, "model_dump")
235
- and callable(getattr(t, "model_dump", None))
236
- )
237
-
238
- # For instances, check the class instead of the instance to avoid deprecation warning
239
- return (
240
- hasattr(t.__class__, "model_fields")
241
- and hasattr(t, "model_dump")
242
- and callable(getattr(t, "model_dump", None))
243
- )
244
-
245
-
246
- def is_pydantic_basemodel_instance(t: "Any") -> bool:
247
- """Check if an object is an instance (not class) of a Pydantic BaseModel using duck typing.
248
-
249
- This function specifically identifies Pydantic BaseModel instances by ensuring
250
- the object is not a type/class itself and has the characteristic Pydantic attributes.
251
-
252
- Args:
253
- t: The object to check.
254
-
255
- Returns:
256
- True if the object is a Pydantic BaseModel instance (not the class itself),
257
- False otherwise.
258
-
259
- Example:
260
- >>> from pydantic import BaseModel
261
- >>> class User(BaseModel):
262
- ... name: str
263
- >>> user = User(name="John")
264
- >>> is_pydantic_basemodel_instance(user)
265
- True
266
- >>> is_pydantic_basemodel_instance(User) # Class, not instance
267
- False
268
- """
269
- return (
270
- not isinstance(t, type)
271
- and hasattr(t.__class__, "model_fields")
272
- and hasattr(t, "model_dump")
273
- and callable(getattr(t, "model_dump", None))
274
- )
275
-
276
-
277
- def is_msgspec_struct(t: "Any") -> bool:
278
- """Check if an object is a msgspec Struct class or instance using duck typing.
279
-
280
- This function uses duck typing to identify msgspec Struct objects by checking
281
- for the presence of characteristic attributes (`__struct_fields__` and
282
- `__struct_config__`) without requiring direct imports of msgspec.
283
-
284
- Args:
285
- t: The object to check. Can be a class, instance, or any other type.
286
-
287
- Returns:
288
- True if the object appears to be a msgspec Struct (class or instance),
289
- False otherwise.
290
-
291
- Example:
292
- >>> import msgspec
293
- >>> class User(msgspec.Struct):
294
- ... name: str
295
- >>> is_msgspec_struct(User)
296
- True
297
- >>> is_msgspec_struct(User(name="John"))
298
- True
299
- >>> is_msgspec_struct(dict)
300
- False
301
- """
302
- return hasattr(t, "__struct_fields__") and hasattr(t, "__struct_config__")
303
-
304
-
305
- def get_type_description(t: "Any") -> str:
306
- """Creates a human-readable description of a type hint.
307
-
308
- Args:
309
- t : The type hint to create a description for.
310
-
311
- Returns:
312
- A human-readable description of the type hint.
313
- """
314
- origin = inspection.get_origin(t)
315
- args = inspection.get_args(t)
316
-
317
- if origin is None:
318
- # Handle basic types that should have special names
319
- if t is list:
320
- return "array"
321
- elif t is dict:
322
- return "object"
323
- elif t is tuple:
324
- return "tuple"
325
- elif hasattr(t, "__name__"):
326
- return t.__name__
327
- return str(t)
328
-
329
- if origin is list:
330
- if args:
331
- return f"array of {get_type_description(args[0])}"
332
- return "array"
333
-
334
- if origin is dict:
335
- if len(args) == 2:
336
- return f"object with {get_type_description(args[0])} keys and {get_type_description(args[1])} values"
337
- return "object"
338
-
339
- if origin is tuple:
340
- if args:
341
- arg_descriptions = [get_type_description(arg) for arg in args]
342
- return f"tuple of ({', '.join(arg_descriptions)})"
343
- return "tuple"
344
-
345
- if inspection.is_literal_type(t):
346
- if args:
347
- values = [repr(arg) for arg in args]
348
- return f"one of: {', '.join(values)}"
349
- return "literal"
350
-
351
- # Handle Union types (including Optional)
352
- if inspection.is_union_type(t):
353
- if inspection.is_optional_type(t):
354
- # This is Optional[T]
355
- non_none_args = [arg for arg in args if arg is not type(None)]
356
- if non_none_args:
357
- return f"optional {get_type_description(non_none_args[0])}"
358
- else:
359
- # This is Union[T1, T2, ...]
360
- arg_descriptions = [get_type_description(arg) for arg in args]
361
- return f"one of: {', '.join(arg_descriptions)}"
362
-
363
- # Handle callable types
364
- if inspection.is_callable_type(t):
365
- if args and len(args) >= 2:
366
- param_types_arg = args[0] # First arg is the parameter types
367
- return_type = args[1] # Second arg is the return type
368
-
369
- # param_types_arg is either a list of types or ... (Ellipsis)
370
- if param_types_arg is ...:
371
- return f"function(...) -> {get_type_description(return_type)}"
372
- elif isinstance(param_types_arg, (list, tuple)):
373
- if param_types_arg:
374
- param_descriptions = [
375
- get_type_description(param) for param in param_types_arg
376
- ]
377
- return f"function({', '.join(param_descriptions)}) -> {get_type_description(return_type)}"
378
- else:
379
- return f"function() -> {get_type_description(return_type)}"
380
- return "function"
381
-
382
- # Handle generic types
383
- if inspection.is_generic_type(t):
384
- if args:
385
- arg_descriptions = [get_type_description(arg) for arg in args]
386
- return f"{origin.__name__}[{', '.join(arg_descriptions)}]"
387
- return str(origin)
388
-
389
- # Handle final types
390
- if inspection.is_final_type(t):
391
- if args:
392
- return f"final {get_type_description(args[0])}"
393
- return "final"
394
-
395
- # Handle forward references
396
- if inspection.is_forward_ref(t):
397
- return f"forward_ref({t.__forward_arg__})"
398
-
399
- # Handle new types
400
- if inspection.is_new_type(t):
401
- return f"new_type({t.__name__})"
402
-
403
- # Handle type variables
404
- if inspection.is_typevar(t):
405
- return f"typevar({t.__name__})"
406
-
407
- return str(t)