wandb 0.19.8__py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.19.10__py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.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 (154) hide show
  1. wandb/__init__.py +5 -1
  2. wandb/__init__.pyi +15 -8
  3. wandb/_pydantic/__init__.py +30 -0
  4. wandb/_pydantic/base.py +148 -0
  5. wandb/_pydantic/utils.py +66 -0
  6. wandb/_pydantic/v1_compat.py +284 -0
  7. wandb/apis/paginator.py +82 -38
  8. wandb/apis/public/__init__.py +2 -2
  9. wandb/apis/public/api.py +111 -53
  10. wandb/apis/public/artifacts.py +387 -639
  11. wandb/apis/public/automations.py +69 -0
  12. wandb/apis/public/files.py +2 -2
  13. wandb/apis/public/integrations.py +168 -0
  14. wandb/apis/public/projects.py +32 -2
  15. wandb/apis/public/reports.py +2 -2
  16. wandb/apis/public/runs.py +19 -11
  17. wandb/apis/public/utils.py +107 -1
  18. wandb/automations/__init__.py +81 -0
  19. wandb/automations/_filters/__init__.py +40 -0
  20. wandb/automations/_filters/expressions.py +179 -0
  21. wandb/automations/_filters/operators.py +267 -0
  22. wandb/automations/_filters/run_metrics.py +183 -0
  23. wandb/automations/_generated/__init__.py +184 -0
  24. wandb/automations/_generated/create_filter_trigger.py +21 -0
  25. wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
  26. wandb/automations/_generated/delete_trigger.py +19 -0
  27. wandb/automations/_generated/enums.py +33 -0
  28. wandb/automations/_generated/fragments.py +343 -0
  29. wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
  30. wandb/automations/_generated/get_triggers.py +24 -0
  31. wandb/automations/_generated/get_triggers_by_entity.py +24 -0
  32. wandb/automations/_generated/input_types.py +104 -0
  33. wandb/automations/_generated/integrations_by_entity.py +22 -0
  34. wandb/automations/_generated/operations.py +710 -0
  35. wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
  36. wandb/automations/_generated/update_filter_trigger.py +21 -0
  37. wandb/automations/_utils.py +123 -0
  38. wandb/automations/_validators.py +73 -0
  39. wandb/automations/actions.py +205 -0
  40. wandb/automations/automations.py +109 -0
  41. wandb/automations/events.py +235 -0
  42. wandb/automations/integrations.py +26 -0
  43. wandb/automations/scopes.py +76 -0
  44. wandb/beta/workflows.py +9 -10
  45. wandb/bin/gpu_stats +0 -0
  46. wandb/bin/wandb-core +0 -0
  47. wandb/cli/cli.py +3 -3
  48. wandb/integration/keras/keras.py +2 -1
  49. wandb/integration/langchain/wandb_tracer.py +2 -1
  50. wandb/integration/metaflow/metaflow.py +19 -17
  51. wandb/integration/sacred/__init__.py +1 -1
  52. wandb/jupyter.py +155 -133
  53. wandb/old/summary.py +0 -2
  54. wandb/proto/v3/wandb_internal_pb2.py +297 -292
  55. wandb/proto/v3/wandb_settings_pb2.py +2 -2
  56. wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
  57. wandb/proto/v4/wandb_internal_pb2.py +292 -292
  58. wandb/proto/v4/wandb_settings_pb2.py +2 -2
  59. wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
  60. wandb/proto/v5/wandb_internal_pb2.py +292 -292
  61. wandb/proto/v5/wandb_settings_pb2.py +2 -2
  62. wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
  63. wandb/proto/v6/wandb_base_pb2.py +41 -0
  64. wandb/proto/v6/wandb_internal_pb2.py +393 -0
  65. wandb/proto/v6/wandb_server_pb2.py +78 -0
  66. wandb/proto/v6/wandb_settings_pb2.py +58 -0
  67. wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
  68. wandb/proto/wandb_base_pb2.py +2 -0
  69. wandb/proto/wandb_deprecated.py +10 -0
  70. wandb/proto/wandb_internal_pb2.py +3 -1
  71. wandb/proto/wandb_server_pb2.py +2 -0
  72. wandb/proto/wandb_settings_pb2.py +2 -0
  73. wandb/proto/wandb_telemetry_pb2.py +2 -0
  74. wandb/sdk/artifacts/_generated/__init__.py +248 -0
  75. wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
  76. wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
  77. wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
  78. wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
  79. wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
  80. wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
  81. wandb/sdk/artifacts/_generated/enums.py +17 -0
  82. wandb/sdk/artifacts/_generated/fragments.py +186 -0
  83. wandb/sdk/artifacts/_generated/input_types.py +16 -0
  84. wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
  85. wandb/sdk/artifacts/_generated/operations.py +510 -0
  86. wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
  87. wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
  88. wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
  89. wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
  90. wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
  91. wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
  92. wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
  93. wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
  94. wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
  95. wandb/sdk/artifacts/_graphql_fragments.py +56 -81
  96. wandb/sdk/artifacts/_validators.py +1 -0
  97. wandb/sdk/artifacts/artifact.py +110 -49
  98. wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
  99. wandb/sdk/artifacts/artifact_saver.py +16 -2
  100. wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
  101. wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
  102. wandb/sdk/data_types/audio.py +1 -3
  103. wandb/sdk/data_types/base_types/media.py +13 -7
  104. wandb/sdk/data_types/base_types/wb_value.py +34 -11
  105. wandb/sdk/data_types/html.py +36 -9
  106. wandb/sdk/data_types/image.py +56 -37
  107. wandb/sdk/data_types/molecule.py +1 -5
  108. wandb/sdk/data_types/object_3d.py +2 -1
  109. wandb/sdk/data_types/saved_model.py +7 -9
  110. wandb/sdk/data_types/table.py +5 -0
  111. wandb/sdk/data_types/trace_tree.py +2 -0
  112. wandb/sdk/data_types/utils.py +1 -1
  113. wandb/sdk/data_types/video.py +15 -30
  114. wandb/sdk/interface/interface.py +2 -0
  115. wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
  116. wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
  117. wandb/sdk/internal/internal_api.py +138 -47
  118. wandb/sdk/internal/profiler.py +6 -5
  119. wandb/sdk/internal/run.py +13 -6
  120. wandb/sdk/internal/sender.py +2 -0
  121. wandb/sdk/internal/sender_config.py +8 -11
  122. wandb/sdk/internal/settings_static.py +24 -2
  123. wandb/sdk/lib/apikey.py +40 -20
  124. wandb/sdk/lib/asyncio_compat.py +1 -1
  125. wandb/sdk/lib/deprecate.py +13 -22
  126. wandb/sdk/lib/disabled.py +2 -1
  127. wandb/sdk/lib/printer.py +37 -8
  128. wandb/sdk/lib/printer_asyncio.py +46 -0
  129. wandb/sdk/lib/redirect.py +10 -5
  130. wandb/sdk/lib/run_moment.py +4 -6
  131. wandb/sdk/lib/wb_logging.py +161 -0
  132. wandb/sdk/service/server_sock.py +19 -14
  133. wandb/sdk/service/service.py +9 -7
  134. wandb/sdk/service/streams.py +5 -0
  135. wandb/sdk/verify/verify.py +6 -3
  136. wandb/sdk/wandb_config.py +44 -43
  137. wandb/sdk/wandb_init.py +323 -141
  138. wandb/sdk/wandb_login.py +13 -4
  139. wandb/sdk/wandb_metadata.py +107 -91
  140. wandb/sdk/wandb_run.py +529 -325
  141. wandb/sdk/wandb_settings.py +422 -202
  142. wandb/sdk/wandb_setup.py +52 -1
  143. wandb/util.py +29 -29
  144. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/METADATA +7 -7
  145. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/RECORD +700 -643
  146. wandb/_globals.py +0 -19
  147. wandb/apis/public/_generated/base.py +0 -128
  148. wandb/apis/public/_generated/typing_compat.py +0 -14
  149. /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
  150. /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
  151. /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
  152. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
  153. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
  154. {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/licenses/LICENSE +0 -0
wandb/__init__.py CHANGED
@@ -10,7 +10,7 @@ For reference documentation, see https://docs.wandb.com/ref/python.
10
10
  """
11
11
  from __future__ import annotations
12
12
 
13
- __version__ = "0.19.8"
13
+ __version__ = "0.19.10"
14
14
 
15
15
 
16
16
  from wandb.errors import Error
@@ -18,6 +18,10 @@ from wandb.errors import Error
18
18
  # This needs to be early as other modules call it.
19
19
  from wandb.errors.term import termsetup, termlog, termerror, termwarn
20
20
 
21
+ # Configure the logger as early as possible for consistent behavior.
22
+ from wandb.sdk.lib import wb_logging as _wb_logging
23
+ _wb_logging.configure_wandb_logger()
24
+
21
25
  from wandb import sdk as wandb_sdk
22
26
 
23
27
  import wandb
wandb/__init__.pyi CHANGED
@@ -106,7 +106,7 @@ if TYPE_CHECKING:
106
106
  import wandb
107
107
  from wandb.plot import CustomChart
108
108
 
109
- __version__: str = "0.19.8"
109
+ __version__: str = "0.19.10"
110
110
 
111
111
  run: Run | None
112
112
  config: wandb_config.Config
@@ -222,7 +222,16 @@ def init(
222
222
  mode: Literal["online", "offline", "disabled"] | None = None,
223
223
  force: bool | None = None,
224
224
  anonymous: Literal["never", "allow", "must"] | None = None,
225
- reinit: bool | None = None,
225
+ reinit: (
226
+ bool
227
+ | Literal[
228
+ None,
229
+ "default",
230
+ "return_previous",
231
+ "finish_previous",
232
+ "create_new",
233
+ ]
234
+ ) = None,
226
235
  resume: bool | Literal["allow", "never", "must", "auto"] | None = None,
227
236
  resume_from: str | None = None,
228
237
  fork_from: str | None = None,
@@ -370,12 +379,8 @@ def init(
370
379
  to view the charts and data in the UI.
371
380
  - `"must"`: Forces the run to be logged to an anonymous account, even
372
381
  if the user is logged in.
373
- reinit: Determines if multiple `wandb.init()` calls can start new runs
374
- within the same process. By default (`False`), if an active run
375
- exists, calling `wandb.init()` returns the existing run instead of
376
- creating a new one. When `reinit=True`, the active run is finished
377
- before a new run is initialized. In notebook environments, runs are
378
- reinitialized by default unless `reinit` is explicitly set to `False`.
382
+ reinit: Shorthand for the "reinit" setting. Determines the behavior of
383
+ `wandb.init()` when a run is active.
379
384
  resume: Controls the behavior when resuming a run with the specified `id`.
380
385
  Available options are:
381
386
  - `"allow"`: If a run with the specified `id` exists, it will resume
@@ -472,6 +477,7 @@ def login(
472
477
  force: Optional[bool] = None,
473
478
  timeout: Optional[int] = None,
474
479
  verify: bool = False,
480
+ referrer: Optional[str] = None,
475
481
  ) -> bool:
476
482
  """Set up W&B login credentials.
477
483
 
@@ -491,6 +497,7 @@ def login(
491
497
  force: (bool, optional) If true, will force a relogin.
492
498
  timeout: (int, optional) Number of seconds to wait for user input.
493
499
  verify: (bool) Verify the credentials with the W&B server.
500
+ referrer: (string, optional) The referrer to use in the URL login request.
494
501
 
495
502
  Returns:
496
503
  bool: if key is configured
@@ -0,0 +1,30 @@
1
+ """Internal utilities for working with pydantic."""
2
+
3
+ from .base import (
4
+ Base,
5
+ CompatBaseModel,
6
+ GQLBase,
7
+ GQLId,
8
+ SerializedToJson,
9
+ Typename,
10
+ ensure_json,
11
+ )
12
+ from .utils import IS_PYDANTIC_V2, pydantic_isinstance, to_json
13
+ from .v1_compat import AliasChoices, computed_field, field_validator, model_validator
14
+
15
+ __all__ = [
16
+ "IS_PYDANTIC_V2",
17
+ "CompatBaseModel",
18
+ "Base",
19
+ "GQLBase",
20
+ "Typename",
21
+ "GQLId",
22
+ "SerializedToJson",
23
+ "AliasChoices",
24
+ "computed_field",
25
+ "field_validator",
26
+ "model_validator",
27
+ "pydantic_isinstance",
28
+ "to_json",
29
+ "ensure_json",
30
+ ]
@@ -0,0 +1,148 @@
1
+ """Base classes and other customizations for generated pydantic types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field, Json, StrictStr
8
+ from typing_extensions import Annotated, TypedDict, Unpack, override
9
+
10
+ from .utils import IS_PYDANTIC_V2, to_json
11
+ from .v1_compat import PydanticCompatMixin
12
+
13
+ if TYPE_CHECKING:
14
+ from pydantic.main import IncEx
15
+
16
+
17
+ class ModelDumpKwargs(TypedDict, total=False):
18
+ """Shared keyword arguments for `BaseModel.model_{dump,dump_json}`."""
19
+
20
+ include: IncEx | None
21
+ exclude: IncEx | None
22
+ context: dict[str, Any] | None
23
+ by_alias: bool | None
24
+ exclude_unset: bool
25
+ exclude_defaults: bool
26
+ exclude_none: bool
27
+ round_trip: bool
28
+ warnings: bool | Literal["none", "warn", "error"]
29
+ fallback: Callable[[Any], Any] | None
30
+ serialize_as_any: bool
31
+
32
+
33
+ #: Custom overrides of default kwargs for `BaseModel.model_{dump,dump_json}`.
34
+ MODEL_DUMP_DEFAULTS = ModelDumpKwargs(
35
+ by_alias=True, # Always serialize with aliases (e.g. camelCase names)
36
+ round_trip=True, # Ensure serialized values remain valid inputs for deserialization
37
+ )
38
+
39
+
40
+ # v1-compatible base class for pydantic types.
41
+ class CompatBaseModel(PydanticCompatMixin, BaseModel):
42
+ __doc__ = None # Prevent subclasses from inheriting the BaseModel docstring
43
+
44
+
45
+ # Base class for all generated classes/types.
46
+ # Omitted from docstring to avoid inclusion in generated docs.
47
+ class Base(CompatBaseModel):
48
+ model_config = ConfigDict(
49
+ populate_by_name=True,
50
+ validate_assignment=True,
51
+ validate_default=True,
52
+ extra="forbid",
53
+ use_attribute_docstrings=True,
54
+ from_attributes=True,
55
+ revalidate_instances="always",
56
+ )
57
+
58
+ @override
59
+ def model_dump(
60
+ self,
61
+ *,
62
+ mode: Literal["json", "python"] | str = "json", # NOTE: changed default
63
+ **kwargs: Unpack[ModelDumpKwargs],
64
+ ) -> dict[str, Any]:
65
+ kwargs = {**MODEL_DUMP_DEFAULTS, **kwargs}
66
+ return super().model_dump(mode=mode, **kwargs)
67
+
68
+ @override
69
+ def model_dump_json(
70
+ self,
71
+ *,
72
+ indent: int | None = None,
73
+ **kwargs: Unpack[ModelDumpKwargs],
74
+ ) -> str:
75
+ kwargs = {**MODEL_DUMP_DEFAULTS, **kwargs}
76
+ return super().model_dump_json(indent=indent, **kwargs)
77
+
78
+
79
+ # Base class with extra customization for GQL generated types.
80
+ # Omitted from docstring to avoid inclusion in generated docs.
81
+ class GQLBase(Base):
82
+ model_config = ConfigDict(
83
+ extra="ignore",
84
+ protected_namespaces=(),
85
+ )
86
+
87
+
88
+ # ------------------------------------------------------------------------------
89
+ # Reusable annotations for field types
90
+ T = TypeVar("T")
91
+
92
+ if IS_PYDANTIC_V2:
93
+ GQLId = Annotated[
94
+ StrictStr,
95
+ Field(repr=False, frozen=True),
96
+ ]
97
+ else:
98
+ # FIXME: Find a way to fix this for pydantic v1, which doesn't like when
99
+ # `Field(...)` used in the field assignment AND `Annotated[...]`.
100
+ # This is a problem for codegen, which can currently outputs e.g.
101
+ #
102
+ # class MyModel(GQLBase):
103
+ # my_id: GQLId = Field(alias="myID")
104
+ #
105
+ GQLId = StrictStr # type: ignore[misc]
106
+
107
+ Typename = Annotated[
108
+ T,
109
+ Field(repr=False, frozen=True, alias="__typename"),
110
+ ]
111
+
112
+
113
+ def ensure_json(v: Any) -> Any:
114
+ """In case the incoming value isn't serialized JSON, reserialize it.
115
+
116
+ This lets us use `Json[...]` fields with values that are already deserialized.
117
+ """
118
+ # NOTE: Assumes that the deserialized type is not itself a string.
119
+ # Revisit this if we need to support deserialized types that are str/bytes.
120
+ return v if isinstance(v, (str, bytes)) else to_json(v)
121
+
122
+
123
+ if IS_PYDANTIC_V2 or TYPE_CHECKING:
124
+ from pydantic import BeforeValidator
125
+
126
+ SerializedToJson = Annotated[
127
+ Json[T],
128
+ # Allow lenient instantiation/validation: incoming data may already be deserialized.
129
+ BeforeValidator(ensure_json),
130
+ ]
131
+ else:
132
+ SerializedToJson = Json[T] # type: ignore[misc]
133
+
134
+ # FIXME: Restore, modify, or replace this later after ensuring pydantic v1 compatibility.
135
+ # def validate_maybe_json(v: Any, handler: ValidatorFunctionWrapHandler) -> Any:
136
+ # """Wraps default Json[...] field validator to allow instantiation with an already-decoded value."""
137
+ # try:
138
+ # return handler(v)
139
+ # except ValidationError:
140
+ # # Try revalidating after properly jsonifying the value
141
+ # return handler(to_json(v, by_alias=True, round_trip=True))
142
+ #
143
+ #
144
+ # SerializedToJson = Annotated[
145
+ # Json[T],
146
+ # # Allow lenient instantiation/validation: incoming data may already be deserialized.
147
+ # WrapValidator(validate_maybe_json),
148
+ # ]
@@ -0,0 +1,66 @@
1
+ """Internal utilities for working with Pydantic types and data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from contextlib import suppress
8
+ from typing import Any, Type
9
+
10
+ import pydantic
11
+ from pydantic import BaseModel, ValidationError
12
+ from typing_extensions import TypeAlias
13
+
14
+ PYTHON_VERSION = sys.version_info
15
+
16
+ pydantic_major, *_ = pydantic.VERSION.split(".")
17
+ IS_PYDANTIC_V2: bool = int(pydantic_major) >= 2
18
+
19
+
20
+ BaseModelType: TypeAlias = Type[BaseModel]
21
+
22
+
23
+ if IS_PYDANTIC_V2:
24
+ import pydantic_core # pydantic_core is only installed by pydantic v2
25
+
26
+ def to_json(v: Any) -> str:
27
+ """Serialize a Python object to a JSON string."""
28
+ return pydantic_core.to_json(v, by_alias=True, round_trip=True).decode("utf-8")
29
+
30
+ def pydantic_isinstance(
31
+ v: Any, classinfo: BaseModelType | tuple[BaseModelType, ...]
32
+ ) -> bool:
33
+ """Return True if the object could be parsed into the given Pydantic type.
34
+
35
+ This is like a more lenient version of `isinstance()` for use with Pydantic.
36
+ In Pydantic v2, should be fast since the underlying implementation is in Rust,
37
+ and it may be preferable over `try:...except ValidationError:...`.
38
+
39
+ See: https://docs.pydantic.dev/latest/api/pydantic_core/#pydantic_core.SchemaValidator.isinstance_python
40
+ """
41
+ if isinstance(classinfo, tuple):
42
+ return any(
43
+ cls.__pydantic_validator__.isinstance_python(v) for cls in classinfo
44
+ )
45
+ cls = classinfo
46
+ return cls.__pydantic_validator__.isinstance_python(v)
47
+
48
+ else:
49
+ # Pydantic v1 fallback implementations.
50
+ # These may be noticeably slower, but their primary goal is to ensure
51
+ # compatibility with Pydantic v1 so long as we need to support it.
52
+
53
+ from pydantic.json import pydantic_encoder # Only valid in pydantic v1
54
+
55
+ def to_json(v: Any) -> str:
56
+ return json.dumps(v, default=pydantic_encoder)
57
+
58
+ def pydantic_isinstance(
59
+ v: Any, classinfo: BaseModelType | tuple[BaseModelType, ...]
60
+ ) -> bool:
61
+ classes = classinfo if isinstance(classinfo, tuple) else (classinfo,)
62
+ for cls in classes:
63
+ with suppress(ValidationError):
64
+ cls.model_validate(v)
65
+ return True
66
+ return False
@@ -0,0 +1,284 @@
1
+ """Provides partial support for compatibility with Pydantic v1."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from functools import lru_cache
7
+ from inspect import signature
8
+ from operator import attrgetter
9
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, overload
10
+
11
+ import pydantic
12
+
13
+ from .utils import IS_PYDANTIC_V2, to_json
14
+
15
+ if TYPE_CHECKING:
16
+ from typing import Protocol
17
+
18
+ class V1Model(Protocol):
19
+ # ------------------------------------------------------------------------------
20
+ # NOTE: These aren't part of the original v1 BaseModel spec, but were added as
21
+ # internal helpers and are (re-)declared here to satisfy mypy checks.
22
+ @classmethod
23
+ def _dump_json_vals(cls, values: dict, by_alias: bool) -> dict: ...
24
+
25
+ # ------------------------------------------------------------------------------
26
+ # These methods are part of the original v1 BaseModel spec.
27
+
28
+ __config__: ClassVar[type]
29
+ __fields__: ClassVar[dict[str, Any]]
30
+ __fields_set__: set[str]
31
+
32
+ @classmethod
33
+ def update_forward_refs(cls, *args: Any, **kwargs: Any) -> None: ...
34
+ @classmethod
35
+ def construct(cls, *args: Any, **kwargs: Any) -> V1Model: ...
36
+ @classmethod
37
+ def parse_obj(cls, *args: Any, **kwargs: Any) -> V1Model: ...
38
+ @classmethod
39
+ def parse_raw(cls, *args: Any, **kwargs: Any) -> V1Model: ...
40
+
41
+ def dict(self, **kwargs: Any) -> dict[str, Any]: ...
42
+ def json(self, **kwargs: Any) -> str: ...
43
+ def copy(self, **kwargs: Any) -> V1Model: ...
44
+
45
+
46
+ # Maps {v2 -> v1} model config keys that were renamed in v2.
47
+ # See: https://docs.pydantic.dev/latest/migration/#changes-to-config
48
+ _V1_CONFIG_KEYS = {
49
+ "populate_by_name": "allow_population_by_field_name",
50
+ "str_to_lower": "anystr_lower",
51
+ "str_strip_whitespace": "anystr_strip_whitespace",
52
+ "str_to_upper": "anystr_upper",
53
+ "ignored_types": "keep_untouched",
54
+ "str_max_length": "max_anystr_length",
55
+ "str_min_length": "min_anystr_length",
56
+ "from_attributes": "orm_mode",
57
+ "json_schema_extra": "schema_extra",
58
+ "validate_default": "validate_all",
59
+ }
60
+
61
+
62
+ def convert_v2_config(v2_config: dict[str, Any]) -> dict[str, Any]:
63
+ """Internal helper: Return a copy of the v2 ConfigDict with renamed v1 keys."""
64
+ return {_V1_CONFIG_KEYS.get(k, k): v for k, v in v2_config.items()}
65
+
66
+
67
+ @lru_cache(maxsize=None) # Reduce repeat introspection via `signature()`
68
+ def allowed_arg_names(func: Callable) -> set[str]:
69
+ """Internal helper: Return the names of args accepted by the given function."""
70
+ return set(signature(func).parameters)
71
+
72
+
73
+ # Pydantic BaseModels are defined with a custom metaclass, but its namespace
74
+ # has changed between pydantic versions.
75
+ #
76
+ # In v1, it can be imported as `from pydantic.main import ModelMetaclass`
77
+ # In v2, it's defined in an internal module so we avoid directly importing it.
78
+ PydanticModelMetaclass: type = type(pydantic.BaseModel)
79
+
80
+
81
+ class V1MixinMetaclass(PydanticModelMetaclass):
82
+ def __new__(
83
+ cls,
84
+ name: str,
85
+ bases: tuple[type, ...],
86
+ namespace: dict[str, Any],
87
+ **kwargs: Any,
88
+ ):
89
+ # In the class definition, convert the model config, if any:
90
+ # # BEFORE
91
+ # class MyModel(BaseModel): # v2 model with `ConfigDict`
92
+ # model_config = ConfigDict(populate_by_name=True)
93
+ #
94
+ # # AFTER
95
+ # class MyModel(BaseModel): # v1 model with inner `Config` class
96
+ # class Config:
97
+ # allow_population_by_field_name = True
98
+ if config_dict := namespace.pop("model_config", None):
99
+ namespace["Config"] = type("Config", (), convert_v2_config(config_dict))
100
+ return super().__new__(cls, name, bases, namespace, **kwargs)
101
+
102
+ @property
103
+ def model_fields(self) -> dict[str, Any]:
104
+ return self.__fields__
105
+
106
+
107
+ # Mixin to ensure compatibility of Pydantic models if Pydantic v1 is detected.
108
+ # These are "best effort" implementations and cannot guarantee complete
109
+ # compatibility in v1 environments.
110
+ #
111
+ # Whenever possible, users should strongly prefer upgrading to Pydantic v2 to
112
+ # ensure full compatibility.
113
+ class V1Mixin(metaclass=V1MixinMetaclass):
114
+ # Internal compat helpers
115
+ @classmethod
116
+ def _dump_json_vals(cls, values: dict[str, Any], by_alias: bool) -> dict[str, Any]:
117
+ """Reserialize values from `Json`-typed fields after dumping the model to dict."""
118
+ # Get the expected keys (after `.model_dump()`) for `Json`-typed fields.
119
+ # Note: In v1, `Json` fields have `ModelField.parse_json == True`
120
+ json_fields = (f for f in cls.__fields__.values() if f.parse_json)
121
+ get_key = attrgetter("alias" if by_alias else "name")
122
+ json_field_keys = set(map(get_key, json_fields))
123
+
124
+ return {
125
+ # Only serialize `Json` fields with non-null values.
126
+ k: to_json(v) if ((v is not None) and (k in json_field_keys)) else v
127
+ for k, v in values.items()
128
+ }
129
+
130
+ # ------------------------------------------------------------------------------
131
+ @classmethod
132
+ def __try_update_forward_refs__(cls: type[V1Model], **localns: Any) -> None:
133
+ if hasattr(sup := super(), "__try_update_forward_refs__"):
134
+ sup.__try_update_forward_refs__(**localns)
135
+
136
+ @classmethod
137
+ def model_rebuild(cls, *args: Any, **kwargs: Any) -> None:
138
+ return cls.update_forward_refs(*args, **kwargs)
139
+
140
+ @classmethod
141
+ def model_construct(cls, *args: Any, **kwargs: Any) -> V1Model:
142
+ return cls.construct(*args, **kwargs)
143
+
144
+ @classmethod
145
+ def model_validate(cls, *args: Any, **kwargs: Any) -> V1Model:
146
+ return cls.parse_obj(*args, **kwargs)
147
+
148
+ @classmethod
149
+ def model_validate_json(cls, *args: Any, **kwargs: Any) -> V1Model:
150
+ return cls.parse_raw(*args, **kwargs)
151
+
152
+ def model_dump(self: V1Model, **kwargs: Any) -> dict[str, Any]:
153
+ # Pass only kwargs that are allowed in the V1 method.
154
+ allowed_keys = allowed_arg_names(self.dict) & kwargs.keys()
155
+ dict_ = self.dict(**{k: kwargs[k] for k in allowed_keys})
156
+
157
+ # Ugly hack: Try to serialize `Json` fields correctly when `round_trip=True` in pydantic v1
158
+ if kwargs.get("round_trip", False):
159
+ by_alias: bool = kwargs.get("by_alias", False)
160
+ return self._dump_json_vals(dict_, by_alias=by_alias)
161
+
162
+ return dict_
163
+
164
+ def model_dump_json(self: V1Model, **kwargs: Any) -> str:
165
+ # Pass only kwargs that are allowed in the V1 method.
166
+ allowed_keys = allowed_arg_names(self.json) & kwargs.keys()
167
+ json_ = self.json(**{k: kwargs[k] for k in allowed_keys})
168
+
169
+ # Ugly hack: Try to serialize `Json` fields correctly when `round_trip=True` in pydantic v1
170
+ if kwargs.get("round_trip", False):
171
+ by_alias: bool = kwargs.get("by_alias", False)
172
+ dict_ = json.loads(json_)
173
+ return json.dumps(self._dump_json_vals(dict_, by_alias=by_alias))
174
+
175
+ return json_
176
+
177
+ def model_copy(self: V1Model, **kwargs: Any) -> V1Model:
178
+ # Pass only kwargs that are allowed in the V1 method.
179
+ allowed_keys = allowed_arg_names(self.copy) & kwargs.keys()
180
+ return self.copy(**{k: kwargs[k] for k in allowed_keys})
181
+
182
+ @property
183
+ def model_fields_set(self: V1Model) -> set[str]:
184
+ return self.__fields_set__
185
+
186
+
187
+ # Placeholder. Pydantic v2 is already compatible with itself, so no need for extra mixins.
188
+ class V2Mixin:
189
+ pass
190
+
191
+
192
+ # Pick the mixin type based on the detected Pydantic version.
193
+ PydanticCompatMixin: type = V2Mixin if IS_PYDANTIC_V2 else V1Mixin
194
+
195
+
196
+ # ----------------------------------------------------------------------------
197
+ # Decorators and other pydantic helpers
198
+ # ----------------------------------------------------------------------------
199
+ if IS_PYDANTIC_V2:
200
+ field_validator = pydantic.field_validator
201
+ model_validator = pydantic.model_validator
202
+ AliasChoices = pydantic.AliasChoices
203
+ computed_field = pydantic.computed_field
204
+
205
+ else:
206
+ # Redefines `@field_validator` with a v2-like signature
207
+ # to call `@validator` from v1 instead.
208
+ def field_validator(
209
+ field: str,
210
+ /,
211
+ *fields: str,
212
+ mode: Literal["before", "after", "wrap", "plain"] = "after",
213
+ check_fields: bool | None = None,
214
+ **_: Any,
215
+ ) -> Callable:
216
+ return pydantic.validator(
217
+ field,
218
+ *fields,
219
+ pre=(mode == "before"),
220
+ always=True,
221
+ check_fields=bool(check_fields),
222
+ allow_reuse=True,
223
+ )
224
+
225
+ # Redefines `@model_validator` with a v2-like signature
226
+ # to call `@root_validator` from v1 instead.
227
+ def model_validator(
228
+ *,
229
+ mode: Literal["before", "after", "wrap", "plain"],
230
+ **_: Any,
231
+ ) -> Callable:
232
+ if mode == "after":
233
+ # Patch the behavior for `@model_validator(mode="after")` in v1. This is
234
+ # necessarily complicated because:
235
+ # - `@model_validator(mode="after")` decorates an instance method in pydantic v2
236
+ # - `@root_validator(pre=False)` always decorates a classmethod in pydantic v1
237
+ def _decorator(v2_method: Callable) -> Any:
238
+ def v1_method(
239
+ cls: type[V1Model], values: dict[str, Any]
240
+ ) -> dict[str, Any]:
241
+ # Note: Since this is an "after" validator, the values should already be
242
+ # validated, so `.construct()` in v1 (`.model_construct()` in v2)
243
+ # should create a valid object to pass to the **original** decorated instance method.
244
+ validated = v2_method(cls.construct(**values))
245
+
246
+ # Pydantic v1 expects the validator to return a dict of {field_name -> value}
247
+ return {
248
+ name: getattr(validated, name) for name in validated.__fields__
249
+ }
250
+
251
+ return pydantic.root_validator(pre=False, allow_reuse=True)( # type: ignore[call-overload]
252
+ classmethod(v1_method)
253
+ )
254
+
255
+ return _decorator
256
+ else:
257
+ return pydantic.root_validator(pre=(mode == "before"), allow_reuse=True) # type: ignore[call-overload]
258
+
259
+ @overload # type: ignore[no-redef]
260
+ def computed_field(func: Callable | property, /) -> property: ...
261
+ @overload
262
+ def computed_field(
263
+ func: None, /, **_: Any
264
+ ) -> Callable[[Callable | property], property]: ...
265
+
266
+ def computed_field(
267
+ func: Callable | property | None = None, /, **_: Any
268
+ ) -> property | Callable[[Callable | property], property]:
269
+ """Compatibility wrapper for Pydantic v2's `computed_field` in v1."""
270
+
271
+ def always_property(f: Callable | property) -> property:
272
+ # Convert the method to a property only if needed
273
+ return f if isinstance(f, property) else property(f)
274
+
275
+ # Handle both decorator styles
276
+ return always_property if (func is None) else always_property(func)
277
+
278
+ class AliasChoices: # type: ignore [no-redef]
279
+ """Placeholder class for Pydantic v2's AliasChoices for partial v1 compatibility."""
280
+
281
+ aliases: list[str]
282
+
283
+ def __init__(self, *aliases: str):
284
+ self.aliases = list(aliases)