wandb 0.19.8__py3-none-macosx_11_0_x86_64.whl → 0.19.10__py3-none-macosx_11_0_x86_64.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.
- wandb/__init__.py +5 -1
- wandb/__init__.pyi +15 -8
- wandb/_pydantic/__init__.py +30 -0
- wandb/_pydantic/base.py +148 -0
- wandb/_pydantic/utils.py +66 -0
- wandb/_pydantic/v1_compat.py +284 -0
- wandb/apis/paginator.py +82 -38
- wandb/apis/public/__init__.py +2 -2
- wandb/apis/public/api.py +111 -53
- wandb/apis/public/artifacts.py +387 -639
- wandb/apis/public/automations.py +69 -0
- wandb/apis/public/files.py +2 -2
- wandb/apis/public/integrations.py +168 -0
- wandb/apis/public/projects.py +32 -2
- wandb/apis/public/reports.py +2 -2
- wandb/apis/public/runs.py +19 -11
- wandb/apis/public/utils.py +107 -1
- wandb/automations/__init__.py +81 -0
- wandb/automations/_filters/__init__.py +40 -0
- wandb/automations/_filters/expressions.py +179 -0
- wandb/automations/_filters/operators.py +267 -0
- wandb/automations/_filters/run_metrics.py +183 -0
- wandb/automations/_generated/__init__.py +184 -0
- wandb/automations/_generated/create_filter_trigger.py +21 -0
- wandb/automations/_generated/create_generic_webhook_integration.py +43 -0
- wandb/automations/_generated/delete_trigger.py +19 -0
- wandb/automations/_generated/enums.py +33 -0
- wandb/automations/_generated/fragments.py +343 -0
- wandb/automations/_generated/generic_webhook_integrations_by_entity.py +22 -0
- wandb/automations/_generated/get_triggers.py +24 -0
- wandb/automations/_generated/get_triggers_by_entity.py +24 -0
- wandb/automations/_generated/input_types.py +104 -0
- wandb/automations/_generated/integrations_by_entity.py +22 -0
- wandb/automations/_generated/operations.py +710 -0
- wandb/automations/_generated/slack_integrations_by_entity.py +22 -0
- wandb/automations/_generated/update_filter_trigger.py +21 -0
- wandb/automations/_utils.py +123 -0
- wandb/automations/_validators.py +73 -0
- wandb/automations/actions.py +205 -0
- wandb/automations/automations.py +109 -0
- wandb/automations/events.py +235 -0
- wandb/automations/integrations.py +26 -0
- wandb/automations/scopes.py +76 -0
- wandb/beta/workflows.py +9 -10
- wandb/bin/gpu_stats +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/cli.py +3 -3
- wandb/integration/keras/keras.py +2 -1
- wandb/integration/langchain/wandb_tracer.py +2 -1
- wandb/integration/metaflow/metaflow.py +19 -17
- wandb/integration/sacred/__init__.py +1 -1
- wandb/jupyter.py +155 -133
- wandb/old/summary.py +0 -2
- wandb/proto/v3/wandb_internal_pb2.py +297 -292
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v4/wandb_internal_pb2.py +292 -292
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v5/wandb_internal_pb2.py +292 -292
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v6/wandb_base_pb2.py +41 -0
- wandb/proto/v6/wandb_internal_pb2.py +393 -0
- wandb/proto/v6/wandb_server_pb2.py +78 -0
- wandb/proto/v6/wandb_settings_pb2.py +58 -0
- wandb/proto/v6/wandb_telemetry_pb2.py +52 -0
- wandb/proto/wandb_base_pb2.py +2 -0
- wandb/proto/wandb_deprecated.py +10 -0
- wandb/proto/wandb_internal_pb2.py +3 -1
- wandb/proto/wandb_server_pb2.py +2 -0
- wandb/proto/wandb_settings_pb2.py +2 -0
- wandb/proto/wandb_telemetry_pb2.py +2 -0
- wandb/sdk/artifacts/_generated/__init__.py +248 -0
- wandb/sdk/artifacts/_generated/artifact_collection_membership_files.py +43 -0
- wandb/sdk/artifacts/_generated/artifact_version_files.py +36 -0
- wandb/sdk/artifacts/_generated/create_artifact_collection_tag_assignments.py +36 -0
- wandb/sdk/artifacts/_generated/delete_artifact_collection_tag_assignments.py +25 -0
- wandb/sdk/artifacts/_generated/delete_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/delete_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_generated/enums.py +17 -0
- wandb/sdk/artifacts/_generated/fragments.py +186 -0
- wandb/sdk/artifacts/_generated/input_types.py +16 -0
- wandb/sdk/artifacts/_generated/move_artifact_collection.py +35 -0
- wandb/sdk/artifacts/_generated/operations.py +510 -0
- wandb/sdk/artifacts/_generated/project_artifact_collection.py +101 -0
- wandb/sdk/artifacts/_generated/project_artifact_collections.py +33 -0
- wandb/sdk/artifacts/_generated/project_artifact_type.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifact_types.py +24 -0
- wandb/sdk/artifacts/_generated/project_artifacts.py +42 -0
- wandb/sdk/artifacts/_generated/run_input_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/run_output_artifacts.py +51 -0
- wandb/sdk/artifacts/_generated/update_artifact_portfolio.py +35 -0
- wandb/sdk/artifacts/_generated/update_artifact_sequence.py +35 -0
- wandb/sdk/artifacts/_graphql_fragments.py +56 -81
- wandb/sdk/artifacts/_validators.py +1 -0
- wandb/sdk/artifacts/artifact.py +110 -49
- wandb/sdk/artifacts/artifact_manifest_entry.py +2 -1
- wandb/sdk/artifacts/artifact_saver.py +16 -2
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +1 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +23 -2
- wandb/sdk/data_types/audio.py +1 -3
- wandb/sdk/data_types/base_types/media.py +13 -7
- wandb/sdk/data_types/base_types/wb_value.py +34 -11
- wandb/sdk/data_types/html.py +36 -9
- wandb/sdk/data_types/image.py +56 -37
- wandb/sdk/data_types/molecule.py +1 -5
- wandb/sdk/data_types/object_3d.py +2 -1
- wandb/sdk/data_types/saved_model.py +7 -9
- wandb/sdk/data_types/table.py +5 -0
- wandb/sdk/data_types/trace_tree.py +2 -0
- wandb/sdk/data_types/utils.py +1 -1
- wandb/sdk/data_types/video.py +15 -30
- wandb/sdk/interface/interface.py +2 -0
- wandb/{apis/public → sdk/internal}/_generated/__init__.py +0 -6
- wandb/{apis/public → sdk/internal}/_generated/server_features_query.py +3 -3
- wandb/sdk/internal/internal_api.py +138 -47
- wandb/sdk/internal/profiler.py +6 -5
- wandb/sdk/internal/run.py +13 -6
- wandb/sdk/internal/sender.py +2 -0
- wandb/sdk/internal/sender_config.py +8 -11
- wandb/sdk/internal/settings_static.py +24 -2
- wandb/sdk/lib/apikey.py +40 -20
- wandb/sdk/lib/asyncio_compat.py +1 -1
- wandb/sdk/lib/deprecate.py +13 -22
- wandb/sdk/lib/disabled.py +2 -1
- wandb/sdk/lib/printer.py +37 -8
- wandb/sdk/lib/printer_asyncio.py +46 -0
- wandb/sdk/lib/redirect.py +10 -5
- wandb/sdk/lib/run_moment.py +4 -6
- wandb/sdk/lib/wb_logging.py +161 -0
- wandb/sdk/service/server_sock.py +19 -14
- wandb/sdk/service/service.py +9 -7
- wandb/sdk/service/streams.py +5 -0
- wandb/sdk/verify/verify.py +6 -3
- wandb/sdk/wandb_config.py +44 -43
- wandb/sdk/wandb_init.py +323 -141
- wandb/sdk/wandb_login.py +13 -4
- wandb/sdk/wandb_metadata.py +107 -91
- wandb/sdk/wandb_run.py +529 -325
- wandb/sdk/wandb_settings.py +422 -202
- wandb/sdk/wandb_setup.py +52 -1
- wandb/util.py +29 -29
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/METADATA +7 -7
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/RECORD +151 -94
- wandb/_globals.py +0 -19
- wandb/apis/public/_generated/base.py +0 -128
- wandb/apis/public/_generated/typing_compat.py +0 -14
- /wandb/{apis/public → sdk/internal}/_generated/enums.py +0 -0
- /wandb/{apis/public → sdk/internal}/_generated/input_types.py +0 -0
- /wandb/{apis/public → sdk/internal}/_generated/operations.py +0 -0
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/WHEEL +0 -0
- {wandb-0.19.8.dist-info → wandb-0.19.10.dist-info}/entry_points.txt +0 -0
- {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.
|
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.
|
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:
|
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:
|
374
|
-
|
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
|
+
]
|
wandb/_pydantic/base.py
ADDED
@@ -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
|
+
# ]
|
wandb/_pydantic/utils.py
ADDED
@@ -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)
|