mirascope 2.0.1__py3-none-any.whl → 2.0.2__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.
mirascope/_utils.py ADDED
@@ -0,0 +1,34 @@
1
+ """Shared internal utilities for mirascope."""
2
+
3
+ from typing import Any
4
+
5
+ # Attributes to copy from wrapped functions (matches functools.WRAPPER_ASSIGNMENTS)
6
+ WRAPPER_ASSIGNMENTS = (
7
+ "__module__",
8
+ "__name__",
9
+ "__qualname__",
10
+ "__annotations__",
11
+ "__doc__",
12
+ )
13
+
14
+
15
+ def copy_function_metadata(target: Any, source: Any) -> None: # noqa: ANN401
16
+ """Copy standard function metadata from source to target.
17
+
18
+ Copies __module__, __name__, __qualname__, __annotations__, __doc__
19
+ from source to target, and sets __wrapped__ to source.
20
+
21
+ This enables decorator stacking by preserving the original function's
22
+ metadata on wrapper objects.
23
+
24
+ Args:
25
+ target: The wrapper object to copy metadata to
26
+ source: The original function to copy metadata from
27
+ """
28
+ for attr in WRAPPER_ASSIGNMENTS:
29
+ try:
30
+ value = getattr(source, attr)
31
+ object.__setattr__(target, attr, value)
32
+ except AttributeError:
33
+ pass
34
+ object.__setattr__(target, "__wrapped__", source)
@@ -92,7 +92,7 @@ class OrganizationInvitationsClient:
92
92
  organization_id : str
93
93
 
94
94
  recipient_email : str
95
- a string matching the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$
95
+ a string matching the pattern ^[^ \t\n\r\f\v@]+@[^ \t\n\r\f\v@]+[.][^ \t\n\r\f\v@]+$
96
96
 
97
97
  role : OrganizationInvitationsCreateRequestRole
98
98
 
@@ -335,7 +335,7 @@ class AsyncOrganizationInvitationsClient:
335
335
  organization_id : str
336
336
 
337
337
  recipient_email : str
338
- a string matching the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$
338
+ a string matching the pattern ^[^ \t\n\r\f\v@]+@[^ \t\n\r\f\v@]+[.][^ \t\n\r\f\v@]+$
339
339
 
340
340
  role : OrganizationInvitationsCreateRequestRole
341
341
 
@@ -172,7 +172,7 @@ class RawOrganizationInvitationsClient:
172
172
  organization_id : str
173
173
 
174
174
  recipient_email : str
175
- a string matching the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$
175
+ a string matching the pattern ^[^ \t\n\r\f\v@]+@[^ \t\n\r\f\v@]+[.][^ \t\n\r\f\v@]+$
176
176
 
177
177
  role : OrganizationInvitationsCreateRequestRole
178
178
 
@@ -911,7 +911,7 @@ class AsyncRawOrganizationInvitationsClient:
911
911
  organization_id : str
912
912
 
913
913
  recipient_email : str
914
- a string matching the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$
914
+ a string matching the pattern ^[^ \t\n\r\f\v@]+@[^ \t\n\r\f\v@]+[.][^ \t\n\r\f\v@]+$
915
915
 
916
916
  role : OrganizationInvitationsCreateRequestRole
917
917
 
@@ -1682,7 +1682,7 @@ client.organization_invitations.create(
1682
1682
  <dl>
1683
1683
  <dd>
1684
1684
 
1685
- **recipient_email:** `str` — a string matching the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$
1685
+ **recipient_email:** `str` — a string matching the pattern ^[^ \t\n\r\f\v@]+@[^ \t\n\r\f\v@]+[.][^ \t\n\r\f\v@]+$
1686
1686
 
1687
1687
  </dd>
1688
1688
  </dl>
@@ -1,8 +1,10 @@
1
1
  """The Call module for generating responses using LLMs."""
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Generic, TypeVar, overload
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Generic, TypeVar, overload
5
6
 
7
+ from ..._utils import copy_function_metadata
6
8
  from ..context import Context, DepsT
7
9
  from ..formatting import FormattableT
8
10
  from ..models import Model, use_model
@@ -12,6 +14,7 @@ from ..prompts import (
12
14
  ContextPrompt,
13
15
  Prompt,
14
16
  )
17
+ from ..prompts.prompts import BasePrompt
15
18
  from ..responses import (
16
19
  AsyncContextResponse,
17
20
  AsyncContextStreamResponse,
@@ -24,24 +27,35 @@ from ..responses import (
24
27
  )
25
28
  from ..types import P
26
29
 
27
- CallT = TypeVar("CallT", bound="BaseCall")
30
+ PromptT = TypeVar("PromptT", bound=BasePrompt[Callable[..., Any]])
31
+ CallT = TypeVar("CallT", bound="BaseCall[Any]")
28
32
 
29
33
 
30
- @dataclass
31
- class BaseCall:
34
+ @dataclass(kw_only=True)
35
+ class BaseCall(Generic[PromptT]):
32
36
  """Base class for all Call types with shared model functionality."""
33
37
 
34
38
  default_model: Model
35
39
  """The default model that will be used if no model is set in context."""
36
40
 
41
+ prompt: PromptT
42
+ """The underlying Prompt instance that generates messages with tools and format."""
43
+
44
+ __name__: str = field(init=False, repr=False, default="")
45
+ """The name of the underlying function (preserved for decorator stacking)."""
46
+
37
47
  @property
38
48
  def model(self) -> Model:
39
49
  """The model used for generating responses. May be overwritten via `with llm.model(...)`."""
40
50
  return use_model(self.default_model)
41
51
 
52
+ def __post_init__(self) -> None:
53
+ """Preserve standard function attributes for decorator stacking."""
54
+ copy_function_metadata(self, self.prompt.fn)
55
+
42
56
 
43
57
  @dataclass
44
- class Call(BaseCall, Generic[P, FormattableT]):
58
+ class Call(BaseCall[Prompt[P, FormattableT]], Generic[P, FormattableT]):
45
59
  """A call that directly generates LLM responses without requiring a model argument.
46
60
 
47
61
  Created by decorating a `MessageTemplate` with `llm.call`. The decorated function
@@ -53,9 +67,6 @@ class Call(BaseCall, Generic[P, FormattableT]):
53
67
  The model can be overridden at runtime using `with llm.model(...)` context manager.
54
68
  """
55
69
 
56
- prompt: Prompt[P, FormattableT]
57
- """The underlying Prompt instance that generates messages with tools and format."""
58
-
59
70
  @overload
60
71
  def __call__(
61
72
  self: "Call[P, None]", *args: P.args, **kwargs: P.kwargs
@@ -104,7 +115,7 @@ class Call(BaseCall, Generic[P, FormattableT]):
104
115
 
105
116
 
106
117
  @dataclass
107
- class AsyncCall(BaseCall, Generic[P, FormattableT]):
118
+ class AsyncCall(BaseCall[AsyncPrompt[P, FormattableT]], Generic[P, FormattableT]):
108
119
  """An async call that directly generates LLM responses without requiring a model argument.
109
120
 
110
121
  Created by decorating an async `MessageTemplate` with `llm.call`. The decorated async
@@ -116,9 +127,6 @@ class AsyncCall(BaseCall, Generic[P, FormattableT]):
116
127
  The model can be overridden at runtime using `with llm.model(...)` context manager.
117
128
  """
118
129
 
119
- prompt: AsyncPrompt[P, FormattableT]
120
- """The underlying AsyncPrompt instance that generates messages with tools and format."""
121
-
122
130
  @overload
123
131
  async def __call__(
124
132
  self: "AsyncCall[P, None]", *args: P.args, **kwargs: P.kwargs
@@ -169,7 +177,9 @@ class AsyncCall(BaseCall, Generic[P, FormattableT]):
169
177
 
170
178
 
171
179
  @dataclass
172
- class ContextCall(BaseCall, Generic[P, DepsT, FormattableT]):
180
+ class ContextCall(
181
+ BaseCall[ContextPrompt[P, DepsT, FormattableT]], Generic[P, DepsT, FormattableT]
182
+ ):
173
183
  """A context-aware call that directly generates LLM responses without requiring a model argument.
174
184
 
175
185
  Created by decorating a `ContextMessageTemplate` with `llm.call`. The decorated function
@@ -182,9 +192,6 @@ class ContextCall(BaseCall, Generic[P, DepsT, FormattableT]):
182
192
  The model can be overridden at runtime using `with llm.model(...)` context manager.
183
193
  """
184
194
 
185
- prompt: ContextPrompt[P, DepsT, FormattableT]
186
- """The underlying ContextPrompt instance that generates messages with tools and format."""
187
-
188
195
  @overload
189
196
  def __call__(
190
197
  self: "ContextCall[P, DepsT, None]",
@@ -255,7 +262,10 @@ class ContextCall(BaseCall, Generic[P, DepsT, FormattableT]):
255
262
 
256
263
 
257
264
  @dataclass
258
- class AsyncContextCall(BaseCall, Generic[P, DepsT, FormattableT]):
265
+ class AsyncContextCall(
266
+ BaseCall[AsyncContextPrompt[P, DepsT, FormattableT]],
267
+ Generic[P, DepsT, FormattableT],
268
+ ):
259
269
  """An async context-aware call that directly generates LLM responses without requiring a model argument.
260
270
 
261
271
  Created by decorating an async `ContextMessageTemplate` with `llm.call`. The decorated async
@@ -268,9 +278,6 @@ class AsyncContextCall(BaseCall, Generic[P, DepsT, FormattableT]):
268
278
  The model can be overridden at runtime using `with llm.model(...)` context manager.
269
279
  """
270
280
 
271
- prompt: AsyncContextPrompt[P, DepsT, FormattableT]
272
- """The underlying AsyncContextPrompt instance that generates messages with tools and format."""
273
-
274
281
  @overload
275
282
  async def __call__(
276
283
  self: "AsyncContextCall[P, DepsT, None]",
@@ -1,9 +1,10 @@
1
1
  """Concrete Prompt classes for generating messages with tools and formatting."""
2
2
 
3
- from collections.abc import Sequence
4
- from dataclasses import dataclass
5
- from typing import Generic, overload
3
+ from collections.abc import Callable, Sequence
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Generic, TypeVar, overload
6
6
 
7
+ from ..._utils import copy_function_metadata
7
8
  from ..context import Context, DepsT
8
9
  from ..formatting import Format, FormattableT, OutputParser
9
10
  from ..messages import Message, promote_to_messages
@@ -19,12 +20,7 @@ from ..responses import (
19
20
  Response,
20
21
  StreamResponse,
21
22
  )
22
- from ..tools import (
23
- AsyncContextToolkit,
24
- AsyncToolkit,
25
- ContextToolkit,
26
- Toolkit,
27
- )
23
+ from ..tools import AsyncContextToolkit, AsyncToolkit, ContextToolkit, Toolkit
28
24
  from ..types import P
29
25
  from .protocols import (
30
26
  AsyncContextMessageTemplate,
@@ -33,9 +29,26 @@ from .protocols import (
33
29
  MessageTemplate,
34
30
  )
35
31
 
32
+ FunctionT = TypeVar("FunctionT", bound=Callable[..., Any])
33
+
34
+
35
+ @dataclass(kw_only=True)
36
+ class BasePrompt(Generic[FunctionT]):
37
+ """Base class for all Prompt types with shared metadata functionality."""
38
+
39
+ fn: FunctionT
40
+ """The underlying prompt function that generates message content."""
41
+
42
+ __name__: str = field(init=False, repr=False, default="")
43
+ """The name of the underlying function (preserved for decorator stacking)."""
44
+
45
+ def __post_init__(self) -> None:
46
+ """Preserve standard function attributes for decorator stacking."""
47
+ copy_function_metadata(self, self.fn)
48
+
36
49
 
37
50
  @dataclass
38
- class Prompt(Generic[P, FormattableT]):
51
+ class Prompt(BasePrompt[MessageTemplate[P]], Generic[P, FormattableT]):
39
52
  """A prompt that can be called with a model to generate a response.
40
53
 
41
54
  Created by decorating a `MessageTemplate` with `llm.prompt`. The decorated
@@ -45,9 +58,6 @@ class Prompt(Generic[P, FormattableT]):
45
58
  It can be invoked with a model: `prompt(model, *args, **kwargs)`.
46
59
  """
47
60
 
48
- fn: MessageTemplate[P]
49
- """The underlying prompt function that generates message content."""
50
-
51
61
  toolkit: Toolkit
52
62
  """The toolkit containing this prompt's tools."""
53
63
 
@@ -134,7 +144,7 @@ class Prompt(Generic[P, FormattableT]):
134
144
 
135
145
 
136
146
  @dataclass
137
- class AsyncPrompt(Generic[P, FormattableT]):
147
+ class AsyncPrompt(BasePrompt[AsyncMessageTemplate[P]], Generic[P, FormattableT]):
138
148
  """An async prompt that can be called with a model to generate a response.
139
149
 
140
150
  Created by decorating an async `MessageTemplate` with `llm.prompt`. The decorated
@@ -144,9 +154,6 @@ class AsyncPrompt(Generic[P, FormattableT]):
144
154
  It can be invoked with a model: `await prompt(model, *args, **kwargs)`.
145
155
  """
146
156
 
147
- fn: AsyncMessageTemplate[P]
148
- """The underlying async prompt function that generates message content."""
149
-
150
157
  toolkit: AsyncToolkit
151
158
  """The toolkit containing this prompt's async tools."""
152
159
 
@@ -235,7 +242,9 @@ class AsyncPrompt(Generic[P, FormattableT]):
235
242
 
236
243
 
237
244
  @dataclass
238
- class ContextPrompt(Generic[P, DepsT, FormattableT]):
245
+ class ContextPrompt(
246
+ BasePrompt[ContextMessageTemplate[P, DepsT]], Generic[P, DepsT, FormattableT]
247
+ ):
239
248
  """A context-aware prompt that can be called with a model to generate a response.
240
249
 
241
250
  Created by decorating a `ContextMessageTemplate` with `llm.prompt`. The decorated
@@ -246,9 +255,6 @@ class ContextPrompt(Generic[P, DepsT, FormattableT]):
246
255
  It can be invoked with a model: `prompt(model, ctx, *args, **kwargs)`.
247
256
  """
248
257
 
249
- fn: ContextMessageTemplate[P, DepsT]
250
- """The underlying context-aware prompt function that generates message content."""
251
-
252
258
  toolkit: ContextToolkit[DepsT]
253
259
  """The toolkit containing this prompt's context-aware tools."""
254
260
 
@@ -361,7 +367,9 @@ class ContextPrompt(Generic[P, DepsT, FormattableT]):
361
367
 
362
368
 
363
369
  @dataclass
364
- class AsyncContextPrompt(Generic[P, DepsT, FormattableT]):
370
+ class AsyncContextPrompt(
371
+ BasePrompt[AsyncContextMessageTemplate[P, DepsT]], Generic[P, DepsT, FormattableT]
372
+ ):
365
373
  """An async context-aware prompt that can be called with a model to generate a response.
366
374
 
367
375
  Created by decorating an async `ContextMessageTemplate` with `llm.prompt`. The decorated
@@ -372,9 +380,6 @@ class AsyncContextPrompt(Generic[P, DepsT, FormattableT]):
372
380
  It can be invoked with a model: `await prompt(model, ctx, *args, **kwargs)`.
373
381
  """
374
382
 
375
- fn: AsyncContextMessageTemplate[P, DepsT]
376
- """The underlying async context-aware prompt function that generates message content."""
377
-
378
383
  toolkit: AsyncContextToolkit[DepsT]
379
384
  """The toolkit containing this prompt's async context-aware tools."""
380
385
 
@@ -22,6 +22,7 @@ from docstring_parser import parse
22
22
  from pydantic import BaseModel, Field, create_model
23
23
  from pydantic.fields import FieldInfo
24
24
 
25
+ from ..._utils import copy_function_metadata
25
26
  from ..content import ToolCall
26
27
  from ..types import Jsonable
27
28
  from .protocols import AsyncContextToolFn, AsyncToolFn, ContextToolFn, ToolFn
@@ -159,11 +160,14 @@ class ToolSchema(Generic[ToolFnT]):
159
160
 
160
161
  strict: bool | None
161
162
  """Whether the tool should use strict mode when supported by the model.
162
-
163
- If set to None, will use the provider's default setting (usually as strict as
163
+
164
+ If set to None, will use the provider's default setting (usually as strict as
164
165
  possible).
165
166
  """
166
167
 
168
+ __name__: str
169
+ """The name of the underlying function (preserved for decorator stacking)."""
170
+
167
171
  def __hash__(self) -> int:
168
172
  if not hasattr(self, "_hash"):
169
173
  self._hash = hash(
@@ -200,6 +204,7 @@ class ToolSchema(Generic[ToolFnT]):
200
204
  self.description = description
201
205
  self.parameters = parameters
202
206
  self.strict = strict
207
+ copy_function_metadata(self, fn)
203
208
 
204
209
  @classmethod
205
210
  def from_function(
@@ -738,12 +738,15 @@ class _DependencyCollector:
738
738
  # For Python 3.13+
739
739
  return definition.func # pyright: ignore[reportFunctionMemberAccess] # pragma: no cover
740
740
 
741
+ # Handle objects with .fn but no __qualname__ (e.g., old-style wrappers).
742
+ # With copy_function_metadata() now copying __qualname__ to ToolSchema, Prompt,
743
+ # Call, etc., this branch is no longer reached in normal usage.
741
744
  if (
742
745
  (wrapped_function := getattr(definition, "fn", None)) is not None
743
746
  and not hasattr(definition, "__qualname__")
744
747
  and callable(wrapped_function)
745
748
  ):
746
- return wrapped_function
749
+ return wrapped_function # pragma: no cover
747
750
 
748
751
  return definition
749
752
 
@@ -20,14 +20,17 @@ from ....api._generated.traces.types import (
20
20
  TracesCreateRequestResourceSpansItemResource,
21
21
  TracesCreateRequestResourceSpansItemResourceAttributesItem,
22
22
  TracesCreateRequestResourceSpansItemResourceAttributesItemValue,
23
+ TracesCreateRequestResourceSpansItemResourceAttributesItemValueArrayValue,
23
24
  TracesCreateRequestResourceSpansItemScopeSpansItem,
24
25
  TracesCreateRequestResourceSpansItemScopeSpansItemScope,
25
26
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem,
26
27
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItem,
27
28
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue,
29
+ TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValueArrayValue,
28
30
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemStatus,
29
31
  )
30
32
  from ....api.client import Mirascope
33
+ from .utils import to_otlp_any_value
31
34
 
32
35
  logger = logging.getLogger(__name__)
33
36
 
@@ -282,17 +285,7 @@ class MirascopeOTLPExporter(SpanExporter):
282
285
  def _convert_attribute_value(
283
286
  self, value: AttributeValue
284
287
  ) -> TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue:
285
- """Convert OpenTelemetry AttributeValue to Mirascope API's KeyValueValue.
286
-
287
- This conversion is necessary because the Fern-generated API client
288
- expects KeyValueValue objects, not OpenTelemetry's AttributeValue types.
289
-
290
- Args:
291
- value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
292
-
293
- Returns:
294
- A KeyValueValue object for the Mirascope API
295
- """
288
+ """Convert OpenTelemetry AttributeValue to Mirascope API's KeyValueValue."""
296
289
  match value:
297
290
  case str():
298
291
  return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue(
@@ -312,49 +305,21 @@ class MirascopeOTLPExporter(SpanExporter):
312
305
  )
313
306
  case _:
314
307
  return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue(
315
- string_value=str(list(value))
308
+ array_value=TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValueArrayValue(
309
+ values=[to_otlp_any_value(v) for v in value]
310
+ )
316
311
  )
317
312
 
318
313
  def _convert_event_attribute_value(
319
314
  self, value: AttributeValue
320
315
  ) -> dict[str, object]:
321
- """Convert OpenTelemetry AttributeValue to OTLP event attribute value format.
322
-
323
- This uses the OTLP JSON format with typed value wrappers (e.g., stringValue, intValue).
324
-
325
- Args:
326
- value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
327
-
328
- Returns:
329
- A dict with the typed value (e.g., {"stringValue": "..."})
330
- """
331
- match value:
332
- case str():
333
- return {"stringValue": value}
334
- case bool():
335
- return {"boolValue": value}
336
- case int():
337
- return {"intValue": str(value)}
338
- case float():
339
- return {"doubleValue": value}
340
- case _:
341
- # Sequences - convert to string representation
342
- return {"stringValue": str(list(value))}
316
+ """Convert OpenTelemetry AttributeValue to OTLP event attribute value format."""
317
+ return to_otlp_any_value(value)
343
318
 
344
319
  def _convert_resource_attribute_value(
345
320
  self, value: AttributeValue
346
321
  ) -> TracesCreateRequestResourceSpansItemResourceAttributesItemValue:
347
- """Convert OpenTelemetry AttributeValue to Mirascope API's resource KeyValueValue.
348
-
349
- This conversion is necessary because the Fern-generated API client
350
- expects KeyValueValue objects, not OpenTelemetry's AttributeValue types.
351
-
352
- Args:
353
- value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
354
-
355
- Returns:
356
- A KeyValueValue object for the Mirascope API resource attributes
357
- """
322
+ """Convert OpenTelemetry AttributeValue to Mirascope API's resource KeyValueValue."""
358
323
  match value:
359
324
  case str():
360
325
  return TracesCreateRequestResourceSpansItemResourceAttributesItemValue(
@@ -374,7 +339,9 @@ class MirascopeOTLPExporter(SpanExporter):
374
339
  )
375
340
  case _:
376
341
  return TracesCreateRequestResourceSpansItemResourceAttributesItemValue(
377
- string_value=str(list(value))
342
+ array_value=TracesCreateRequestResourceSpansItemResourceAttributesItemValueArrayValue(
343
+ values=[to_otlp_any_value(v) for v in value]
344
+ )
378
345
  )
379
346
 
380
347
  def shutdown(self) -> None:
@@ -4,6 +4,43 @@ This module provides helper functions for formatting and converting
4
4
  OpenTelemetry data types for export.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping, Sequence
10
+
11
+ from opentelemetry.util.types import AttributeValue
12
+
13
+
14
+ def to_otlp_any_value(value: AttributeValue) -> dict[str, object]:
15
+ """Convert AttributeValue to OTLP AnyValue (dict form).
16
+
17
+ - string/bool/int/float are converted to stringValue/boolValue/intValue/doubleValue
18
+ - Sequence (excluding str/bytes/Mapping) is converted to arrayValue.values
19
+ - Unsupported types fallback to stringValue=str(value)
20
+
21
+ Args:
22
+ value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
23
+
24
+ Returns:
25
+ A dict representing OTLP AnyValue (e.g., {"stringValue": "..."})
26
+ """
27
+ match value:
28
+ case str():
29
+ return {"stringValue": value}
30
+ case bool():
31
+ return {"boolValue": value}
32
+ case int():
33
+ return {"intValue": str(value)}
34
+ case float():
35
+ return {"doubleValue": value}
36
+ case _ if isinstance(value, bytes | bytearray | memoryview | Mapping):
37
+ return {"stringValue": str(value)}
38
+ case _ if isinstance(value, Sequence):
39
+ values = [to_otlp_any_value(v) for v in value]
40
+ return {"arrayValue": {"values": values}}
41
+ case _:
42
+ return {"stringValue": str(value)}
43
+
7
44
 
8
45
  def format_trace_id(trace_id: int) -> str:
9
46
  """Format a trace ID as a 32-character hex string.
@@ -6,6 +6,7 @@ from dataclasses import dataclass, field
6
6
  from typing import Any, Generic, TypeVar
7
7
  from typing_extensions import TypeIs
8
8
 
9
+ from ..._utils import copy_function_metadata
9
10
  from ...llm.calls import AsyncCall, AsyncContextCall, Call, ContextCall
10
11
  from ...llm.context import Context, DepsT
11
12
  from ...llm.formatting import FormattableT
@@ -35,6 +36,7 @@ from .traced_functions import (
35
36
  TracedContextFunction,
36
37
  TracedFunction,
37
38
  )
39
+ from .utils import get_original_fn
38
40
 
39
41
  CallT = TypeVar(
40
42
  "CallT",
@@ -106,6 +108,14 @@ class _BaseTracedCall(Generic[CallT]):
106
108
  metadata: dict[str, str] = field(default_factory=dict)
107
109
  """Arbitrary key-value pairs for additional metadata."""
108
110
 
111
+ __name__: str = field(init=False, repr=False, default="")
112
+ """The name of the underlying function (preserved for decorator stacking)."""
113
+
114
+ def __post_init__(self) -> None:
115
+ """Preserve standard function attributes for decorator stacking."""
116
+ original_fn = get_original_fn(self._call.prompt.fn)
117
+ copy_function_metadata(self, original_fn)
118
+
109
119
 
110
120
  @dataclass(kw_only=True)
111
121
  class TracedCall(_BaseTracedCall[Call[P, FormattableT]]):
@@ -153,6 +163,7 @@ class TracedCall(_BaseTracedCall[Call[P, FormattableT]]):
153
163
 
154
164
  def __post_init__(self) -> None:
155
165
  """Initialize TracedFunction wrappers for call and stream methods."""
166
+ super().__post_init__()
156
167
  self.call = TracedFunction(
157
168
  fn=self._call.call, tags=self.tags, metadata=self.metadata
158
169
  )
@@ -213,6 +224,7 @@ class TracedAsyncCall(_BaseTracedCall[AsyncCall[P, FormattableT]]):
213
224
 
214
225
  def __post_init__(self) -> None:
215
226
  """Initialize AsyncTracedFunction wrappers for call and stream methods."""
227
+ super().__post_init__()
216
228
  self.call = AsyncTracedFunction(
217
229
  fn=self._call.call, tags=self.tags, metadata=self.metadata
218
230
  )
@@ -276,6 +288,7 @@ class TracedContextCall(_BaseTracedCall[ContextCall[P, DepsT, FormattableT]]):
276
288
 
277
289
  def __post_init__(self) -> None:
278
290
  """Initialize TracedContextFunction wrappers for call and stream methods."""
291
+ super().__post_init__()
279
292
  self.call = TracedContextFunction(
280
293
  fn=self._call.call, tags=self.tags, metadata=self.metadata
281
294
  )
@@ -344,6 +357,7 @@ class TracedAsyncContextCall(_BaseTracedCall[AsyncContextCall[P, DepsT, Formatta
344
357
 
345
358
  def __post_init__(self) -> None:
346
359
  """Initialize AsyncTracedContextFunction wrappers for call and stream methods."""
360
+ super().__post_init__()
347
361
  self.call = AsyncTracedContextFunction(
348
362
  fn=self._call.call, tags=self.tags, metadata=self.metadata
349
363
  )
@@ -10,6 +10,7 @@ from typing import Any, Generic, Literal, TypeVar
10
10
 
11
11
  from opentelemetry.util.types import AttributeValue
12
12
 
13
+ from ..._utils import copy_function_metadata
13
14
  from ...api.client import get_async_client, get_sync_client
14
15
  from ...llm.context import Context, DepsT
15
16
  from ...llm.responses.root_response import RootResponse
@@ -175,11 +176,15 @@ class _BaseFunction(Generic[P, R, FunctionT], ABC):
175
176
  _is_async: bool = field(init=False)
176
177
  """Whether the wrapped function is asynchronous."""
177
178
 
179
+ __name__: str = field(init=False, repr=False, default="")
180
+ """The name of the underlying function (preserved for decorator stacking)."""
181
+
178
182
  def __post_init__(self) -> None:
179
183
  """Initialize additional attributes after dataclass init."""
180
184
  self._qualified_name = get_qualified_name(self.fn)
181
185
  original_fn = get_original_fn(self.fn)
182
186
  self._module_name = getattr(original_fn, "__module__", "")
187
+ copy_function_metadata(self, original_fn)
183
188
 
184
189
 
185
190
  @dataclass(kw_only=True)
@@ -200,7 +205,7 @@ class _BaseTracedFunction(_BaseFunction[P, R, FunctionT]):
200
205
  "mirascope.trace.arg_values": json_dumps(arg_values),
201
206
  }
202
207
  if self.tags:
203
- attributes["mirascope.trace.tags"] = self.tags
208
+ attributes["mirascope.trace.tags"] = list(self.tags)
204
209
  if self.metadata:
205
210
  attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
206
211
  span.set(**attributes)
@@ -314,7 +319,7 @@ class _BaseTracedContextFunction(
314
319
  "mirascope.trace.arg_values": json_dumps(arg_values),
315
320
  }
316
321
  if self.tags:
317
- attributes["mirascope.trace.tags"] = self.tags
322
+ attributes["mirascope.trace.tags"] = list(self.tags)
318
323
  if self.metadata:
319
324
  attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
320
325
  span.set(**attributes)
@@ -33,15 +33,23 @@ def _is_call_method(fn: Callable[..., Any]) -> bool:
33
33
 
34
34
 
35
35
  def get_original_fn(fn: Callable[..., Any]) -> Callable[..., Any]:
36
- """Get the original function from a Call method or return fn as-is.
36
+ """Get the original unwrapped function.
37
37
 
38
- When fn is a bound method of a Call object (e.g., Call.call or Call.stream),
39
- returns the original decorated function from prompt.fn. Otherwise returns fn.
38
+ Follows the __wrapped__ chain set by copy_function_metadata() to find the
39
+ original function. Falls back to checking for Call methods for bound methods.
40
40
  """
41
+ # Follow __wrapped__ chain if available (set by copy_function_metadata)
42
+ # In practice, get_original_fn is called with original functions or bound methods,
43
+ # so this loop is defensive code for edge cases like @functools.wraps chains.
44
+ while hasattr(fn, "__wrapped__"):
45
+ fn = fn.__wrapped__ # pyright: ignore[reportFunctionMemberAccess] # pragma: no cover
46
+
47
+ # Handle bound methods of Call objects (e.g., Call.call or Call.stream)
41
48
  if _is_call_method(fn):
42
49
  prompt = fn.__self__.prompt # pyright: ignore[reportFunctionMemberAccess]
43
50
  if hasattr(prompt, "fn"):
44
- return prompt.fn
51
+ return get_original_fn(prompt.fn)
52
+
45
53
  return fn
46
54
 
47
55
 
@@ -196,7 +196,7 @@ class _BaseVersionedFunction(_BaseTracedFunction[P, R, Any]):
196
196
  if self.name:
197
197
  span.set(**{"mirascope.version.name": self.name})
198
198
  if self.tags:
199
- span.set(**{"mirascope.version.tags": self.tags})
199
+ span.set(**{"mirascope.version.tags": list(self.tags)})
200
200
  if self.metadata:
201
201
  for key, value in self.metadata.items():
202
202
  span.set(**{f"mirascope.version.meta.{key}": value})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mirascope
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Every frontier LLM. One unified interface.
5
5
  Project-URL: Homepage, https://mirascope.com
6
6
  Project-URL: Documentation, https://mirascope.com/docs/mirascope/v2
@@ -1,5 +1,6 @@
1
1
  mirascope/__init__.py,sha256=V3T7JlZJ4G9dA5lmq5eTtLYUajbQmTvT3-tUlv8o-fg,265
2
2
  mirascope/_stubs.py,sha256=iq5hxVYdLEZ_b2fyBNeY5HXcL50Dcw22HBaBr3ydPgU,12179
3
+ mirascope/_utils.py,sha256=hSwsMKURXrje8Dpl2-KhEBLWJrudWKqExbemKwozRyE,1036
3
4
  mirascope/api/__init__.py,sha256=FL0OpTweJC0SUpOnZJddDzeC0JwxPDkIaL78VpeMtko,498
4
5
  mirascope/api/client.py,sha256=3JrrC2zNlEttAUXHGN1kbDpGczyI9zCNiSZ3dEyqtAI,8253
5
6
  mirascope/api/settings.py,sha256=N9mqpEpx7dupszKYK9li8vY-iDq2FFvlus0fK-gYPu0,2639
@@ -7,7 +8,7 @@ mirascope/api/_generated/README.md,sha256=7-B_qhXCHaXKX9FPG-G112hKmXM64uM6eNqmSd
7
8
  mirascope/api/_generated/__init__.py,sha256=ZIX_SjuD8PIB3gifL9sDgEt8zqfEFTv2IvjK-_GzOsw,16800
8
9
  mirascope/api/_generated/client.py,sha256=Xh_VGnnbdZaSUpWzjGWu-1lwoZ_6isz9eU-BATjo_vs,8775
9
10
  mirascope/api/_generated/environment.py,sha256=eKhvb5ZF3qzJj2OtEI7E9STAquQ1VivYLdmHRc4hFV4,262
10
- mirascope/api/_generated/reference.md,sha256=Zb8SXjz4ZRLdaGdWGzCldyCwykWkmyotNK2qD3XJX9A,53535
11
+ mirascope/api/_generated/reference.md,sha256=I_GJI0SONEQYPuVAlss8lzcJpUx2Mjra9kzYREppwpg,53563
11
12
  mirascope/api/_generated/annotations/__init__.py,sha256=z1rsEyNXWcegCIeC-Y2P9TBI0fVbNs1FohqSJiqrK84,987
12
13
  mirascope/api/_generated/annotations/client.py,sha256=rikFtCmou4D4hSZqtaxFnIc7ZKMt86XztUyh4pMcXII,13818
13
14
  mirascope/api/_generated/annotations/raw_client.py,sha256=EEC2Rr5KT_WmLn9aFgQxApnxHGl3MdOOcoYAti4syhE,54205
@@ -96,8 +97,8 @@ mirascope/api/_generated/health/types/__init__.py,sha256=dYIZPgww1FuKOKJTeUWMDad
96
97
  mirascope/api/_generated/health/types/health_check_response.py,sha256=OCmNPA0BN9mfNoQKzl1aS2uFwzG60su540pZtC8gFJI,659
97
98
  mirascope/api/_generated/health/types/health_check_response_status.py,sha256=eCSX3a4cA5zETaIbLvtYkSeS3585DuJa9VnM1fw3VxE,155
98
99
  mirascope/api/_generated/organization_invitations/__init__.py,sha256=4LHOmmo5wJRLPqyiZ3jVG_VW3LK-VQVbFUfTwweMbcw,1247
99
- mirascope/api/_generated/organization_invitations/client.py,sha256=4vpnNlGWnIJ4L8PAkUxn5iU4dTv_qCIP-gMZ6Ap2rCk,13992
100
- mirascope/api/_generated/organization_invitations/raw_client.py,sha256=MQ1AopdvO0xGMuxTnd-2nS0ytalLW2IFp2D9PUysUfI,58289
100
+ mirascope/api/_generated/organization_invitations/client.py,sha256=EHGrekNKf4uhupP9IffP20SGBKtYJotyv34Tjs3haqY,14048
101
+ mirascope/api/_generated/organization_invitations/raw_client.py,sha256=6Or-I-vkvnx2LHC3G50oCExFAfWdRJIaSNP6OtJuP3U,58345
101
102
  mirascope/api/_generated/organization_invitations/types/__init__.py,sha256=eoOMa4pr1aks2d79z2YXDV8JsPs2giYVC-KVLxFWmos,1956
102
103
  mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py,sha256=pbvwLUaCkCQ5fuPKlh2OR0U5JINptYKHL4E_g9JusmA,1129
103
104
  mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py,sha256=s7pe7kJSJ_QZuvK1GNmD3v8Kwyy8GhDPrE7wokylXbw,199
@@ -265,7 +266,7 @@ mirascope/api/_generated/types/unauthorized_error_tag.py,sha256=sx-QdNmQjboMaPni
265
266
  mirascope/llm/__init__.py,sha256=1hP54tE5tO6lyrgiZHafc8JbF3LOIKrPON9UQJPo9uw,5879
266
267
  mirascope/llm/exceptions.py,sha256=hYc8A8A6unqPaB9GmvdlWIEUso4NWlR7lXELEVVyGFc,10839
267
268
  mirascope/llm/calls/__init__.py,sha256=aA-IoOlXdmhVybHTj_fGx1jDlIL-a7ed7xncO2aMIJY,293
268
- mirascope/llm/calls/calls.py,sha256=K4lLbAgfpvuNyYfh8UYm37onUr8QcEm4BMK_1CBr7i4,11836
269
+ mirascope/llm/calls/calls.py,sha256=g71njIpo5hWvNCnSsDeT8c3l-IaAes01iKrwMyPIYlY,12089
269
270
  mirascope/llm/calls/decorator.py,sha256=hMJscJ1cPnQctGZfTqJV6hoCScYherCutOecIcpJfbk,9006
270
271
  mirascope/llm/content/__init__.py,sha256=4a5UaoiF8OKfXS9j7INFvOnU7bkNpacpiZ6IlLgC6Lc,1926
271
272
  mirascope/llm/content/audio.py,sha256=W6liY1PuQ0pF91eDBnNKW52dr-G7-vnd-rhlSqebx9Y,5311
@@ -297,7 +298,7 @@ mirascope/llm/models/thinking_config.py,sha256=CkFOkw-fjDlsZbHJbV68v4yRwb5o6miVx
297
298
  mirascope/llm/prompts/__init__.py,sha256=rOtSiZvw6vsJl7vCFWzKZnm3haNhnLtPRzPfdbgoH7U,727
298
299
  mirascope/llm/prompts/_utils.py,sha256=42hTqm8PuLpDOz4Mi59kaUou43qeo5R-9703l8cciSg,1018
299
300
  mirascope/llm/prompts/decorator.py,sha256=UiCA921u1qCQZPqrDRE4xERg7nzyzf1rChAEN216ojA,7179
300
- mirascope/llm/prompts/prompts.py,sha256=WZUERWWVIvJq6LGMl6ixSGFoAayVwdVjm16CLEUGyew,15926
301
+ mirascope/llm/prompts/prompts.py,sha256=Yb4ijwZCokkPkBv4rWfH22qNzG8gm9ueuIjIMYrwpww,16277
301
302
  mirascope/llm/prompts/protocols.py,sha256=auopJnLAfOognCFVVnXrvb4zFj3102Ofi2iVHfublM0,2242
302
303
  mirascope/llm/providers/__init__.py,sha256=JP1cW_IFUZwkcHWP2Kc9ezihfQu7CY80ayqmkVeR9pc,1953
303
304
  mirascope/llm/providers/model_id.py,sha256=qr7RKkP_3Blgxjzw_HvQVJLM1aTzsP-jVFsycz0itI8,292
@@ -373,7 +374,7 @@ mirascope/llm/tools/__init__.py,sha256=P9NX6pP5fKScTAuR9K3fODdW17wQ-6fcArVx2klGi
373
374
  mirascope/llm/tools/_utils.py,sha256=mUi6vUEwYuwu8W9FCQcQ_9h_VE_U-Y3Y2UQ2YptPZL4,1171
374
375
  mirascope/llm/tools/decorator.py,sha256=2ZTvYH0jrft0E3BdQ2BB9Okyb72JOIqQvBkLWmWuo7w,5663
375
376
  mirascope/llm/tools/protocols.py,sha256=pUgGuh4TilRuoipeEHfTzu5cXacMnUpz0Q5YXQyAY2I,3050
376
- mirascope/llm/tools/tool_schema.py,sha256=2wVqbXx2B12dE1u_By9Vlbrjz0R_s8pmE6M1Av4GrAE,10291
377
+ mirascope/llm/tools/tool_schema.py,sha256=1fwEFZpxfFyXKFrJmsj0A1W6ptaMHaJQRUc0Yz0GEZk,10473
377
378
  mirascope/llm/tools/toolkit.py,sha256=YFBEUWEeFRsujYjhsBvD7Hu3-tlvQP3sihp_c65ZVHE,5284
378
379
  mirascope/llm/tools/tools.py,sha256=CaSGvjFnj-A8KIpInc7m5VNj7wY-eW25f4fvAZeDwWQ,9844
379
380
  mirascope/llm/types/__init__.py,sha256=lqzi1FkZ-s-D9-KQzVkAHuQQ1zp6B6yM3r9UNo46teE,357
@@ -383,26 +384,26 @@ mirascope/llm/types/type_vars.py,sha256=OsAcQAZh5T_X8ZTLlP4GC1x3qgVY9rfYSnME8aMT
383
384
  mirascope/ops/__init__.py,sha256=tS2HIXbbKo9VY2LPngNaAtel9aNQblt2Tz3scyYUmyw,2617
384
385
  mirascope/ops/exceptions.py,sha256=N5AES285tTxFJ1THIeJht-_ZEQ7-7_FWoes4EBAu3B8,641
385
386
  mirascope/ops/_internal/__init__.py,sha256=yCf8bnHiQFWYsBkwBPyMqknI5apmOwlmyf105mH9EAE,173
386
- mirascope/ops/_internal/closure.py,sha256=lPkZLfL33OZ3yxdZhT3RXqhD0ychKWH0zevU4V-VHCY,42465
387
+ mirascope/ops/_internal/closure.py,sha256=lZuT71ufwHd987BWb7mdk88_d0_eO18fNs9sS16zLss,42727
387
388
  mirascope/ops/_internal/configuration.py,sha256=kWgqLM6XOJ9WK_7y7R3ULQ0UE7r9dgxxMIfg4YRZ0c4,5228
388
389
  mirascope/ops/_internal/context.py,sha256=G0Jiv6XfWjeDyRrh5-Wg6I0UlqiDj17SNMhtQ6Rw3lM,2522
389
390
  mirascope/ops/_internal/propagation.py,sha256=2in7uttv2b5MBoe3Ol1mgn6N20TqhH0sErGak7hMUDY,7032
390
391
  mirascope/ops/_internal/protocols.py,sha256=72K-AIHpDsflo58cig_jO1_xdAwuqZaQBU96G4sGe_I,4229
391
392
  mirascope/ops/_internal/session.py,sha256=-7r2TbJiAKeEQ0YaJ9X_2BlumLK7x6h9wP6f8ei-5GA,4105
392
393
  mirascope/ops/_internal/spans.py,sha256=Mx4x2sSYYIhibcHKr99TTboUdGrSPV09NpftJD5kkBE,7675
393
- mirascope/ops/_internal/traced_calls.py,sha256=nHz2NgG9IHyhDKozKp7m252lDetdSaGn-tYhtZSO9LM,13000
394
- mirascope/ops/_internal/traced_functions.py,sha256=dAMx6BfBs6phc8LgdbsBZFUCN7el-sRMhK6yBYm6zVw,18884
394
+ mirascope/ops/_internal/traced_calls.py,sha256=L0LG1SWW7DNYnvqajPoD1ptPi8IUM9JssYBqHo7rp0E,13577
395
+ mirascope/ops/_internal/traced_functions.py,sha256=lPN8c-lzHY-BxhhbcDpKC7JQtvgnmYMDDpx7M46VP-8,19136
395
396
  mirascope/ops/_internal/tracing.py,sha256=RzHBc-yEbj3Z_Kjobh26FWDSnF6AGbRCsb0JXBSzPdc,10244
396
397
  mirascope/ops/_internal/types.py,sha256=5B1RgChTEbGkLZvdoX8XA2vMKn4bS8BczjGDElxmugI,412
397
- mirascope/ops/_internal/utils.py,sha256=qxr0ysyrBZP7ov9pqAirtDjftprMswwq3szbbVqjabk,4466
398
+ mirascope/ops/_internal/utils.py,sha256=uGGKuAToxUUlkiqcmZIAICCAtmQay_fQ_ciVWF6jcIQ,4907
398
399
  mirascope/ops/_internal/versioned_calls.py,sha256=7oePDEuf98ph6yrHKJmXSagc1mGz5sdTTJgQnB2CyHM,18445
399
- mirascope/ops/_internal/versioned_functions.py,sha256=lX0jgOPdtaajFM8kEkkEBRbGCrLuTo65EApnYCSDo_U,13522
400
+ mirascope/ops/_internal/versioned_functions.py,sha256=g_m-szx6NiBmwxakygNDnJTOyOQN-cVEgsUZZXLFh0w,13528
400
401
  mirascope/ops/_internal/versioning.py,sha256=MxiezWrGUPiLZxZnwwXDFTR7EMrfTHR7rmd14TuDCL8,8286
401
402
  mirascope/ops/_internal/exporters/__init__.py,sha256=3PUd75TczW_8bUImcWgddWyoRmygGf6T_jL7Jo3t_sY,608
402
- mirascope/ops/_internal/exporters/exporters.py,sha256=nRyDXxf0Mgy0D5sni60tdi8Hgfivj2eLO1dWwtaydRI,14932
403
+ mirascope/ops/_internal/exporters/exporters.py,sha256=9RUKKgtKCxrhh-9H0eaYdG_LApmirmvRZjNIlhr3tGA,14043
403
404
  mirascope/ops/_internal/exporters/processors.py,sha256=hkNwA5m0e_KJoPQf_EPUaK6ItZlJ8D5TCBs-JAhGKXo,3511
404
405
  mirascope/ops/_internal/exporters/types.py,sha256=1ge7nwNQ3HUH4qVoYeHhSUp3LxohkvDSoqPw2MDLEqw,4057
405
- mirascope/ops/_internal/exporters/utils.py,sha256=zDZihsj4BxLZH3o2RKCofCvSUf4Nbo8Yro2dqkm0rI4,727
406
+ mirascope/ops/_internal/exporters/utils.py,sha256=1EwxK70bKrB5fkm9y3MnXduSF-NKJqRbBDgqD1T0fnc,2026
406
407
  mirascope/ops/_internal/instrumentation/__init__.py,sha256=Fz2WUX_vFvLqHbMxkuVQAGRXyWkk9Oqug8DcA-oa4p8,180
407
408
  mirascope/ops/_internal/instrumentation/llm/__init__.py,sha256=_L6JL3sw6XbW9ICsWe4RZpkT-_BFSIBbh_n00O9h_SM,184
408
409
  mirascope/ops/_internal/instrumentation/llm/common.py,sha256=bb4rHjnQyAs55Cf_qy0OupEuzxvskpOTvRBGEJyByfY,17637
@@ -417,7 +418,7 @@ mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.p
417
418
  mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py,sha256=qxLxSmwd_2EdymR4a5cDXc9DTSFGQXMhqSHGgS219Y4,957
418
419
  mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py,sha256=ZkWNOh6-tG6LtFP_0Hjfdf_8BqAsIFyP3tNybeo1FnY,388
419
420
  mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py,sha256=JpmcF1i25EHc8KMv6nkpBwyIU46B_T8v1L71C16n080,3004
420
- mirascope-2.0.1.dist-info/METADATA,sha256=C9jPBWHGrM_vyZG6XA69yUmOwOz9uDb2UMq_jpC3yvI,9019
421
- mirascope-2.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
422
- mirascope-2.0.1.dist-info/licenses/LICENSE,sha256=MxD-HQ4YHSi2YqZ3UH2MsjclZbesds6j5it4O2IWyJs,1072
423
- mirascope-2.0.1.dist-info/RECORD,,
421
+ mirascope-2.0.2.dist-info/METADATA,sha256=G9TopFOv0bxICfwTZO2IY5Nk3pxz06oUC24kTOaL-yo,9019
422
+ mirascope-2.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
423
+ mirascope-2.0.2.dist-info/licenses/LICENSE,sha256=MxD-HQ4YHSi2YqZ3UH2MsjclZbesds6j5it4O2IWyJs,1072
424
+ mirascope-2.0.2.dist-info/RECORD,,