From 14fc3fe10372dbd1ed421129935e45d3d6f40dc0 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 11:52:58 -0700 Subject: [PATCH 01/27] init --- dev_docs/BaseObjectClasses.md | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 dev_docs/BaseObjectClasses.md diff --git a/dev_docs/BaseObjectClasses.md b/dev_docs/BaseObjectClasses.md new file mode 100644 index 00000000000..420b3ae15cc --- /dev/null +++ b/dev_docs/BaseObjectClasses.md @@ -0,0 +1,98 @@ +# BaseObjectClasses + +## Refresher on Objects and object storage + +In Weave, we have a general-purpose data storage system for objects. +The payloads themselves are completely free-form - basically anything that can be JSON-serialized. +Users can "publish" runtime objects to weave using `weave.publish`. +For example: + +```python +config = {"model_name": "my_model", "model_version": "1.0"} +ref = weave.publish(config, name="my_model_config") +``` + +This will create a new object "version" in the collection called "my_model_config". +These can then be retrieved using `weave.ref().get()`: + +```python +config = weave.ref("my_model_config").get() +``` + +Sometimes users are working with standard structured classes like `dataclasses` or `pydantic.BaseModel`. +In such cases, we have special serialization and deserialization logic that allows for cleaner serialization patterns. +For example, let's say the user does: + +```python +class ModelConfig(weave.Object): + model_name: str + model_version: str +``` + +Then the user can publish an instance of `ModelConfig` as follows: + +```python +config = ModelConfig(model_name="my_model", model_version="1.0") +ref = weave.publish(config) +``` + +This will result in an on-disk payload that looks like: + +```json +{ + "model_name": "my_model", + "model_version": "1.0", + "_class_name": "ModelConfig", + "_bases": ["Object", "BaseModel"] +} +``` + +And additionally, the user can query for all objects of the `ModelConfig` class using the `base_model_classes` filter in `objs_query` or `POST objs/query`. +Effectively, this is like creating a virtual table for that class. + +**Terminology**: We use the term "weave Object" (capital "O") to refer to instances of classes that subclass `weave.Object`. + +**Technical note**: the "base_model_class" is the first subtype of "Object", not the _class_name. +For example, let's say the class heirarchy is: +* `A -> Object -> BaseModel`, then the `base_model_class` filter will be "A". +* `B -> A -> Object -> BaseModel`, then the `base_model_class` filter will still be "A"! + +Finally, the Weave library itself utilizes this mechanism for common objects like `Model`, `Dataset`, `Evaluation`, etc... +This allows the user to subclass these objects to add additional metadata or functionality, while categorizing them in the same virtual table. + +## The need for validation +Objects, like people, sometimes need validation. Many of our Objects (eg. `Model`) don't really need validation as they are completely free-form and up to the user to definie the properties. +However, there is an increasing need to have a well-defined schema for certain configuration objects - where those objects are tightly defined by Weave, not the user. +In such cases, it is important to have a standard, validated schema that can be shared across the python sdk, the http api, the DB, the frontend UI, and the typescript sdk. + +### Motivating example: +Let's consider that we are defining a new concept "Leaderboard". We want to store this as a configuration object in Weave. +Let's say that it's pydantic representation is: + +```python +class LeaderboardColumn(BaseModel): + name: str + description: str + evaluation_obj: str # <- reference to an Evaluation object + metric_name: str + lower_is_better: bool + +class Leaderboard(BaseModel): + name: str + description: str + columns: List[LeaderboardColumn] +``` + +We want to be able to store & query `Leaderboards` efficiently and ensure they always have a well-defined schema. Therefore we need: +1. An ability to validate the schema at insertion time +2. An ability to create such objects view the REST API +3. An ability to construct, publish, & query these objects: + * via the HTTP API (future: would be nice to have these types inside the openapi schema) + * from Python SDK (need a `weave.Object` subclass) + * from Typescript SDK (TBD - not yet supported, but need the ability to generate the correct utilities) + * from Frontend UI (need a generated Zod schema & hooks to validate the data) + * Ideally, with static type-safety. + +And all of the above should be generated from a single source of truth: the pydantic schema. + + From 298ffc559a7740836d1b70ee1e4a5dfd1e83dc30 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 14:07:08 -0700 Subject: [PATCH 02/27] init --- dev_docs/BaseObjectClasses.md | 27 +++- tests/trace/test_base_object_classes.py | 127 ++++++++++++++++++ weave/trace/base_objects.py | 1 + weave/trace/weave_client.py | 2 +- .../base_object_classes/base_object_def.py | 9 ++ .../base_object_registry.py | 9 ++ .../base_object_classes/test_only_example.py | 23 ++++ 7 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 tests/trace/test_base_object_classes.py create mode 100644 weave/trace/base_objects.py create mode 100644 weave/trace_server/interface/base_object_classes/base_object_def.py create mode 100644 weave/trace_server/interface/base_object_classes/base_object_registry.py create mode 100644 weave/trace_server/interface/base_object_classes/test_only_example.py diff --git a/dev_docs/BaseObjectClasses.md b/dev_docs/BaseObjectClasses.md index 420b3ae15cc..d77799d98f1 100644 --- a/dev_docs/BaseObjectClasses.md +++ b/dev_docs/BaseObjectClasses.md @@ -47,15 +47,15 @@ This will result in an on-disk payload that looks like: } ``` -And additionally, the user can query for all objects of the `ModelConfig` class using the `base_model_classes` filter in `objs_query` or `POST objs/query`. +And additionally, the user can query for all objects of the `ModelConfig` class using the `base_object_classes` filter in `objs_query` or `POST objs/query`. Effectively, this is like creating a virtual table for that class. **Terminology**: We use the term "weave Object" (capital "O") to refer to instances of classes that subclass `weave.Object`. -**Technical note**: the "base_model_class" is the first subtype of "Object", not the _class_name. +**Technical note**: the "base_object_class" is the first subtype of "Object", not the _class_name. For example, let's say the class heirarchy is: -* `A -> Object -> BaseModel`, then the `base_model_class` filter will be "A". -* `B -> A -> Object -> BaseModel`, then the `base_model_class` filter will still be "A"! +* `A -> Object -> BaseModel`, then the `base_object_class` filter will be "A". +* `B -> A -> Object -> BaseModel`, then the `base_object_class` filter will still be "A"! Finally, the Weave library itself utilizes this mechanism for common objects like `Model`, `Dataset`, `Evaluation`, etc... This allows the user to subclass these objects to add additional metadata or functionality, while categorizing them in the same virtual table. @@ -95,4 +95,21 @@ We want to be able to store & query `Leaderboards` efficiently and ensure they a And all of the above should be generated from a single source of truth: the pydantic schema. - +### Solution +1. We define a set of "validated base object classes" in a common location. +2. We use that: + 1. At the DB layer to validate insertion schemas + 2. Can be published from the python sdk + * Note: figuring out the interplay with `weave.Object` is tricky here. There are a few issues: + 1. We don't want to depend on the weave sdk inside the common interface, meaning the class heirarchy is inherently divergent + 2. Without `weave.Object`: + * Our python serialization layer's `is_instance` checks will not maintain behavior + * (Seems fine?) No ref tracing or nested deserialization. Actually, i think we do want to avoid this. + * (Seems fine?) No "handle_relocatable_object" behavior + 3. We still want to have the same bases array in the final payload + - I think all of this can be resolved by extended the base_object_class to also look for `BaseObject` (objects that are pure data schema: only fields) + 3. Can be used in the frontend UI layer + * via generated Zod types & hooks + 4. Future: Exposed in the OpenAPI schema + 5. Future: Generates Typescript SDK types + diff --git a/tests/trace/test_base_object_classes.py b/tests/trace/test_base_object_classes.py new file mode 100644 index 00000000000..5d6df3600ea --- /dev/null +++ b/tests/trace/test_base_object_classes.py @@ -0,0 +1,127 @@ +import pytest + +import weave +from weave.trace import base_objects +from weave.trace.refs import ObjectRef +from weave.trace.weave_client import WeaveClient +from weave.trace_server import trace_server_interface as tsi + + +def test_pythonic_creation(client: WeaveClient): + # First, let's use the high-level pythonic creation API. + nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) + top_obj = base_objects.TestOnlyExample( + primitive=1, + nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_obj=weave.publish(nested_obj).uri(), + ) + ref = weave.publish(top_obj) + + top_obj_gotten = weave.ref(ref.uri()).get() + + assert top_obj_gotten.model_dump() == top_obj.model_dump() + + objs = client.server.obj_query( + tsi.ObjQueryReq.model_validate({ + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyExample"]}}, + ) + ) + + assert len(objs) == 1 + assert objs[0].val == top_obj.model_dump() + + + objs = client.server.obj_query( + tsi.ObjQueryReq.model_validate({ + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}}, + ) + ) + + assert len(objs) == 1 + assert objs[0].val == nested_obj.model_dump() + +def test_interface_creation(client): + # Now we will do the equivant operation using low-level interface. + nested_obj_id = "nested_obj" + nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) + nested_obj_res = client.server.obj_create( + tsi.ObjCreateReq.model_validate({ + "obj": { + "project_id": client._project_id(), + "object_id": nested_obj_id, + "val": nested_obj.model_dump(), + } + }) + ) + nested_obj_ref = ObjectRef( + entity=client.entity, + project=client.project, + name=nested_obj_id, + digest=nested_obj_res.digest, + ) + + top_level_obj_id = "top_obj" + top_obj = base_objects.TestOnlyExample( + primitive=1, + nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_obj=nested_obj_ref.uri(), + ) + top_obj_res = client.server.obj_create( + tsi.ObjCreateReq.model_validate({ + "obj": { + "project_id": client._project_id(), + "object_id": top_level_obj_id, + "val": top_obj.model_dump(), + } + }) + ) + top_obj_ref = ObjectRef( + entity=client.entity, + project=client.project, + name=top_level_obj_id, + digest=top_obj_res.digest, + ) + + top_obj_gotten = weave.ref(top_obj_ref.uri()).get() + + assert top_obj_gotten.model_dump() == top_obj.model_dump() + + nested_obj_gotten = weave.ref(nested_obj_ref.uri()).get() + + assert nested_obj_gotten.model_dump() == nested_obj.model_dump() + + objs = client.server.obj_query( + tsi.ObjQueryReq.model_validate({ + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyExample"]}}, + ) + ) + + assert len(objs) == 1 + assert objs[0].val == top_obj.model_dump() + + + objs = client.server.obj_query( + tsi.ObjQueryReq.model_validate({ + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}}, + ) + ) + + assert len(objs) == 1 + assert objs[0].val == nested_obj.model_dump() + +def test_schema_validation(client): + # Test that we can't create an object with the wrong schema + with pytest.raises(weave.errors.WeaveError): + client.server.obj_create( + tsi.ObjCreateReq.model_validate({ + "obj": { + "project_id": client._project_id(), + "object_id": "nested_obj", + "val": {"a": 2}, + } + }) + ) diff --git a/weave/trace/base_objects.py b/weave/trace/base_objects.py new file mode 100644 index 00000000000..5a520e87ee9 --- /dev/null +++ b/weave/trace/base_objects.py @@ -0,0 +1 @@ +from weave.trace_server.interface.base_object_classes.base_object_registry import * diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index 7f4d1d88ef0..57166f92fd1 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -1232,7 +1232,7 @@ def _save_nested_objects(self, obj: Any, name: Optional[str] = None) -> Any: # Case 1: Object: # Here we recurse into each of the properties of the object # and save them, and then save the object itself. - if isinstance(obj, Object): + if isinstance(obj, Object): # TODO: add the generic object here obj_rec = pydantic_object_record(obj) for v in obj_rec.__dict__.values(): self._save_nested_objects(v) diff --git a/weave/trace_server/interface/base_object_classes/base_object_def.py b/weave/trace_server/interface/base_object_classes/base_object_def.py new file mode 100644 index 00000000000..b9ab4fa7a8d --- /dev/null +++ b/weave/trace_server/interface/base_object_classes/base_object_def.py @@ -0,0 +1,9 @@ +from typing import Optional + +import pydantic + +RefStr = str + +class BaseObject(pydantic.BaseModel): + name: Optional[str] = None + description: Optional[str] = None diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py new file mode 100644 index 00000000000..bc679229f40 --- /dev/null +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -0,0 +1,9 @@ +from typing import Dict, Type + +from weave.trace_server.interface.base_object_classes.base_object_def import BaseObject +from weave.trace_server.interface.base_object_classes.test_only_example import * + +REGISTRY: Dict[str, Type[BaseObject]] = { + "TestOnlyExample": TestOnlyExample, + "TestOnlyNestedBaseObject": TestOnlyNestedBaseObject, +} diff --git a/weave/trace_server/interface/base_object_classes/test_only_example.py b/weave/trace_server/interface/base_object_classes/test_only_example.py new file mode 100644 index 00000000000..dd355110bde --- /dev/null +++ b/weave/trace_server/interface/base_object_classes/test_only_example.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel + +from weave.trace_server.interface.base_object_classes import base_object_def + + +class TestOnlyNestedBaseModel(BaseModel): + a: int + + +class TestOnlyNestedBaseObject(base_object_def.BaseObject): + b: int + + +class TestOnlyExample(base_object_def.BaseObject): + primitive: int + nested_base_model: TestOnlyNestedBaseModel + # Important: `RefStr` is just an alias for `str`. When defining `BaseObject`s, we + # should never have a property point to another `BaseObject`. This is because each + # base object is stored in the database and should be treated like a foreign key. + nested_base_object: base_object_def.RefStr + + +__all__ = ["TestOnlyExample", "TestOnlyNestedBaseObject", "TestOnlyNestedBaseModel"] \ No newline at end of file From f6be1cedc16981b398a8091dc493a6bc8cd72368 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 14:17:17 -0700 Subject: [PATCH 03/27] init --- tests/trace/test_base_object_classes.py | 151 ++++++++++++++---- weave/trace/weave_client.py | 2 +- .../base_object_classes/base_object_def.py | 1 + .../base_object_classes/test_only_example.py | 2 +- 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/tests/trace/test_base_object_classes.py b/tests/trace/test_base_object_classes.py index 5d6df3600ea..c0c9ba25b14 100644 --- a/tests/trace/test_base_object_classes.py +++ b/tests/trace/test_base_object_classes.py @@ -1,3 +1,17 @@ +""" +This test file ensures the base_object_classes behavior is as expected. Specifically: +1. We ensure that pythonic publishing and getting of objects: + a. Results in the correct base_object_class filter in the query. + b. Produces identical results. +2. We ensure that using the low-level interface: + a. Results in the correct base_object_class filter in the query. + b. Produces identical results. +3. We ensure that digests are equivalent between pythonic and interface style creation. + This is important to ensure that UI-based generation of objects is consistent with + programmatic generation. +4. We ensure that invalid schemas are properly rejected from the server. +""" + import pytest import weave @@ -19,41 +33,48 @@ def test_pythonic_creation(client: WeaveClient): top_obj_gotten = weave.ref(ref.uri()).get() + assert isinstance(top_obj_gotten, base_objects.TestOnlyExample) assert top_obj_gotten.model_dump() == top_obj.model_dump() objs = client.server.obj_query( - tsi.ObjQueryReq.model_validate({ - "project_id": client._project_id(), - "filter": {"base_object_classes": ["TestOnlyExample"]}}, + tsi.ObjQueryReq.model_validate( + { + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyExample"]}, + }, ) ) assert len(objs) == 1 assert objs[0].val == top_obj.model_dump() - objs = client.server.obj_query( - tsi.ObjQueryReq.model_validate({ - "project_id": client._project_id(), - "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}}, + tsi.ObjQueryReq.model_validate( + { + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}, + }, ) ) assert len(objs) == 1 assert objs[0].val == nested_obj.model_dump() + def test_interface_creation(client): # Now we will do the equivant operation using low-level interface. nested_obj_id = "nested_obj" nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) nested_obj_res = client.server.obj_create( - tsi.ObjCreateReq.model_validate({ - "obj": { - "project_id": client._project_id(), - "object_id": nested_obj_id, - "val": nested_obj.model_dump(), + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": nested_obj_id, + "val": nested_obj.model_dump(), + } } - }) + ) ) nested_obj_ref = ObjectRef( entity=client.entity, @@ -69,13 +90,15 @@ def test_interface_creation(client): nested_obj=nested_obj_ref.uri(), ) top_obj_res = client.server.obj_create( - tsi.ObjCreateReq.model_validate({ - "obj": { - "project_id": client._project_id(), - "object_id": top_level_obj_id, - "val": top_obj.model_dump(), + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": top_level_obj_id, + "val": top_obj.model_dump(), + } } - }) + ) ) top_obj_ref = ObjectRef( entity=client.entity, @@ -93,35 +116,97 @@ def test_interface_creation(client): assert nested_obj_gotten.model_dump() == nested_obj.model_dump() objs = client.server.obj_query( - tsi.ObjQueryReq.model_validate({ - "project_id": client._project_id(), - "filter": {"base_object_classes": ["TestOnlyExample"]}}, + tsi.ObjQueryReq.model_validate( + { + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyExample"]}, + }, ) ) assert len(objs) == 1 assert objs[0].val == top_obj.model_dump() - objs = client.server.obj_query( - tsi.ObjQueryReq.model_validate({ - "project_id": client._project_id(), - "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}}, + tsi.ObjQueryReq.model_validate( + { + "project_id": client._project_id(), + "filter": {"base_object_classes": ["TestOnlyNestedBaseObject"]}, + }, ) ) assert len(objs) == 1 assert objs[0].val == nested_obj.model_dump() + +def test_digest_equality(client): + # Next, let's make sure that the digests are all equivalent + + nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) + top_obj = base_objects.TestOnlyExample( + primitive=1, + nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_obj=weave.publish(nested_obj).uri(), + ) + ref = weave.publish(top_obj) + pythonic_digest = ref.digest + + # Now we will do the equivant operation using low-level interface. + nested_obj_id = "nested_obj" + nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) + nested_obj_res = client.server.obj_create( + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": nested_obj_id, + "val": nested_obj.model_dump(), + } + } + ) + ) + nested_obj_ref = ObjectRef( + entity=client.entity, + project=client.project, + name=nested_obj_id, + digest=nested_obj_res.digest, + ) + + top_level_obj_id = "top_obj" + top_obj = base_objects.TestOnlyExample( + primitive=1, + nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_obj=nested_obj_ref.uri(), + ) + top_obj_res = client.server.obj_create( + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": top_level_obj_id, + "val": top_obj.model_dump(), + } + } + ) + ) + + interface_style_digest = top_obj_res.digest + + assert pythonic_digest == interface_style_digest + + def test_schema_validation(client): # Test that we can't create an object with the wrong schema - with pytest.raises(weave.errors.WeaveError): + with pytest.raises(): client.server.obj_create( - tsi.ObjCreateReq.model_validate({ - "obj": { - "project_id": client._project_id(), - "object_id": "nested_obj", - "val": {"a": 2}, + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": "nested_obj", + "val": {"a": 2}, + } } - }) + ) ) diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index 57166f92fd1..656bf1c360f 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -1232,7 +1232,7 @@ def _save_nested_objects(self, obj: Any, name: Optional[str] = None) -> Any: # Case 1: Object: # Here we recurse into each of the properties of the object # and save them, and then save the object itself. - if isinstance(obj, Object): # TODO: add the generic object here + if isinstance(obj, Object): # TODO: add the generic object here obj_rec = pydantic_object_record(obj) for v in obj_rec.__dict__.values(): self._save_nested_objects(v) diff --git a/weave/trace_server/interface/base_object_classes/base_object_def.py b/weave/trace_server/interface/base_object_classes/base_object_def.py index b9ab4fa7a8d..746aae1df5f 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_def.py +++ b/weave/trace_server/interface/base_object_classes/base_object_def.py @@ -4,6 +4,7 @@ RefStr = str + class BaseObject(pydantic.BaseModel): name: Optional[str] = None description: Optional[str] = None diff --git a/weave/trace_server/interface/base_object_classes/test_only_example.py b/weave/trace_server/interface/base_object_classes/test_only_example.py index dd355110bde..b93af28790e 100644 --- a/weave/trace_server/interface/base_object_classes/test_only_example.py +++ b/weave/trace_server/interface/base_object_classes/test_only_example.py @@ -20,4 +20,4 @@ class TestOnlyExample(base_object_def.BaseObject): nested_base_object: base_object_def.RefStr -__all__ = ["TestOnlyExample", "TestOnlyNestedBaseObject", "TestOnlyNestedBaseModel"] \ No newline at end of file +__all__ = ["TestOnlyExample", "TestOnlyNestedBaseObject", "TestOnlyNestedBaseModel"] From 29da8cb7e0d431d68fbecea4ac7ca1a79198cb70 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 15:43:59 -0700 Subject: [PATCH 04/27] generation complete --- Makefile | 6 +- weave-js/package.json | 8 +- .../generatedBaseObjectClasses.zod.ts | 29 ++ weave-js/yarn.lock | 340 +++++++++++++++++- weave/Makefile | 3 + weave/scripts/generate_base_object_schemas.py | 28 ++ .../generated_base_object_class_schemas.json | 114 ++++++ .../base_object_registry.py | 7 + 8 files changed, 525 insertions(+), 10 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts create mode 100644 weave/scripts/generate_base_object_schemas.py create mode 100644 weave/scripts/generated_base_object_class_schemas.json diff --git a/Makefile b/Makefile index a53cde0a026..d152017c58b 100644 --- a/Makefile +++ b/Makefile @@ -14,4 +14,8 @@ docs: build: uv build -prepare-release: docs build \ No newline at end of file +prepare-release: docs build + +synchronize-base-object-schemas: + cd weave && make generate_base_object_schemas && \ + cd ../weave-js && yarn generate-schemas \ No newline at end of file diff --git a/weave-js/package.json b/weave-js/package.json index d925f9d0a42..5897561a6c1 100644 --- a/weave-js/package.json +++ b/weave-js/package.json @@ -20,7 +20,8 @@ "prettier": "prettier --config .prettierrc --check \"src/**/*.ts\" \"src/**/*.tsx\"", "prettier-fix": "prettier --loglevel warn --config .prettierrc --write \"src/**/*.ts\" \"src/**/*.tsx\"", "lint": "yarn eslint & yarn tslint & yarn prettier & wait", - "lint-fix": "yarn eslint-fix & yarn tslint-fix & yarn prettier-fix & wait" + "lint-fix": "yarn eslint-fix & yarn tslint-fix & yarn prettier-fix & wait", + "generate-schemas": "yarn quicktype -s schema ../weave/scripts/generated_base_object_class_schemas.json -o ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts --lang typescript-zod" }, "dependencies": { "@apollo/client": "^3.8.4", @@ -145,7 +146,8 @@ "wavesurfer.js": "^2.0.0", "web-tree-sitter": "^0.20.5", "yet-another-react-lightbox": "^3.17.5", - "zen-observable": "^0.10.0" + "zen-observable": "^0.10.0", + "zod": "^3.23.8" }, "devDependencies": { "@babel/core": "^7.23.2", @@ -217,11 +219,13 @@ "identity-obj-proxy": "^3.0.0", "jsdom": "^22.1.0", "json-schema-to-typescript": "^11.0.2", + "json-schema-to-zod": "^2.4.1", "less": "^2.7.3", "lodash.defaults": "^4.2.0", "nodemon": "^2.0.22", "prettier": "^2.8.7", "prettier-plugin-tailwindcss": "^0.2.1", + "quicktype": "^23.0.170", "rimraf": "^3.0.2", "rollup-plugin-visualizer": "^5.5.2", "tailwindcss": "^3.3.2", diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts new file mode 100644 index 00000000000..a01bf4e68ee --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts @@ -0,0 +1,29 @@ +import * as z from "zod"; + + +export const TestOnlyNestedBaseModelSchema = z.object({ + "a": z.number(), +}); +export type TestOnlyNestedBaseModel = z.infer; + +export const TestOnlyNestedBaseObjectSchema = z.object({ + "b": z.number(), + "description": z.union([z.null(), z.string()]).optional(), + "name": z.union([z.null(), z.string()]).optional(), +}); +export type TestOnlyNestedBaseObject = z.infer; + +export const TestOnlyExampleSchema = z.object({ + "description": z.union([z.null(), z.string()]).optional(), + "name": z.union([z.null(), z.string()]).optional(), + "nested_base_model": TestOnlyNestedBaseModelSchema, + "nested_base_object": z.string(), + "primitive": z.number(), +}); +export type TestOnlyExample = z.infer; + +export const GeneratedBaseObjectClassesZodSchema = z.object({ + "TestOnlyExample": TestOnlyExampleSchema, + "TestOnlyNestedBaseObject": TestOnlyNestedBaseObjectSchema, +}); +export type GeneratedBaseObjectClassesZod = z.infer; diff --git a/weave-js/yarn.lock b/weave-js/yarn.lock index 2ee20553257..8541ca9a5d7 100644 --- a/weave-js/yarn.lock +++ b/weave-js/yarn.lock @@ -2119,6 +2119,16 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== +"@glideapps/ts-necessities@2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz#62e25b3a1ace8b8c3f47e55e66d101a0a854eb23" + integrity sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w== + +"@glideapps/ts-necessities@^2.2.3": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@glideapps/ts-necessities/-/ts-necessities-2.3.2.tgz#3e7a07f41c8c07527757631f25599a7b67d39d8c" + integrity sha512-tOXo3SrEeLu+4X2q6O2iNPXdGI1qoXEz/KrbkElTsWiWb69tFH4GzWz2K++0nBD6O3qO2Ft1C4L4ZvUfE2QDlQ== + "@graphql-codegen/add@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.0.tgz#578ebaf4fa87c1e934c381cd679bcedcf79feaba" @@ -2774,6 +2784,20 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== +"@mark.probst/typescript-json-schema@0.55.0": + version "0.55.0" + resolved "https://registry.yarnpkg.com/@mark.probst/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz#a82c0cb8b3c9ba1a14faf2ea3fa95f26c1a6a57d" + integrity sha512-jI48mSnRgFQxXiE/UTUCVCpX8lK3wCFKLF1Ss2aEreboKNuLQGt3e0/YFqWVHe/WENxOaqiJvwOz+L/SrN2+qQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/node" "^16.9.2" + glob "^7.1.7" + path-equal "^1.1.2" + safe-stable-stringify "^2.2.0" + ts-node "^10.9.1" + typescript "4.9.4" + yargs "^17.1.1" + "@material-ui/core@^4.12.4": version "4.12.4" resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.4.tgz#4ac17488e8fcaf55eb6a7f5efb2a131e10138a73" @@ -4398,6 +4422,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.53.tgz#42855629b8773535ab868238718745bf56c56219" integrity sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A== +"@types/node@^16.9.2": + version "16.18.116" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.116.tgz#138a0ce907c9f308d43f89902a9ef79fbcbc5e5c" + integrity sha512-mLigUvhoaADRewggiby+XfAAFOUOMCm/SwL5DAJ+CMUGjSLIGMsJVN7BOKftuQSHGjUmS/W7hVht8fcNbi/MRA== + "@types/node@^18.15.11": version "18.16.19" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" @@ -4997,6 +5026,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + abs-svg-path@^0.1.1, abs-svg-path@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf" @@ -5195,6 +5231,16 @@ aria-query@^5.0.0, aria-query@^5.1.3: dependencies: dequal "^2.0.3" +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.2.tgz#f567d99e9af88a6d3d2f9dfcc21db6f9ba9fd157" + integrity sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw== + array-bounds@^1.0.0, array-bounds@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-bounds/-/array-bounds-1.0.1.tgz#da11356b4e18e075a4f0c86e1f179a67b7d7ea31" @@ -5562,7 +5608,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -5645,6 +5691,11 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browser-or-node@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-3.0.0.tgz#2b11335570b28887e0bf5cd857f2e8062c6ae293" + integrity sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ== + browserify-package-json@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" @@ -5687,6 +5738,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -5817,6 +5876,13 @@ chai@^4.3.10: pathval "^1.1.1" type-detect "^4.0.8" +chalk-template@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" + integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== + dependencies: + chalk "^4.1.2" + chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -5834,7 +5900,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -6047,6 +6113,11 @@ collapse-white-space@^1.0.2: resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== +collection-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collection-utils/-/collection-utils-1.0.1.tgz#31d14336488674f27aefc0a7c5eccacf6df78044" + integrity sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg== + color-alpha@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/color-alpha/-/color-alpha-1.0.4.tgz#c141dc926e95fc3db647d0e14e5bc3651c29e040" @@ -6198,6 +6269,26 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== +command-line-args@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-7.0.3.tgz#6bce992354f6af10ecea2b631bfdf0c8b3bfaea3" + integrity sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q== + dependencies: + array-back "^6.2.2" + chalk-template "^0.4.0" + table-layout "^4.1.0" + typical "^7.1.1" + commander@2, commander@^2.12.1, commander@^2.15.1, commander@^2.19.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -6437,6 +6528,13 @@ cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.12" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -7797,7 +7895,12 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" -events@^3.2.0: +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -8005,6 +8108,13 @@ filter-obj@^5.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -8349,7 +8459,7 @@ glob@7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: +glob@^7.1.1, glob@^7.1.3, glob@^7.1.6, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -8585,6 +8695,13 @@ graphql@=16.6.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@^0.11.7: + version "0.11.7" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" + integrity sha512-x7uDjyz8Jx+QPbpCFCMQ8lltnQa4p4vSYHx6ADe8rVYRTdsyhCJbvSty5DAsLVmU6cGakl+r8HQYolKHxk/tiw== + dependencies: + iterall "1.1.3" + grid-index@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" @@ -9026,7 +9143,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.12, ieee754@^1.1.13: +ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -9552,6 +9669,11 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== +iterall@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" + integrity sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ== + jest-diff@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" @@ -9636,6 +9758,11 @@ jose@^4.11.4: resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.6.tgz#94dca1d04a0ad8c6bff0998cdb51220d473cc3af" integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ== +js-base64@^3.7.7: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== + js-levenshtein@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" @@ -10038,6 +10165,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -10850,6 +10982,11 @@ moment@^2.29.3: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + monaco-editor@^0.29.1: version "0.29.1" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.29.1.tgz#6ee93d8a5320704d48fd7058204deed72429c020" @@ -11382,6 +11519,16 @@ package-json-versionify@^1.0.4: dependencies: browserify-package-json "^1.0.0" +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + +pako@^1.0.6: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + pako@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" @@ -11500,6 +11647,11 @@ path-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +path-equal@^1.1.2: + version "1.2.5" + resolved "https://registry.yarnpkg.com/path-equal/-/path-equal-1.2.5.tgz#9fcbdd5e5daee448e96f43f3bac06c666b5e982a" + integrity sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -11695,6 +11847,11 @@ plotly.js@^2.23.2: webgl-context "^2.2.0" world-calendars "^1.0.3" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + point-in-polygon@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/point-in-polygon/-/point-in-polygon-1.1.0.tgz#b0af2616c01bdee341cbf2894df643387ca03357" @@ -11859,6 +12016,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -11965,6 +12127,66 @@ quickselect@^2.0.0: resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== +quicktype-core@23.0.170: + version "23.0.170" + resolved "https://registry.yarnpkg.com/quicktype-core/-/quicktype-core-23.0.170.tgz#ecaab8091552980883dd587ebe7c91abed74866c" + integrity sha512-ZsjveG0yJUIijUx4yQshzyQ5EAXKbFSBTQJHnJ+KoSZVxcS+m3GcmDpzrdUIRYMhgLaF11ZGvLSYi5U0xcwemw== + dependencies: + "@glideapps/ts-necessities" "2.2.3" + browser-or-node "^3.0.0" + collection-utils "^1.0.1" + cross-fetch "^4.0.0" + is-url "^1.2.4" + js-base64 "^3.7.7" + lodash "^4.17.21" + pako "^1.0.6" + pluralize "^8.0.0" + readable-stream "4.5.2" + unicode-properties "^1.4.1" + urijs "^1.19.1" + wordwrap "^1.0.0" + yaml "^2.4.1" + +quicktype-graphql-input@23.0.170: + version "23.0.170" + resolved "https://registry.yarnpkg.com/quicktype-graphql-input/-/quicktype-graphql-input-23.0.170.tgz#f52bb9204a1b434b4e5f0a9003da227d50bcd9da" + integrity sha512-L0xPKdIFZFChwups9oqJuQw/vwEbRVKBvU9L5jAs0Z/aLyfdsuxDpKGMJXnNWa2yE7NhPX/UDX8ytxn8uc8hdQ== + dependencies: + collection-utils "^1.0.1" + graphql "^0.11.7" + quicktype-core "23.0.170" + +quicktype-typescript-input@23.0.170: + version "23.0.170" + resolved "https://registry.yarnpkg.com/quicktype-typescript-input/-/quicktype-typescript-input-23.0.170.tgz#13efb2f8a7846a0f685fab2852086995f8c712b2" + integrity sha512-lckhc//Mc95f/puRFKv4BFs7VpUUJXhw/psh+5ZAMiErxOWgoF87XthGusmaqoXNzjmEy1AVwGgMCG2pp/tJ/w== + dependencies: + "@mark.probst/typescript-json-schema" "0.55.0" + quicktype-core "23.0.170" + typescript "4.9.5" + +quicktype@^23.0.170: + version "23.0.170" + resolved "https://registry.yarnpkg.com/quicktype/-/quicktype-23.0.170.tgz#3a70a5d0870e327d0f7ee35e61d25b17744bb8bc" + integrity sha512-3gFyS7w36ktxrttEv1gMfuUlGairepnSpLN0cp7JVevkKX2N6Uk8AyMlDS2Puki09MY6PB6ch90plThvACtEHA== + dependencies: + "@glideapps/ts-necessities" "^2.2.3" + chalk "^4.1.2" + collection-utils "^1.0.1" + command-line-args "^5.2.1" + command-line-usage "^7.0.1" + cross-fetch "^4.0.0" + graphql "^0.11.7" + lodash "^4.17.21" + moment "^2.30.1" + quicktype-core "23.0.170" + quicktype-graphql-input "23.0.170" + quicktype-typescript-input "23.0.170" + readable-stream "^4.5.2" + stream-json "1.8.0" + string-to-stream "^3.0.1" + typescript "4.9.5" + raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -12330,6 +12552,17 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@4.5.2, readable-stream@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + "readable-stream@>=1.0.33-1 <1.1.0-0": version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -12936,6 +13169,11 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +safe-stable-stringify@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -13346,6 +13584,18 @@ std-env@^3.5.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +stream-chain@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" + integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== + +stream-json@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.8.0.tgz#53f486b2e3b4496c506131f8d7260ba42def151c" + integrity sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw== + dependencies: + stream-chain "^2.2.5" + stream-parser@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" @@ -13380,6 +13630,13 @@ string-split-by@^1.0.0: dependencies: parenthesis "^3.1.5" +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" + "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -13430,7 +13687,7 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -13635,6 +13892,14 @@ tabbable@^6.0.1: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== +table-layout@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-4.1.1.tgz#0f72965de1a5c0c1419c9ba21cae4e73a2f73a42" + integrity sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA== + dependencies: + array-back "^6.2.2" + wordwrapjs "^5.1.0" + tailwind-merge@^1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b" @@ -13733,6 +13998,11 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + tiny-invariant@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" @@ -14144,6 +14414,26 @@ typescript@4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-7.2.0.tgz#b3ec4b76530d144640df86c6b061dafd70e10c1e" + integrity sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g== + ua-parser-js@^1.0.35: version "1.0.36" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c" @@ -14222,11 +14512,27 @@ unicode-match-property-value-ecmascript@^2.1.0: resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== +unicode-properties@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f" + integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + unicode-property-aliases-ecmascript@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + unified@^10.0.0, unified@^10.1.0: version "10.1.2" resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df" @@ -14431,6 +14737,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urijs@^1.19.1: + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== + url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -15292,6 +15603,11 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== +wordwrapjs@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.0.tgz#4c4d20446dcc670b14fa115ef4f8fd9947af2b3a" + integrity sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg== + world-calendars@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/world-calendars/-/world-calendars-1.0.3.tgz#b25c5032ba24128ffc41d09faf4a5ec1b9c14335" @@ -15414,6 +15730,11 @@ yaml@^2.3.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== +yaml@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3" + integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== + yargs-parser@20.x: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -15449,7 +15770,7 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.0, yargs@^17.3.1, yargs@^17.5.1: +yargs@^17.0.0, yargs@^17.1.1, yargs@^17.3.1, yargs@^17.5.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -15525,6 +15846,11 @@ zen-observable@^0.10.0: resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.10.0.tgz#ee10eba75272897dbee5f152ab26bb5e0107f0c8" integrity sha512-iI3lT0iojZhKwT5DaFy2Ce42n3yFcLdFyOh01G7H0flMY60P8MJuVFEoJoNwXlmAyQ45GrjL6AcZmmlv8A5rbw== +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== + zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" diff --git a/weave/Makefile b/weave/Makefile index 77943c2d9cb..15f90bad6e5 100644 --- a/weave/Makefile +++ b/weave/Makefile @@ -1,2 +1,5 @@ update_costs: python trace_server/costs/update_costs.py + +generate_base_object_schemas: + python scripts/generate_base_object_schemas.py \ No newline at end of file diff --git a/weave/scripts/generate_base_object_schemas.py b/weave/scripts/generate_base_object_schemas.py new file mode 100644 index 00000000000..c78d5636cee --- /dev/null +++ b/weave/scripts/generate_base_object_schemas.py @@ -0,0 +1,28 @@ +import json +from pathlib import Path + +from weave.trace_server.interface.base_object_classes.base_object_registry import ( + REGISTRY, + CompositeBaseObject, +) + +OUTPUT_PATH = Path(__file__).parent / "generated_base_object_class_schemas.json" + + +def generate_schemas(): + """ + Generate JSON schemas for all registered base objects in REGISTRY. + Creates a top-level union type of all registered objects and writes the schemas + to a file named 'base_object_schemas.json'. + """ + top_level_schema = CompositeBaseObject.model_json_schema(mode='validation') + + # Write schemas to file + with OUTPUT_PATH.open("w") as f: + json.dump(top_level_schema, f, indent=2) + + print(f"Generated union schema for {len(REGISTRY)} objects") + print(f"Wrote schema to {OUTPUT_PATH.absolute()}") + +if __name__ == "__main__": + generate_schemas() \ No newline at end of file diff --git a/weave/scripts/generated_base_object_class_schemas.json b/weave/scripts/generated_base_object_class_schemas.json new file mode 100644 index 00000000000..4f7aee9dd54 --- /dev/null +++ b/weave/scripts/generated_base_object_class_schemas.json @@ -0,0 +1,114 @@ +{ + "$defs": { + "TestOnlyExample": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "primitive": { + "title": "Primitive", + "type": "integer" + }, + "nested_base_model": { + "$ref": "#/$defs/TestOnlyNestedBaseModel" + }, + "nested_base_object": { + "title": "Nested Base Object", + "type": "string" + } + }, + "required": [ + "primitive", + "nested_base_model", + "nested_base_object" + ], + "title": "TestOnlyExample", + "type": "object" + }, + "TestOnlyNestedBaseModel": { + "properties": { + "a": { + "title": "A", + "type": "integer" + } + }, + "required": [ + "a" + ], + "title": "TestOnlyNestedBaseModel", + "type": "object" + }, + "TestOnlyNestedBaseObject": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "b": { + "title": "B", + "type": "integer" + } + }, + "required": [ + "b" + ], + "title": "TestOnlyNestedBaseObject", + "type": "object" + } + }, + "properties": { + "TestOnlyExample": { + "$ref": "#/$defs/TestOnlyExample" + }, + "TestOnlyNestedBaseObject": { + "$ref": "#/$defs/TestOnlyNestedBaseObject" + } + }, + "required": [ + "TestOnlyExample", + "TestOnlyNestedBaseObject" + ], + "title": "CompositeBaseObject", + "type": "object" +} \ No newline at end of file diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py index bc679229f40..b4b9ab7242d 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_registry.py +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -1,5 +1,7 @@ from typing import Dict, Type +from pydantic import BaseModel + from weave.trace_server.interface.base_object_classes.base_object_def import BaseObject from weave.trace_server.interface.base_object_classes.test_only_example import * @@ -7,3 +9,8 @@ "TestOnlyExample": TestOnlyExample, "TestOnlyNestedBaseObject": TestOnlyNestedBaseObject, } + +# This is a union type of all registered base objects. +class CompositeBaseObject(BaseModel): + TestOnlyExample: TestOnlyExample + TestOnlyNestedBaseObject: TestOnlyNestedBaseObject From 2a6b2ac21beac94df139964c03361aec766b2c4e Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 17:13:28 -0700 Subject: [PATCH 05/27] beginning ts implementation --- .../wfReactInterface/baseObjectClassQuery.ts | 123 ++++++++++++++++++ .../traceServerClientTypes.ts | 22 +++- .../traceServerDirectClient.ts | 46 +++++++ 3 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts new file mode 100644 index 00000000000..f9bc49da0f9 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -0,0 +1,123 @@ +import {useDeepMemo} from '@wandb/weave/hookUtils'; +import {useEffect, useState} from 'react'; +import {z} from 'zod'; + +import { TestOnlyExampleSchema, TestOnlyNestedBaseObjectSchema } from './generatedBaseObjectClasses.zod'; +import { TraceServerClient } from './traceServerClient'; +import { useGetTraceServerClientContext } from './traceServerClientContext'; +import { + TraceObjCreateReq, + TraceObjQueryReq, + TraceObjSchema, +} from './traceServerClientTypes'; + +const collectionRegistry = { + TestOnlyExample: TestOnlyExampleSchema, + TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, +} + +export const useCollectionObjects = < + C extends keyof typeof collectionRegistry, + T extends z.infer<(typeof collectionRegistry)[C]> +>( + collectionName: C, + req: TraceObjQueryReq +) => { + const [objects, setObjects] = useState>>([]); + const getTsClient = useGetTraceServerClientContext(); + const client = getTsClient(); + const deepReq = useDeepMemo(req); + + useEffect(() => { + let isMounted = true; + getCollectionObjects(client, collectionName, deepReq).then( + collectionObjects => { + if (isMounted) { + setObjects(collectionObjects as Array>); + } + } + ); + return () => { + isMounted = false; + }; + }, [client, collectionName, deepReq]); + + return objects; +}; + +const getCollectionObjects = async < + C extends keyof typeof collectionRegistry, + T extends z.infer<(typeof collectionRegistry)[C]> +>( + client: TraceServerClient, + collectionName: C, + req: TraceObjQueryReq +): Promise>> => { + const knownCollection = collectionRegistry[collectionName]; + if (!knownCollection) { + console.warn(`Unknown collection: ${collectionName}`); + return []; + } + + const reqWithCollection: TraceObjQueryReq = { + ...req, + filter: {...req.filter, base_object_classes: [collectionName]}, + }; + + const objectPromise = client.objsQuery(reqWithCollection); + + const objects = await objectPromise; + + return objects.objs + .map(obj => ({obj, parsed: knownCollection.safeParse(obj.val)})) + .filter(({parsed}) => parsed.success) + .map(({obj, parsed}) => ({...obj, val: parsed.data!})) as Array< + TraceObjSchema + >; +}; + +export const useCreateCollectionObject = < + C extends keyof typeof collectionRegistry, + T extends z.infer<(typeof collectionRegistry)[C]> +>( + collectionName: C +) => { + const getTsClient = useGetTraceServerClientContext(); + const client = getTsClient(); + return (req: TraceObjCreateReq) => + createCollectionObject(client, collectionName, req); +}; + +const createCollectionObject = async < + C extends keyof typeof collectionRegistry, + T extends z.infer<(typeof collectionRegistry)[C]> +>( + client: TraceServerClient, + collectionName: C, + req: TraceObjCreateReq +) => { + const knownCollection = collectionRegistry[collectionName]; + if (!knownCollection) { + throw new Error(`Unknown collection: ${collectionName}`); + } + + const verifiedObject = knownCollection.safeParse(req.obj.val); + + if (!verifiedObject.success) { + throw new Error( + `Invalid object: ${JSON.stringify(verifiedObject.error.errors)}` + ); + } + + const reqWithCollection: TraceObjCreateReq = { + ...req, + obj: { + ...req.obj, + val: {...req.obj.val, _bases: [collectionName, 'BaseModel']}, + }, + }; + + const createPromse = client.objCreate(reqWithCollection); + + return createPromse; +}; \ No newline at end of file diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts index 88113a37a74..d84563a682f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts @@ -205,7 +205,7 @@ export type TraceObjQueryReq = { metadata_only?: boolean; }; -export interface TraceObjSchema { +export interface TraceObjSchema { project_id: string; object_id: string; created_at: string; @@ -214,11 +214,13 @@ export interface TraceObjSchema { is_latest: number; kind: 'op' | 'object'; base_object_class?: string; - val: any; + val: T; } -export type TraceObjQueryRes = { - objs: TraceObjSchema[]; + +export type TraceObjQueryRes = { + objs: Array>; }; + export type TraceObjReadReq = { project_id: string; object_id: string; @@ -229,6 +231,18 @@ export type TraceObjReadRes = { obj: TraceObjSchema; }; +export type TraceObjCreateReq = { + obj: { + project_id: string; + object_id: string; + val: T; + }; +}; + +export type TraceObjCreateRes = { + digest: string; +}; + export type TraceRefsReadBatchReq = { refs: string[]; }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts index caaf63b7f56..16b56273fca 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts @@ -34,6 +34,8 @@ import { TraceCallUpdateReq, TraceFileContentReadReq, TraceFileContentReadRes, + TraceObjCreateReq, + TraceObjCreateRes, TraceObjQueryReq, TraceObjQueryRes, TraceObjReadReq, @@ -231,6 +233,22 @@ export class DirectTraceServerClient { ); } + public objCreate(req: TraceObjCreateReq): Promise { + const initialObjectId = req.obj.object_id; + const sanitizedObjectId = sanitizeObjectId(initialObjectId); + if (sanitizedObjectId !== initialObjectId) { + // Caller is expected to sanitize the object id. We should be doing this + // on the server, but it is currently disabled. + throw new Error( + `Invalid object name: ${initialObjectId}, sanitized to ${sanitizedObjectId}` + ); + } + return this.makeRequest( + '/obj/create', + req + ); + } + public tableQuery(req: TraceTableQueryReq): Promise { return this.makeRequest( '/table/query', @@ -376,3 +394,31 @@ export class DirectTraceServerClient { return prom; }; } + + +/** + * Sanitizes an object name by replacing non-alphanumeric characters with dashes and enforcing length limits. + * This matches the Python implementation in weave_client.py. + * + * @param name The name to sanitize + * @returns The sanitized name + * @throws Error if the resulting name would be empty + */ +export function sanitizeObjectId(name: string): string { + // Replace any non-word chars (except dots and underscores) with dashes + let res = name.replace(/[^\w._]+/g, '-'); + // Replace multiple consecutive dashes/dots/underscores with a single dash + res = res.replace(/([._-]{2,})+/g, '-'); + // Remove leading/trailing dashes and underscores + res = res.replace(/^[-_]+|[-_]+$/g, ''); + + if (!res) { + throw new Error(`Invalid object name: ${name}`); + } + + if (res.length > 128) { + res = res.slice(0, 128); + } + + return res; +} \ No newline at end of file From 92cf5cd04d53d49d79907ff8eb31c93176fd14a4 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Wed, 30 Oct 2024 20:50:28 -0700 Subject: [PATCH 06/27] Initial TS implementation complete --- .../baseObjectClassQuery.test.ts | 33 ++++++++++ .../wfReactInterface/baseObjectClassQuery.ts | 60 ++++++++++++------- .../generatedBaseObjectClasses.zod.ts | 37 +++++++----- .../traceServerClientTypes.ts | 7 ++- .../traceServerDirectClient.ts | 3 +- 5 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts new file mode 100644 index 00000000000..4bfe3ddd066 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts @@ -0,0 +1,33 @@ +import {useCollectionObjects} from './baseObjectClassQuery'; +import {TestOnlyExampleSchema} from './generatedBaseObjectClasses.zod'; +describe('useCollectionObjects', () => { + it('hasCorrectTypes', () => { + type CT = ReturnType>; + const exampleResult: CT = { + loading: false, + data: [ + { + project_id: '', + object_id: '', + created_at: '', + digest: '', + version_index: 0, + is_latest: 0, + kind: 'object', + base_object_class: 'TestOnlyExample', + val: TestOnlyExampleSchema.parse({ + name: '', + description: '', + nested_base_model: { + a: 1, + }, + nested_base_object: '', + primitive: 1, + }), + }, + ], + }; + // This should fail, looking for the correct way to validate types + expect(exampleResult).toBeNull(); + }); +}); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index f9bc49da0f9..0d40cc4662f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -1,39 +1,57 @@ import {useDeepMemo} from '@wandb/weave/hookUtils'; -import {useEffect, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import {z} from 'zod'; -import { TestOnlyExampleSchema, TestOnlyNestedBaseObjectSchema } from './generatedBaseObjectClasses.zod'; -import { TraceServerClient } from './traceServerClient'; -import { useGetTraceServerClientContext } from './traceServerClientContext'; +import { + TestOnlyExampleSchema, + TestOnlyNestedBaseObjectSchema, +} from './generatedBaseObjectClasses.zod'; +import {TraceServerClient} from './traceServerClient'; +import {useGetTraceServerClientContext} from './traceServerClientContext'; import { TraceObjCreateReq, + TraceObjCreateRes, TraceObjQueryReq, TraceObjSchema, } from './traceServerClientTypes'; const collectionRegistry = { - TestOnlyExample: TestOnlyExampleSchema, - TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, -} + TestOnlyExample: TestOnlyExampleSchema, + TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, +}; + +type Loadable = + | { + loading: true; + } + | { + loading: false; + data: T; + }; export const useCollectionObjects = < C extends keyof typeof collectionRegistry, - T extends z.infer<(typeof collectionRegistry)[C]> + T = z.infer<(typeof collectionRegistry)[C]> >( collectionName: C, req: TraceObjQueryReq -) => { - const [objects, setObjects] = useState>>([]); +): Loadable>> => { + const [objects, setObjects] = useState>>([]); const getTsClient = useGetTraceServerClientContext(); const client = getTsClient(); const deepReq = useDeepMemo(req); + const currReq = useRef(deepReq); + const [loading, setLoading] = useState(true); useEffect(() => { let isMounted = true; + setLoading(true); + currReq.current = deepReq; getCollectionObjects(client, collectionName, deepReq).then( collectionObjects => { - if (isMounted) { - setObjects(collectionObjects as Array>); + if (isMounted && currReq.current === deepReq) { + setObjects(collectionObjects as Array>); + setLoading(false); } } ); @@ -42,17 +60,17 @@ export const useCollectionObjects = < }; }, [client, collectionName, deepReq]); - return objects; + return {data: objects, loading}; }; const getCollectionObjects = async < C extends keyof typeof collectionRegistry, - T extends z.infer<(typeof collectionRegistry)[C]> + T = z.infer<(typeof collectionRegistry)[C]> >( client: TraceServerClient, collectionName: C, req: TraceObjQueryReq -): Promise>> => { +): Promise>> => { const knownCollection = collectionRegistry[collectionName]; if (!knownCollection) { console.warn(`Unknown collection: ${collectionName}`); @@ -72,16 +90,16 @@ const getCollectionObjects = async < .map(obj => ({obj, parsed: knownCollection.safeParse(obj.val)})) .filter(({parsed}) => parsed.success) .map(({obj, parsed}) => ({...obj, val: parsed.data!})) as Array< - TraceObjSchema + TraceObjSchema >; }; export const useCreateCollectionObject = < C extends keyof typeof collectionRegistry, - T extends z.infer<(typeof collectionRegistry)[C]> + T = z.infer<(typeof collectionRegistry)[C]> >( collectionName: C -) => { +): ((req: TraceObjCreateReq) => Promise) => { const getTsClient = useGetTraceServerClientContext(); const client = getTsClient(); return (req: TraceObjCreateReq) => @@ -90,12 +108,12 @@ export const useCreateCollectionObject = < const createCollectionObject = async < C extends keyof typeof collectionRegistry, - T extends z.infer<(typeof collectionRegistry)[C]> + T = z.infer<(typeof collectionRegistry)[C]> >( client: TraceServerClient, collectionName: C, req: TraceObjCreateReq -) => { +): Promise => { const knownCollection = collectionRegistry[collectionName]; if (!knownCollection) { throw new Error(`Unknown collection: ${collectionName}`); @@ -120,4 +138,4 @@ const createCollectionObject = async < const createPromse = client.objCreate(reqWithCollection); return createPromse; -}; \ No newline at end of file +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts index a01bf4e68ee..ba5a38ebf65 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts @@ -1,29 +1,34 @@ -import * as z from "zod"; - +import * as z from 'zod'; export const TestOnlyNestedBaseModelSchema = z.object({ - "a": z.number(), + a: z.number(), }); -export type TestOnlyNestedBaseModel = z.infer; +export type TestOnlyNestedBaseModel = z.infer< + typeof TestOnlyNestedBaseModelSchema +>; export const TestOnlyNestedBaseObjectSchema = z.object({ - "b": z.number(), - "description": z.union([z.null(), z.string()]).optional(), - "name": z.union([z.null(), z.string()]).optional(), + b: z.number(), + description: z.union([z.null(), z.string()]).optional(), + name: z.union([z.null(), z.string()]).optional(), }); -export type TestOnlyNestedBaseObject = z.infer; +export type TestOnlyNestedBaseObject = z.infer< + typeof TestOnlyNestedBaseObjectSchema +>; export const TestOnlyExampleSchema = z.object({ - "description": z.union([z.null(), z.string()]).optional(), - "name": z.union([z.null(), z.string()]).optional(), - "nested_base_model": TestOnlyNestedBaseModelSchema, - "nested_base_object": z.string(), - "primitive": z.number(), + description: z.union([z.null(), z.string()]).optional(), + name: z.union([z.null(), z.string()]).optional(), + nested_base_model: TestOnlyNestedBaseModelSchema, + nested_base_object: z.string(), + primitive: z.number(), }); export type TestOnlyExample = z.infer; export const GeneratedBaseObjectClassesZodSchema = z.object({ - "TestOnlyExample": TestOnlyExampleSchema, - "TestOnlyNestedBaseObject": TestOnlyNestedBaseObjectSchema, + TestOnlyExample: TestOnlyExampleSchema, + TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, }); -export type GeneratedBaseObjectClassesZod = z.infer; +export type GeneratedBaseObjectClassesZod = z.infer< + typeof GeneratedBaseObjectClassesZodSchema +>; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts index d84563a682f..d57b2fb9a67 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts @@ -205,7 +205,10 @@ export type TraceObjQueryReq = { metadata_only?: boolean; }; -export interface TraceObjSchema { +export interface TraceObjSchema< + T extends any = any, + OBC extends string = string +> { project_id: string; object_id: string; created_at: string; @@ -213,7 +216,7 @@ export interface TraceObjSchema { version_index: number; is_latest: number; kind: 'op' | 'object'; - base_object_class?: string; + base_object_class?: OBC; val: T; } diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts index 16b56273fca..25f11624740 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts @@ -395,7 +395,6 @@ export class DirectTraceServerClient { }; } - /** * Sanitizes an object name by replacing non-alphanumeric characters with dashes and enforcing length limits. * This matches the Python implementation in weave_client.py. @@ -421,4 +420,4 @@ export function sanitizeObjectId(name: string): string { } return res; -} \ No newline at end of file +} From 259e4c028fcd7cdd1bf77e60f324f85abd3743ef Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 00:28:28 -0600 Subject: [PATCH 07/27] Initial TS tests complete --- weave-js/package.json | 3 +- .../baseObjectClassQuery.test.ts | 90 +++++++++++++++++-- .../wfReactInterface/baseObjectClassQuery.ts | 12 +-- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/weave-js/package.json b/weave-js/package.json index 5897561a6c1..6dc7da04766 100644 --- a/weave-js/package.json +++ b/weave-js/package.json @@ -237,7 +237,8 @@ "typescript": "4.7.4", "uuid": "^9.0.0", "vite": "5.2.9", - "vitest": "^1.6.0" + "vitest": "^1.6.0", + "tsd": "^0.30.4" }, "resolutions": { "@types/react": "^17.0.26", diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts index 4bfe3ddd066..83cfc3060de 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts @@ -1,11 +1,52 @@ -import {useCollectionObjects} from './baseObjectClassQuery'; -import {TestOnlyExampleSchema} from './generatedBaseObjectClasses.zod'; -describe('useCollectionObjects', () => { - it('hasCorrectTypes', () => { - type CT = ReturnType>; - const exampleResult: CT = { +import {expectType} from 'tsd'; + +import { + useCollectionObjects, + useCreateCollectionObject, +} from './baseObjectClassQuery'; +import { + TestOnlyExample, + TestOnlyExampleSchema, +} from './generatedBaseObjectClasses.zod'; +import { + TraceObjCreateReq, + TraceObjCreateRes, + TraceObjSchema, +} from './traceServerClientTypes'; +import {Loadable} from './wfDataModelHooksInterface'; + +type TypesAreEqual = [T] extends [U] + ? [U] extends [T] + ? true + : false + : false; + +describe('Type Tests', () => { + it('useCollectionObjects return type matches expected structure', () => { + type CollectionObjectsReturn = ReturnType< + typeof useCollectionObjects<'TestOnlyExample'> + >; + + // Define the expected type structure + type ExpectedType = Loadable< + Array> + >; + + // Type assertion tests + type AssertTypesAreEqual = TypesAreEqual< + CollectionObjectsReturn, + ExpectedType + >; + type Assert = AssertTypesAreEqual extends true ? true : never; + + // This will fail compilation if the types don't match exactly + const _assert: Assert = true; + expect(_assert).toBe(true); + + // Additional runtime sample for documentation + const sampleResult: CollectionObjectsReturn = { loading: false, - data: [ + result: [ { project_id: '', object_id: '', @@ -27,7 +68,38 @@ describe('useCollectionObjects', () => { }, ], }; - // This should fail, looking for the correct way to validate types - expect(exampleResult).toBeNull(); + + expectType(sampleResult); + }); + + it('useCreateCollectionObject return type matches expected structure', () => { + type CreateCollectionObjectReturn = ReturnType< + typeof useCreateCollectionObject<'TestOnlyExample'> + >; + + // Define the expected type structure + type ExpectedType = ( + req: TraceObjCreateReq + ) => Promise; + + // Type assertion tests + type AssertTypesAreEqual = TypesAreEqual< + CreateCollectionObjectReturn, + ExpectedType + >; + type Assert = AssertTypesAreEqual extends true ? true : never; + + // This will fail compilation if the types don't match exactly + const _assert: Assert = true; + expect(_assert).toBe(true); + + // Additional runtime sample for documentation + const sampleResult: CreateCollectionObjectReturn = async req => { + return { + digest: '', + }; + }; + + expectType(sampleResult); }); }); diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 0d40cc4662f..945d0c0409b 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -14,21 +14,13 @@ import { TraceObjQueryReq, TraceObjSchema, } from './traceServerClientTypes'; +import {Loadable} from './wfDataModelHooksInterface'; const collectionRegistry = { TestOnlyExample: TestOnlyExampleSchema, TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, }; -type Loadable = - | { - loading: true; - } - | { - loading: false; - data: T; - }; - export const useCollectionObjects = < C extends keyof typeof collectionRegistry, T = z.infer<(typeof collectionRegistry)[C]> @@ -60,7 +52,7 @@ export const useCollectionObjects = < }; }, [client, collectionName, deepReq]); - return {data: objects, loading}; + return {result: objects, loading}; }; const getCollectionObjects = async < From ac471c4b7766d9eaaa19dcff031b2bedcf35d9b9 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 00:43:13 -0600 Subject: [PATCH 08/27] Initial TS tests complete --- weave-js/yarn.lock | 332 +++++++++++++++++- weave/scripts/generate_base_object_schemas.py | 13 +- .../base_object_registry.py | 1 + 3 files changed, 330 insertions(+), 16 deletions(-) diff --git a/weave-js/yarn.lock b/weave-js/yarn.lock index 8541ca9a5d7..1a0ce3ab43e 100644 --- a/weave-js/yarn.lock +++ b/weave-js/yarn.lock @@ -4150,6 +4150,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@tsd/typescript@~5.3.3": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@tsd/typescript/-/typescript-5.3.3.tgz#bc01854b6e0e746b5f70a6b48c30c7b95b81a74e" + integrity sha512-CQlfzol0ldaU+ftWuG52vH29uRoKboLinLy84wS8TQOu+m+tWoaUfk4svL4ij2V8M5284KymJBlHUusKj6k34w== + "@turf/area@^6.4.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@turf/area/-/area-6.5.0.tgz#1d0d7aee01d8a4a3d4c91663ed35cc615f36ad56" @@ -4261,6 +4266,19 @@ resolved "https://registry.yarnpkg.com/@types/downloadjs/-/downloadjs-1.4.3.tgz#01c0414a9756afa2bd438b1e2ec6c50de7df316d" integrity sha512-MjJepFle/tLtT2/jmDNth6ZnwWzEhm40L+olE5HKR70ISUCfgT55eqreeHldAzFLY2HDUGsn8zgyto8KygN0CA== +"@types/eslint@^7.2.13": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/estree@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" @@ -4363,6 +4381,11 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.6.tgz#4b3afd5158b8749095b1f096967b6d0f838d862f" integrity sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw== +"@types/json-schema@*": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -4407,6 +4430,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/minimist@^1.2.0": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" @@ -4432,6 +4460,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" integrity sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA== +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/numeral@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.2.tgz#8ea2c4f4e64c0cc948ad7da375f6f827778a7912" @@ -5323,6 +5356,11 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -5803,7 +5841,16 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase@^5.0.0: +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -6972,7 +7019,15 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -decamelize@^1.2.0: +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -7581,6 +7636,20 @@ eslint-config-react-app@^7.0.1: eslint-plugin-react-hooks "^4.3.0" eslint-plugin-testing-library "^5.0.1" +eslint-formatter-pretty@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz#7a6877c14ffe2672066c853587d89603e97c7708" + integrity sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ== + dependencies: + "@types/eslint" "^7.2.13" + ansi-escapes "^4.2.1" + chalk "^4.1.0" + eslint-rule-docs "^1.1.5" + log-symbols "^4.0.0" + plur "^4.0.0" + string-width "^4.2.0" + supports-hyperlinks "^2.0.0" + eslint-import-resolver-node@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" @@ -7744,6 +7813,11 @@ eslint-rule-composer@^0.3.0: resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== +eslint-rule-docs@^1.1.5: + version "1.1.235" + resolved "https://registry.yarnpkg.com/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz#be6ef1fc3525f17b3c859ae2997fedadc89bfb9b" + integrity sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A== + eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -8490,7 +8564,7 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^11.0.3, globby@^11.1.0: +globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -8737,6 +8811,11 @@ har-validator@~4.2.1: ajv "^4.9.1" har-schema "^1.0.5" +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + harmony-reflect@^1.4.6: version "1.6.2" resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" @@ -8819,6 +8898,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hast-util-from-parse5@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz#3089dc0ee2ccf6ec8bc416919b51a54a589e097c" @@ -9037,6 +9123,18 @@ hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react- dependencies: react-is "^16.7.0" +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + hsluv@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/hsluv/-/hsluv-0.0.3.tgz#829107dafb4a9f8b52a1809ed02e091eade6754c" @@ -9261,6 +9359,11 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +irregular-plurals@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.5.0.tgz#0835e6639aa8425bdc8b0d33d0dc4e89d9c01d2b" + integrity sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ== + is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" @@ -9363,6 +9466,13 @@ is-core-module@^2.11.0, is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -9674,6 +9784,16 @@ iterall@1.1.3: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" integrity sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ== +jest-diff@^29.0.3: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-diff@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" @@ -9694,6 +9814,11 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-matcher-utils@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" @@ -9862,6 +9987,11 @@ json-schema-to-typescript@^11.0.2: mz "^2.7.0" prettier "^2.6.2" +json-schema-to-zod@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/json-schema-to-zod/-/json-schema-to-zod-2.4.1.tgz#bd1e66b4ac4da4d3293e59d9a582b77d8ce9744b" + integrity sha512-aMoez9TxgnfLAIZaWTPaQ+j7rOt1K9Ew/TBI85XcnhcFlo/47b1MDgpi4r07XndLSZWOX/KsJiRJvhdzSvo2Dw== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -10063,6 +10193,11 @@ keyboard-key@^1.0.4: resolved "https://registry.yarnpkg.com/keyboard-key/-/keyboard-key-1.1.0.tgz#6f2e8e37fa11475bb1f1d65d5174f1b35653f5b7" integrity sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ== +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + kleur@^4.0.3: version "4.1.5" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" @@ -10323,6 +10458,16 @@ map-limit@0.0.1: dependencies: once "~1.3.0" +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + mapbox-gl@1.10.1: version "1.10.1" resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.10.1.tgz#7dbd53bdf2f78e45e125c1115e94dea286ef663c" @@ -10534,6 +10679,24 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" +meow@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" + integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize "^1.2.0" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -10881,6 +11044,15 @@ minimatch@^4.2.3: dependencies: brace-expansion "^1.1.7" +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -11211,6 +11383,26 @@ nopt@~1.0.10: dependencies: abbrev "1" +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -11847,6 +12039,13 @@ plotly.js@^2.23.2: webgl-context "^2.2.0" world-calendars "^1.0.3" +plur@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" + integrity sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg== + dependencies: + irregular-plurals "^3.2.0" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -12122,6 +12321,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" @@ -12552,6 +12756,25 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + readable-stream@4.5.2, readable-stream@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" @@ -13025,6 +13248,15 @@ resolve@^1.0.0, resolve@^1.1.10, resolve@^1.1.5, resolve@^1.1.7, resolve@^1.10.1 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.10.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.4: version "2.0.0-next.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" @@ -13247,6 +13479,11 @@ semantic-ui-react@^0.88.2: react-popper "^1.3.4" shallowequal "^1.1.0" +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.7.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + semver@7.x, semver@^7.3.5, semver@^7.3.7: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -13254,16 +13491,16 @@ semver@7.x, semver@^7.3.5, semver@^7.3.7: dependencies: lru-cache "^6.0.0" -semver@^5.3.0, semver@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -13513,6 +13750,32 @@ spawn-command@^0.0.2-1: resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.20" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz#e44ed19ed318dd1e5888f93325cee800f0f51b89" + integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw== + split-on-first@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" @@ -13820,7 +14083,7 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -13834,6 +14097,14 @@ supports-color@^8.1.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -14158,6 +14429,11 @@ trim-lines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -14243,6 +14519,19 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tsd@^0.30.4: + version "0.30.7" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.30.7.tgz#319a0403073df6d3f572c4089378901662554ae5" + integrity sha512-oTiJ28D6B/KXoU3ww/Eji+xqHJojiuPVMwA12g4KYX1O72N93Nb6P3P3h2OAhhf92Xl8NIhb/xFmBZd5zw/xUw== + dependencies: + "@tsd/typescript" "~5.3.3" + eslint-formatter-pretty "^4.1.0" + globby "^11.0.1" + jest-diff "^29.0.3" + meow "^9.0.0" + path-exists "^4.0.0" + read-pkg-up "^7.0.0" + tslib@>=1.10.0, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" @@ -14351,6 +14640,11 @@ type-detect@^4.0.0, type-detect@^4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -14361,6 +14655,16 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" @@ -14832,6 +15136,14 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -15735,7 +16047,7 @@ yaml@^2.4.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3" integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== -yargs-parser@20.x: +yargs-parser@20.x, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== diff --git a/weave/scripts/generate_base_object_schemas.py b/weave/scripts/generate_base_object_schemas.py index c78d5636cee..3be5ee1b2a8 100644 --- a/weave/scripts/generate_base_object_schemas.py +++ b/weave/scripts/generate_base_object_schemas.py @@ -9,20 +9,21 @@ OUTPUT_PATH = Path(__file__).parent / "generated_base_object_class_schemas.json" -def generate_schemas(): +def generate_schemas() -> None: """ Generate JSON schemas for all registered base objects in REGISTRY. - Creates a top-level union type of all registered objects and writes the schemas + Creates a top-level union type of all registered objects and writes the schemas to a file named 'base_object_schemas.json'. """ - top_level_schema = CompositeBaseObject.model_json_schema(mode='validation') - + top_level_schema = CompositeBaseObject.model_json_schema(mode="validation") + # Write schemas to file with OUTPUT_PATH.open("w") as f: json.dump(top_level_schema, f, indent=2) - + print(f"Generated union schema for {len(REGISTRY)} objects") print(f"Wrote schema to {OUTPUT_PATH.absolute()}") + if __name__ == "__main__": - generate_schemas() \ No newline at end of file + generate_schemas() diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py index b4b9ab7242d..6bdcce460b9 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_registry.py +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -10,6 +10,7 @@ "TestOnlyNestedBaseObject": TestOnlyNestedBaseObject, } + # This is a union type of all registered base objects. class CompositeBaseObject(BaseModel): TestOnlyExample: TestOnlyExample From e326b1730ecbb9d4b3640ad3059be07383ad4d02 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 01:36:27 -0600 Subject: [PATCH 09/27] Initial python tests complete --- tests/trace/test_base_object_classes.py | 80 +++++++++++++++++-- .../wfReactInterface/baseObjectClassQuery.ts | 1 + weave/scripts/generate_base_object_schemas.py | 4 +- weave/trace/serialize.py | 5 ++ weave/trace_server/base_object_class_util.py | 17 ++++ .../clickhouse_trace_server_batched.py | 15 +--- .../base_object_registry.py | 4 +- weave/trace_server/sqlite_trace_server.py | 15 +--- 8 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 weave/trace_server/base_object_class_util.py diff --git a/tests/trace/test_base_object_classes.py b/tests/trace/test_base_object_classes.py index c0c9ba25b14..9d4922becfc 100644 --- a/tests/trace/test_base_object_classes.py +++ b/tests/trace/test_base_object_classes.py @@ -12,6 +12,8 @@ 4. We ensure that invalid schemas are properly rejected from the server. """ +from typing import Literal, Optional + import pytest import weave @@ -21,13 +23,34 @@ from weave.trace_server import trace_server_interface as tsi +def with_base_object_class_annotations( + val: dict, + class_name: str, + base_object_name: Optional[Literal["Object", "BaseObject"]] = None, +): + """ + When serializing pydantic objects, add additional fields to indicate the class information. This is + a utlity to perform that mapping for the purposes of testing. We want to ensure that both the client + and server agree on this structure, therefore I am adding this utility here. + """ + bases = ["BaseModel"] + if base_object_name is not None: + bases.insert(0, base_object_name) + return { + **val, + "_type": class_name, + "_class_name": class_name, + "_bases": bases, + } + + def test_pythonic_creation(client: WeaveClient): # First, let's use the high-level pythonic creation API. nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) top_obj = base_objects.TestOnlyExample( primitive=1, nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), - nested_obj=weave.publish(nested_obj).uri(), + nested_base_object=weave.publish(nested_obj).uri(), ) ref = weave.publish(top_obj) @@ -36,7 +59,7 @@ def test_pythonic_creation(client: WeaveClient): assert isinstance(top_obj_gotten, base_objects.TestOnlyExample) assert top_obj_gotten.model_dump() == top_obj.model_dump() - objs = client.server.obj_query( + objs_res = client.server.objs_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -44,11 +67,37 @@ def test_pythonic_creation(client: WeaveClient): }, ) ) + objs = objs_res.objs assert len(objs) == 1 - assert objs[0].val == top_obj.model_dump() + assert ( + objs[0].val + == { + **with_base_object_class_annotations( + top_obj.model_dump(), "TestOnlyExample", "BaseObject" + ), + "nested_base_model": with_base_object_class_annotations( + top_obj.nested_base_model.model_dump(), "TestOnlyNestedBaseModel" + ), + } + == { + "_type": "TestOnlyExample", + "name": None, + "description": None, + "primitive": 1, + "nested_base_model": { + "_type": "TestOnlyNestedBaseModel", + "a": 2, + "_class_name": "TestOnlyNestedBaseModel", + "_bases": ["BaseModel"], + }, + "nested_base_object": "weave:///shawn/test-project/object/TestOnlyNestedBaseObject:JyFvHfyaJ79uCKpdZ3DD3if4NYam8QgTkzUlXQXAILI", + "_class_name": "TestOnlyExample", + "_bases": ["BaseObject", "BaseModel"], + } + ) - objs = client.server.obj_query( + objs_res = client.server.objs_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -56,9 +105,23 @@ def test_pythonic_creation(client: WeaveClient): }, ) ) + objs = objs_res.objs assert len(objs) == 1 - assert objs[0].val == nested_obj.model_dump() + assert ( + objs[0].val + == with_base_object_class_annotations( + nested_obj.model_dump(), "TestOnlyNestedBaseObject", "BaseObject" + ) + == { + "_type": "TestOnlyNestedBaseObject", + "name": None, + "description": None, + "b": 3, + "_class_name": "TestOnlyNestedBaseObject", + "_bases": ["BaseObject", "BaseModel"], + } + ) def test_interface_creation(client): @@ -115,7 +178,7 @@ def test_interface_creation(client): assert nested_obj_gotten.model_dump() == nested_obj.model_dump() - objs = client.server.obj_query( + objs_res = client.server.obj_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -124,10 +187,11 @@ def test_interface_creation(client): ) ) + objs = objs_res.objs assert len(objs) == 1 assert objs[0].val == top_obj.model_dump() - objs = client.server.obj_query( + objs_res = client.server.obj_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -135,7 +199,7 @@ def test_interface_creation(client): }, ) ) - + objs = objs_res.objs assert len(objs) == 1 assert objs[0].val == nested_obj.model_dump() diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 945d0c0409b..efb3bd3d817 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -16,6 +16,7 @@ import { } from './traceServerClientTypes'; import {Loadable} from './wfDataModelHooksInterface'; +// TODO: This should be generated from the registry! const collectionRegistry = { TestOnlyExample: TestOnlyExampleSchema, TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, diff --git a/weave/scripts/generate_base_object_schemas.py b/weave/scripts/generate_base_object_schemas.py index 3be5ee1b2a8..979565a09e1 100644 --- a/weave/scripts/generate_base_object_schemas.py +++ b/weave/scripts/generate_base_object_schemas.py @@ -2,7 +2,7 @@ from pathlib import Path from weave.trace_server.interface.base_object_classes.base_object_registry import ( - REGISTRY, + BASE_OBJECT_REGISTRY, CompositeBaseObject, ) @@ -21,7 +21,7 @@ def generate_schemas() -> None: with OUTPUT_PATH.open("w") as f: json.dump(top_level_schema, f, indent=2) - print(f"Generated union schema for {len(REGISTRY)} objects") + print(f"Generated union schema for {len(BASE_OBJECT_REGISTRY)} objects") print(f"Wrote schema to {OUTPUT_PATH.absolute()}") diff --git a/weave/trace/serialize.py b/weave/trace/serialize.py index 2f605e24b77..c37ae03b7a7 100644 --- a/weave/trace/serialize.py +++ b/weave/trace/serialize.py @@ -5,6 +5,9 @@ from weave.trace import custom_objs from weave.trace.object_record import ObjectRecord from weave.trace.refs import ObjectRef, TableRef, parse_uri +from weave.trace_server.interface.base_object_classes.base_object_registry import ( + BASE_OBJECT_REGISTRY, +) from weave.trace_server.trace_server_interface import ( FileContentReadReq, FileCreateReq, @@ -225,6 +228,8 @@ def from_json(obj: Any, project_id: str, server: TraceServerInterface) -> Any: return custom_objs.decode_custom_obj( obj["weave_type"], files, obj.get("load_op") ) + elif baseObject := BASE_OBJECT_REGISTRY.get(val_type): + return baseObject.model_validate(obj) else: return ObjectRecord( {k: from_json(v, project_id, server) for k, v in obj.items()} diff --git a/weave/trace_server/base_object_class_util.py b/weave/trace_server/base_object_class_util.py new file mode 100644 index 00000000000..50aea59084d --- /dev/null +++ b/weave/trace_server/base_object_class_util.py @@ -0,0 +1,17 @@ +from typing import Any, Optional + +base_object_class_names = ["BaseObject", "Object"] + + +def get_base_object_class(val: Any) -> Optional[str]: + if isinstance(val, dict): + if "_bases" in val: + if isinstance(val["_bases"], list): + if len(val["_bases"]) >= 2: + if val["_bases"][-1] == "BaseModel": + if val["_bases"][-2] in base_object_class_names: + if len(val["_bases"]) > 2: + return val["_bases"][-3] + elif "_class_name" in val: + return val["_class_name"] + return None diff --git a/weave/trace_server/clickhouse_trace_server_batched.py b/weave/trace_server/clickhouse_trace_server_batched.py index eb4ce265f70..9ed9533787e 100644 --- a/weave/trace_server/clickhouse_trace_server_batched.py +++ b/weave/trace_server/clickhouse_trace_server_batched.py @@ -53,6 +53,7 @@ from weave.trace_server import environment as wf_env from weave.trace_server import refs_internal as ri from weave.trace_server import trace_server_interface as tsi +from weave.trace_server.base_object_class_util import get_base_object_class from weave.trace_server.calls_query_builder import ( CallsQuery, HardCodedFilter, @@ -2059,20 +2060,6 @@ def get_kind(val: Any) -> str: return "object" -def get_base_object_class(val: Any) -> Optional[str]: - if isinstance(val, dict): - if "_bases" in val: - if isinstance(val["_bases"], list): - if len(val["_bases"]) >= 2: - if val["_bases"][-1] == "BaseModel": - if val["_bases"][-2] == "Object": - if len(val["_bases"]) > 2: - return val["_bases"][-3] - elif "_class_name" in val: - return val["_class_name"] - return None - - def find_call_descendants( root_ids: list[str], all_calls: list[tsi.CallSchema], diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py index 6bdcce460b9..43191f43671 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_registry.py +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -5,13 +5,13 @@ from weave.trace_server.interface.base_object_classes.base_object_def import BaseObject from weave.trace_server.interface.base_object_classes.test_only_example import * -REGISTRY: Dict[str, Type[BaseObject]] = { +BASE_OBJECT_REGISTRY: Dict[str, Type[BaseObject]] = { "TestOnlyExample": TestOnlyExample, "TestOnlyNestedBaseObject": TestOnlyNestedBaseObject, } -# This is a union type of all registered base objects. +# TODO: Remove this helper class CompositeBaseObject(BaseModel): TestOnlyExample: TestOnlyExample TestOnlyNestedBaseObject: TestOnlyNestedBaseObject diff --git a/weave/trace_server/sqlite_trace_server.py b/weave/trace_server/sqlite_trace_server.py index 93a4f510090..51b51e7ca64 100644 --- a/weave/trace_server/sqlite_trace_server.py +++ b/weave/trace_server/sqlite_trace_server.py @@ -13,6 +13,7 @@ from weave.trace_server import refs_internal as ri from weave.trace_server import trace_server_interface as tsi +from weave.trace_server.base_object_class_util import get_base_object_class from weave.trace_server.emoji_util import detone_emojis from weave.trace_server.errors import InvalidRequest from weave.trace_server.feedback import ( @@ -1205,20 +1206,6 @@ def get_kind(val: Any) -> str: return "object" -def get_base_object_class(val: Any) -> Optional[str]: - if isinstance(val, dict): - if "_bases" in val: - if isinstance(val["_bases"], list): - if len(val["_bases"]) >= 2: - if val["_bases"][-1] == "BaseModel": - if val["_bases"][-2] == "Object": - if len(val["_bases"]) > 2: - return val["_bases"][-3] - elif "_class_name" in val: - return val["_class_name"] - return None - - def _transform_external_calls_field_to_internal_calls_field( field: str, cast: Optional[str] = None, From 154de4798d096fdec5dbacc822813e69a73c278f Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 02:06:15 -0600 Subject: [PATCH 10/27] Typescript improvements --- .../baseObjectClassQuery.test.ts | 8 +- .../wfReactInterface/baseObjectClassQuery.ts | 79 ++++++++++--------- .../traceServerClientTypes.ts | 1 + 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts index 83cfc3060de..9918ae7f285 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.test.ts @@ -1,8 +1,8 @@ import {expectType} from 'tsd'; import { - useCollectionObjects, - useCreateCollectionObject, + useBaseObjectInstances, + useCreateBaseObjectInstance, } from './baseObjectClassQuery'; import { TestOnlyExample, @@ -24,7 +24,7 @@ type TypesAreEqual = [T] extends [U] describe('Type Tests', () => { it('useCollectionObjects return type matches expected structure', () => { type CollectionObjectsReturn = ReturnType< - typeof useCollectionObjects<'TestOnlyExample'> + typeof useBaseObjectInstances<'TestOnlyExample'> >; // Define the expected type structure @@ -74,7 +74,7 @@ describe('Type Tests', () => { it('useCreateCollectionObject return type matches expected structure', () => { type CreateCollectionObjectReturn = ReturnType< - typeof useCreateCollectionObject<'TestOnlyExample'> + typeof useCreateBaseObjectInstance<'TestOnlyExample'> >; // Define the expected type structure diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index efb3bd3d817..3d2f4513e45 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -17,16 +17,16 @@ import { import {Loadable} from './wfDataModelHooksInterface'; // TODO: This should be generated from the registry! -const collectionRegistry = { +const baseObjectClassRegistry = { TestOnlyExample: TestOnlyExampleSchema, TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, }; -export const useCollectionObjects = < - C extends keyof typeof collectionRegistry, - T = z.infer<(typeof collectionRegistry)[C]> +export const useBaseObjectInstances = < + C extends keyof typeof baseObjectClassRegistry, + T = z.infer<(typeof baseObjectClassRegistry)[C]> >( - collectionName: C, + baseObjectClassName: C, req: TraceObjQueryReq ): Loadable>> => { const [objects, setObjects] = useState>>([]); @@ -40,7 +40,7 @@ export const useCollectionObjects = < let isMounted = true; setLoading(true); currReq.current = deepReq; - getCollectionObjects(client, collectionName, deepReq).then( + getBaseObjectInstances(client, baseObjectClassName, deepReq).then( collectionObjects => { if (isMounted && currReq.current === deepReq) { setObjects(collectionObjects as Array>); @@ -51,68 +51,77 @@ export const useCollectionObjects = < return () => { isMounted = false; }; - }, [client, collectionName, deepReq]); + }, [client, baseObjectClassName, deepReq]); return {result: objects, loading}; }; -const getCollectionObjects = async < - C extends keyof typeof collectionRegistry, - T = z.infer<(typeof collectionRegistry)[C]> +const getBaseObjectInstances = async < + C extends keyof typeof baseObjectClassRegistry, + T = z.infer<(typeof baseObjectClassRegistry)[C]> >( client: TraceServerClient, - collectionName: C, + baseObjectClassName: C, req: TraceObjQueryReq ): Promise>> => { - const knownCollection = collectionRegistry[collectionName]; - if (!knownCollection) { - console.warn(`Unknown collection: ${collectionName}`); + const knownObjectClass = baseObjectClassRegistry[baseObjectClassName]; + if (!knownObjectClass) { + console.warn(`Unknown object class: ${baseObjectClassName}`); return []; } - const reqWithCollection: TraceObjQueryReq = { + const reqWithBaseObjectClass: TraceObjQueryReq = { ...req, - filter: {...req.filter, base_object_classes: [collectionName]}, + filter: {...req.filter, base_object_classes: [baseObjectClassName]}, }; - const objectPromise = client.objsQuery(reqWithCollection); + const objectPromise = client.objsQuery(reqWithBaseObjectClass); const objects = await objectPromise; return objects.objs - .map(obj => ({obj, parsed: knownCollection.safeParse(obj.val)})) + .map(obj => ({obj, parsed: knownObjectClass.safeParse(obj.val)})) .filter(({parsed}) => parsed.success) .map(({obj, parsed}) => ({...obj, val: parsed.data!})) as Array< TraceObjSchema >; }; -export const useCreateCollectionObject = < - C extends keyof typeof collectionRegistry, - T = z.infer<(typeof collectionRegistry)[C]> +export const useCreateBaseObjectInstance = < + C extends keyof typeof baseObjectClassRegistry, + T = z.infer<(typeof baseObjectClassRegistry)[C]> >( - collectionName: C + baseObjectClassName: C ): ((req: TraceObjCreateReq) => Promise) => { const getTsClient = useGetTraceServerClientContext(); const client = getTsClient(); return (req: TraceObjCreateReq) => - createCollectionObject(client, collectionName, req); + createBaseObjectInstance(client, baseObjectClassName, req); }; -const createCollectionObject = async < - C extends keyof typeof collectionRegistry, - T = z.infer<(typeof collectionRegistry)[C]> +const createBaseObjectInstance = async < + C extends keyof typeof baseObjectClassRegistry, + T = z.infer<(typeof baseObjectClassRegistry)[C]> >( client: TraceServerClient, - collectionName: C, + baseObjectClassName: C, req: TraceObjCreateReq ): Promise => { - const knownCollection = collectionRegistry[collectionName]; - if (!knownCollection) { - throw new Error(`Unknown collection: ${collectionName}`); + if ( + req.obj.set_base_object_class != null && + req.obj.set_base_object_class !== baseObjectClassName + ) { + throw new Error( + `set_base_object_class must match baseObjectClassName: ${baseObjectClassName}` + ); } - const verifiedObject = knownCollection.safeParse(req.obj.val); + const knownBaseObjectClass = baseObjectClassRegistry[baseObjectClassName]; + if (!knownBaseObjectClass) { + throw new Error(`Unknown object class: ${baseObjectClassName}`); + } + + const verifiedObject = knownBaseObjectClass.safeParse(req.obj.val); if (!verifiedObject.success) { throw new Error( @@ -120,15 +129,13 @@ const createCollectionObject = async < ); } - const reqWithCollection: TraceObjCreateReq = { + const reqWithBaseObjectClass: TraceObjCreateReq = { ...req, obj: { ...req.obj, - val: {...req.obj.val, _bases: [collectionName, 'BaseModel']}, + set_base_object_class: baseObjectClassName, }, }; - const createPromse = client.objCreate(reqWithCollection); - - return createPromse; + return client.objCreate(reqWithBaseObjectClass); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts index d57b2fb9a67..d7219af5834 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts @@ -239,6 +239,7 @@ export type TraceObjCreateReq = { project_id: string; object_id: string; val: T; + set_base_object_class?: string; }; }; From 454058c8a196f3c858ae65f41ac4b59124ad2286 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 02:41:41 -0600 Subject: [PATCH 11/27] Python Tests complete --- tests/trace/test_base_object_classes.py | 125 +++++++++++++++--- weave/trace_server/base_object_class_util.py | 68 +++++++++- .../clickhouse_trace_server_batched.py | 21 +-- weave/trace_server/sqlite_trace_server.py | 23 ++-- weave/trace_server/trace_server_interface.py | 1 + 5 files changed, 201 insertions(+), 37 deletions(-) diff --git a/tests/trace/test_base_object_classes.py b/tests/trace/test_base_object_classes.py index 9d4922becfc..2d52fd1dc27 100644 --- a/tests/trace/test_base_object_classes.py +++ b/tests/trace/test_base_object_classes.py @@ -15,6 +15,7 @@ from typing import Literal, Optional import pytest +from pydantic import ValidationError import weave from weave.trace import base_objects @@ -126,7 +127,7 @@ def test_pythonic_creation(client: WeaveClient): def test_interface_creation(client): # Now we will do the equivant operation using low-level interface. - nested_obj_id = "nested_obj" + nested_obj_id = "TestOnlyNestedBaseObject" nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) nested_obj_res = client.server.obj_create( tsi.ObjCreateReq.model_validate( @@ -135,6 +136,7 @@ def test_interface_creation(client): "project_id": client._project_id(), "object_id": nested_obj_id, "val": nested_obj.model_dump(), + "set_base_object_class": "TestOnlyNestedBaseObject", } } ) @@ -143,14 +145,14 @@ def test_interface_creation(client): entity=client.entity, project=client.project, name=nested_obj_id, - digest=nested_obj_res.digest, + _digest=nested_obj_res.digest, ) - top_level_obj_id = "top_obj" + top_level_obj_id = "TestOnlyExample" top_obj = base_objects.TestOnlyExample( primitive=1, nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), - nested_obj=nested_obj_ref.uri(), + nested_base_object=nested_obj_ref.uri(), ) top_obj_res = client.server.obj_create( tsi.ObjCreateReq.model_validate( @@ -159,6 +161,7 @@ def test_interface_creation(client): "project_id": client._project_id(), "object_id": top_level_obj_id, "val": top_obj.model_dump(), + "set_base_object_class": "TestOnlyExample", } } ) @@ -167,7 +170,7 @@ def test_interface_creation(client): entity=client.entity, project=client.project, name=top_level_obj_id, - digest=top_obj_res.digest, + _digest=top_obj_res.digest, ) top_obj_gotten = weave.ref(top_obj_ref.uri()).get() @@ -178,7 +181,7 @@ def test_interface_creation(client): assert nested_obj_gotten.model_dump() == nested_obj.model_dump() - objs_res = client.server.obj_query( + objs_res = client.server.objs_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -189,9 +192,34 @@ def test_interface_creation(client): objs = objs_res.objs assert len(objs) == 1 - assert objs[0].val == top_obj.model_dump() + assert ( + objs[0].val + == { + **with_base_object_class_annotations( + top_obj.model_dump(), "TestOnlyExample", "BaseObject" + ), + "nested_base_model": with_base_object_class_annotations( + top_obj.nested_base_model.model_dump(), "TestOnlyNestedBaseModel" + ), + } + == { + "_type": "TestOnlyExample", + "name": None, + "description": None, + "primitive": 1, + "nested_base_model": { + "_type": "TestOnlyNestedBaseModel", + "a": 2, + "_class_name": "TestOnlyNestedBaseModel", + "_bases": ["BaseModel"], + }, + "nested_base_object": "weave:///shawn/test-project/object/TestOnlyNestedBaseObject:JyFvHfyaJ79uCKpdZ3DD3if4NYam8QgTkzUlXQXAILI", + "_class_name": "TestOnlyExample", + "_bases": ["BaseObject", "BaseModel"], + } + ) - objs_res = client.server.obj_query( + objs_res = client.server.objs_query( tsi.ObjQueryReq.model_validate( { "project_id": client._project_id(), @@ -201,23 +229,37 @@ def test_interface_creation(client): ) objs = objs_res.objs assert len(objs) == 1 - assert objs[0].val == nested_obj.model_dump() + assert ( + objs[0].val + == with_base_object_class_annotations( + nested_obj.model_dump(), "TestOnlyNestedBaseObject", "BaseObject" + ) + == { + "_type": "TestOnlyNestedBaseObject", + "name": None, + "description": None, + "b": 3, + "_class_name": "TestOnlyNestedBaseObject", + "_bases": ["BaseObject", "BaseModel"], + } + ) def test_digest_equality(client): # Next, let's make sure that the digests are all equivalent - nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) + nested_ref = weave.publish(nested_obj) top_obj = base_objects.TestOnlyExample( primitive=1, nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), - nested_obj=weave.publish(nested_obj).uri(), + nested_base_object=nested_ref.uri(), ) ref = weave.publish(top_obj) - pythonic_digest = ref.digest + nested_pythonic_digest = nested_ref.digest + top_level_pythonic_digest = ref.digest # Now we will do the equivant operation using low-level interface. - nested_obj_id = "nested_obj" + nested_obj_id = "TestOnlyNestedBaseObject" nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) nested_obj_res = client.server.obj_create( tsi.ObjCreateReq.model_validate( @@ -226,6 +268,7 @@ def test_digest_equality(client): "project_id": client._project_id(), "object_id": nested_obj_id, "val": nested_obj.model_dump(), + "set_base_object_class": "TestOnlyNestedBaseObject", } } ) @@ -234,14 +277,18 @@ def test_digest_equality(client): entity=client.entity, project=client.project, name=nested_obj_id, - digest=nested_obj_res.digest, + _digest=nested_obj_res.digest, ) - top_level_obj_id = "top_obj" + nested_interface_style_digest = nested_obj_ref.digest + + assert nested_pythonic_digest == nested_interface_style_digest + + top_level_obj_id = "TestOnlyExample" top_obj = base_objects.TestOnlyExample( primitive=1, nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), - nested_obj=nested_obj_ref.uri(), + nested_base_object=nested_obj_ref.uri(), ) top_obj_res = client.server.obj_create( tsi.ObjCreateReq.model_validate( @@ -250,26 +297,66 @@ def test_digest_equality(client): "project_id": client._project_id(), "object_id": top_level_obj_id, "val": top_obj.model_dump(), + "set_base_object_class": "TestOnlyExample", } } ) ) - interface_style_digest = top_obj_res.digest + top_level_interface_style_digest = top_obj_res.digest - assert pythonic_digest == interface_style_digest + assert top_level_pythonic_digest == top_level_interface_style_digest def test_schema_validation(client): # Test that we can't create an object with the wrong schema - with pytest.raises(): + with pytest.raises(ValidationError): client.server.obj_create( tsi.ObjCreateReq.model_validate( { "obj": { "project_id": client._project_id(), "object_id": "nested_obj", + # Incorrect schema, should raise! "val": {"a": 2}, + "set_base_object_class": "TestOnlyNestedBaseObject", + } + } + ) + ) + + # Correct schema, should work + client.server.obj_create( + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": "nested_obj", + "val": { + "b": 2, + "_class_name": "TestOnlyNestedBaseObject", + "_bases": ["BaseObject", "BaseModel"], + }, + "set_base_object_class": "TestOnlyNestedBaseObject", + } + } + ) + ) + + with pytest.raises(ValueError): + # Mismatching base object class, should raise + client.server.obj_create( + tsi.ObjCreateReq.model_validate( + { + "obj": { + "project_id": client._project_id(), + "object_id": "nested_obj", + "val": { + "b": 2, + "_class_name": "TestOnlyNestedBaseObject", + "_bases": ["BaseObject", "BaseModel"], + }, + "set_base_object_class": "TestOnlyExample", } } ) diff --git a/weave/trace_server/base_object_class_util.py b/weave/trace_server/base_object_class_util.py index 50aea59084d..5fdce65709b 100644 --- a/weave/trace_server/base_object_class_util.py +++ b/weave/trace_server/base_object_class_util.py @@ -1,4 +1,10 @@ -from typing import Any, Optional +from typing import Any, Optional, Tuple + +from pydantic import BaseModel + +from weave.trace_server.interface.base_object_classes.base_object_registry import ( + BASE_OBJECT_REGISTRY, +) base_object_class_names = ["BaseObject", "Object"] @@ -15,3 +21,63 @@ def get_base_object_class(val: Any) -> Optional[str]: elif "_class_name" in val: return val["_class_name"] return None + + +def process_incoming_object( + dict_val: dict, req_base_object_class: Optional[str] = None +) -> Tuple[dict, Optional[str]]: + dict_val = dict_val.copy() + val_base_object_class = get_base_object_class(dict_val) + + if ( + val_base_object_class != None + and req_base_object_class != None + and val_base_object_class != req_base_object_class + ): + raise ValueError( + f"set_base_object_class must match base_object_class: {val_base_object_class} != {req_base_object_class}" + ) + + if val_base_object_class is not None: + # In this case, we simply validate if the match is found + if base_object_class_type := BASE_OBJECT_REGISTRY.get(val_base_object_class): + base_object_class_type.model_validate(dict_val) + elif req_base_object_class is not None: + # In this case, we require that the base object class is registered + if base_object_class_type := BASE_OBJECT_REGISTRY.get(req_base_object_class): + dict_val = dump_base_object(base_object_class_type.model_validate(dict_val)) + else: + raise ValueError(f"Unknown base object class: {req_base_object_class}") + + base_object_class = val_base_object_class or req_base_object_class + + return dict_val, base_object_class + + +# Server-side version of `pydantic_object_record` +def dump_base_object(val: BaseModel) -> dict: + cls = val.__class__ + cls_name = val.__class__.__name__ + bases = [c.__name__ for c in cls.mro()[1:-1]] + + dump = {} + # Order matters here due to the way we calculate the digest! + # This matches the client + dump["_type"] = cls_name + for k in val.model_fields: + dump[k] = _general_dump(getattr(val, k)) + # yes, this is done twice, to match the client + dump["_class_name"] = cls_name + dump["_bases"] = bases + return dump + + +def _general_dump(val: Any) -> Any: + if isinstance(val, BaseModel): + return dump_base_object(val) + elif isinstance(val, dict): + return {k: _general_dump(v) for k, v in val.items()} + elif isinstance(val, list): + return [_general_dump(v) for v in val] + else: + return val diff --git a/weave/trace_server/clickhouse_trace_server_batched.py b/weave/trace_server/clickhouse_trace_server_batched.py index 9ed9533787e..47f5380fa43 100644 --- a/weave/trace_server/clickhouse_trace_server_batched.py +++ b/weave/trace_server/clickhouse_trace_server_batched.py @@ -53,7 +53,9 @@ from weave.trace_server import environment as wf_env from weave.trace_server import refs_internal as ri from weave.trace_server import trace_server_interface as tsi -from weave.trace_server.base_object_class_util import get_base_object_class +from weave.trace_server.base_object_class_util import ( + process_incoming_object, +) from weave.trace_server.calls_query_builder import ( CallsQuery, HardCodedFilter, @@ -600,16 +602,19 @@ def ops_query(self, req: tsi.OpQueryReq) -> tsi.OpQueryRes: return tsi.OpQueryRes(op_objs=objs) def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: - json_val = json.dumps(req.obj.val) + dict_val, base_object_class = process_incoming_object( + req.obj.val, req.obj.set_base_object_class + ) + + json_val = json.dumps(dict_val) digest = str_digest(json_val) - req_obj = req.obj ch_obj = ObjCHInsertable( - project_id=req_obj.project_id, - object_id=req_obj.object_id, - kind=get_kind(req.obj.val), - base_object_class=get_base_object_class(req.obj.val), - refs=extract_refs_from_values(req.obj.val), + project_id=req.obj.project_id, + object_id=req.obj.object_id, + kind=get_kind(dict_val), + base_object_class=base_object_class, + refs=extract_refs_from_values(dict_val), val_dump=json_val, digest=digest, ) diff --git a/weave/trace_server/sqlite_trace_server.py b/weave/trace_server/sqlite_trace_server.py index 51b51e7ca64..78221df2d2f 100644 --- a/weave/trace_server/sqlite_trace_server.py +++ b/weave/trace_server/sqlite_trace_server.py @@ -13,7 +13,9 @@ from weave.trace_server import refs_internal as ri from weave.trace_server import trace_server_interface as tsi -from weave.trace_server.base_object_class_util import get_base_object_class +from weave.trace_server.base_object_class_util import ( + process_incoming_object, +) from weave.trace_server.emoji_util import detone_emojis from weave.trace_server.errors import InvalidRequest from weave.trace_server.feedback import ( @@ -609,25 +611,28 @@ def ops_query(self, req: tsi.OpQueryReq) -> tsi.OpQueryRes: def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: conn, cursor = get_conn_cursor(self.db_path) - json_val = json.dumps(req.obj.val) + + dict_val, base_object_class = process_incoming_object( + req.obj.val, req.obj.set_base_object_class + ) + json_val = json.dumps(dict_val) digest = str_digest(json_val) # Validate object_id_validator(req.obj.object_id) - req_obj = req.obj # TODO: version index isn't right here, what if we delete stuff? with self.lock: cursor.execute("BEGIN TRANSACTION") # Mark all existing objects with such id as not latest cursor.execute( """UPDATE objects SET is_latest = 0 WHERE project_id = ? AND object_id = ?""", - (req_obj.project_id, req_obj.object_id), + (req.obj.project_id, req.obj.object_id), ) # first get version count cursor.execute( """SELECT COUNT(*) FROM objects WHERE project_id = ? AND object_id = ?""", - (req_obj.project_id, req_obj.object_id), + (req.obj.project_id, req.obj.object_id), ) version_index = cursor.fetchone()[0] @@ -645,11 +650,11 @@ def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: is_latest ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( - req_obj.project_id, - req_obj.object_id, + req.obj.project_id, + req.obj.object_id, datetime.datetime.now().isoformat(), - get_kind(req_obj.val), - get_base_object_class(req_obj.val), + get_kind(dict_val), + base_object_class, json.dumps([]), json_val, digest, diff --git a/weave/trace_server/trace_server_interface.py b/weave/trace_server/trace_server_interface.py index abdfeae38ac..af8778dc1d3 100644 --- a/weave/trace_server/trace_server_interface.py +++ b/weave/trace_server/trace_server_interface.py @@ -190,6 +190,7 @@ class ObjSchemaForInsert(BaseModel): project_id: str object_id: str val: Any + set_base_object_class: Optional[str] = None class TableSchemaForInsert(BaseModel): From ec3959f8237f97b16122151f68e70bf4ca987c5a Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 02:50:22 -0600 Subject: [PATCH 12/27] Attempted fix --- .../pages/wfReactInterface/baseObjectClassQuery.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 3d2f4513e45..79a3d03e86a 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -17,7 +17,7 @@ import { import {Loadable} from './wfDataModelHooksInterface'; // TODO: This should be generated from the registry! -const baseObjectClassRegistry = { +const baseObjectClassRegistry: Record = { TestOnlyExample: TestOnlyExampleSchema, TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, }; @@ -82,9 +82,9 @@ const getBaseObjectInstances = async < return objects.objs .map(obj => ({obj, parsed: knownObjectClass.safeParse(obj.val)})) .filter(({parsed}) => parsed.success) - .map(({obj, parsed}) => ({...obj, val: parsed.data!})) as Array< - TraceObjSchema - >; + .map( + ({obj, parsed}) => ({...obj, val: parsed.data} as TraceObjSchema) + ); }; export const useCreateBaseObjectInstance = < From 00516d0d6cf636c7d585696900ce4f25bfaa4dfa Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 02:55:03 -0600 Subject: [PATCH 13/27] Attempted fix --- .../Browse3/pages/wfReactInterface/baseObjectClassQuery.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 79a3d03e86a..b1f7b4b148f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -64,7 +64,8 @@ const getBaseObjectInstances = async < baseObjectClassName: C, req: TraceObjQueryReq ): Promise>> => { - const knownObjectClass = baseObjectClassRegistry[baseObjectClassName]; + const knownObjectClass: z.ZodType = + baseObjectClassRegistry[baseObjectClassName]; if (!knownObjectClass) { console.warn(`Unknown object class: ${baseObjectClassName}`); return []; @@ -79,9 +80,13 @@ const getBaseObjectInstances = async < const objects = await objectPromise; + // We would expect that this filtering does not filter anything + // out because the backend enforces the base object class, but this + // is here as a sanity check. return objects.objs .map(obj => ({obj, parsed: knownObjectClass.safeParse(obj.val)})) .filter(({parsed}) => parsed.success) + .filter(({obj}) => obj.base_object_class === baseObjectClassName) .map( ({obj, parsed}) => ({...obj, val: parsed.data} as TraceObjSchema) ); From a81daed63299064508ebebeea5d7e2164fb29300 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 03:00:06 -0600 Subject: [PATCH 14/27] clean --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d152017c58b..6c1ed017c0f 100644 --- a/Makefile +++ b/Makefile @@ -18,4 +18,4 @@ prepare-release: docs build synchronize-base-object-schemas: cd weave && make generate_base_object_schemas && \ - cd ../weave-js && yarn generate-schemas \ No newline at end of file + cd ../weave-js && yarn generate-schemas From 45c1756e553ca54f2afa1b4f2abb868f8302bbe5 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 03:08:48 -0600 Subject: [PATCH 15/27] clean --- weave/trace/weave_client.py | 2 +- .../interface/base_object_classes/test_only_example.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index 656bf1c360f..7f4d1d88ef0 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -1232,7 +1232,7 @@ def _save_nested_objects(self, obj: Any, name: Optional[str] = None) -> Any: # Case 1: Object: # Here we recurse into each of the properties of the object # and save them, and then save the object itself. - if isinstance(obj, Object): # TODO: add the generic object here + if isinstance(obj, Object): obj_rec = pydantic_object_record(obj) for v in obj_rec.__dict__.values(): self._save_nested_objects(v) diff --git a/weave/trace_server/interface/base_object_classes/test_only_example.py b/weave/trace_server/interface/base_object_classes/test_only_example.py index b93af28790e..87a48907eca 100644 --- a/weave/trace_server/interface/base_object_classes/test_only_example.py +++ b/weave/trace_server/interface/base_object_classes/test_only_example.py @@ -17,6 +17,9 @@ class TestOnlyExample(base_object_def.BaseObject): # Important: `RefStr` is just an alias for `str`. When defining `BaseObject`s, we # should never have a property point to another `BaseObject`. This is because each # base object is stored in the database and should be treated like a foreign key. + # + # It would be nice to have a way to ensure that no `BaseObject` has any `BaseObject` + # properties. nested_base_object: base_object_def.RefStr From 5e4457d815f7c343f98a1cd481ee0942bdb2f4c5 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 03:32:43 -0600 Subject: [PATCH 16/27] fixed bug --- weave/trace_server/base_object_class_util.py | 11 +++++++++-- weave/trace_server/clickhouse_trace_server_batched.py | 8 ++++---- weave/trace_server/sqlite_trace_server.py | 6 +++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/weave/trace_server/base_object_class_util.py b/weave/trace_server/base_object_class_util.py index 5fdce65709b..698b422aad1 100644 --- a/weave/trace_server/base_object_class_util.py +++ b/weave/trace_server/base_object_class_util.py @@ -24,9 +24,16 @@ def get_base_object_class(val: Any) -> Optional[str]: def process_incoming_object( - dict_val: dict, req_base_object_class: Optional[str] = None + val: Any, req_base_object_class: Optional[str] = None ) -> Tuple[dict, Optional[str]]: - dict_val = dict_val.copy() + if not isinstance(val, dict): + if req_base_object_class is not None: + raise ValueError( + "set_base_object_class cannot be provided for non-dict objects" + ) + return val, None + + dict_val = val.copy() val_base_object_class = get_base_object_class(dict_val) if ( diff --git a/weave/trace_server/clickhouse_trace_server_batched.py b/weave/trace_server/clickhouse_trace_server_batched.py index 9c880056707..cb032104f6a 100644 --- a/weave/trace_server/clickhouse_trace_server_batched.py +++ b/weave/trace_server/clickhouse_trace_server_batched.py @@ -599,19 +599,19 @@ def ops_query(self, req: tsi.OpQueryReq) -> tsi.OpQueryRes: return tsi.OpQueryRes(op_objs=objs) def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: - dict_val, base_object_class = process_incoming_object( + val, base_object_class = process_incoming_object( req.obj.val, req.obj.set_base_object_class ) - json_val = json.dumps(dict_val) + json_val = json.dumps(val) digest = str_digest(json_val) ch_obj = ObjCHInsertable( project_id=req.obj.project_id, object_id=req.obj.object_id, - kind=get_kind(dict_val), + kind=get_kind(val), base_object_class=base_object_class, - refs=extract_refs_from_values(dict_val), + refs=extract_refs_from_values(val), val_dump=json_val, digest=digest, ) diff --git a/weave/trace_server/sqlite_trace_server.py b/weave/trace_server/sqlite_trace_server.py index 78221df2d2f..1fd50cf1cb2 100644 --- a/weave/trace_server/sqlite_trace_server.py +++ b/weave/trace_server/sqlite_trace_server.py @@ -612,10 +612,10 @@ def ops_query(self, req: tsi.OpQueryReq) -> tsi.OpQueryRes: def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: conn, cursor = get_conn_cursor(self.db_path) - dict_val, base_object_class = process_incoming_object( + val, base_object_class = process_incoming_object( req.obj.val, req.obj.set_base_object_class ) - json_val = json.dumps(dict_val) + json_val = json.dumps(val) digest = str_digest(json_val) # Validate @@ -653,7 +653,7 @@ def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes: req.obj.project_id, req.obj.object_id, datetime.datetime.now().isoformat(), - get_kind(dict_val), + get_kind(val), base_object_class, json.dumps([]), json_val, From 10f7e1d5310b5d929376d0c289f512d9799c15d9 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 03:52:08 -0600 Subject: [PATCH 17/27] maybe fix --- weave/trace/serialize.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/weave/trace/serialize.py b/weave/trace/serialize.py index c37ae03b7a7..09df639e91a 100644 --- a/weave/trace/serialize.py +++ b/weave/trace/serialize.py @@ -228,7 +228,11 @@ def from_json(obj: Any, project_id: str, server: TraceServerInterface) -> Any: return custom_objs.decode_custom_obj( obj["weave_type"], files, obj.get("load_op") ) - elif baseObject := BASE_OBJECT_REGISTRY.get(val_type): + elif ( + isinstance(val_type, str) + and obj.get("_class_name") == val_type + and (baseObject := BASE_OBJECT_REGISTRY.get(val_type)) + ): return baseObject.model_validate(obj) else: return ObjectRecord( From 166dbdb0fd4b748e4c2cd8fc43544d1f3bff66b1 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 04:10:06 -0600 Subject: [PATCH 18/27] maybe fix --- tests/trace/test_base_object_classes.py | 11 +++++++---- .../base_object_classes/test_only_example.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/trace/test_base_object_classes.py b/tests/trace/test_base_object_classes.py index 2d52fd1dc27..a264941f7b0 100644 --- a/tests/trace/test_base_object_classes.py +++ b/tests/trace/test_base_object_classes.py @@ -22,6 +22,9 @@ from weave.trace.refs import ObjectRef from weave.trace.weave_client import WeaveClient from weave.trace_server import trace_server_interface as tsi +from weave.trace_server.interface.base_object_classes.test_only_example import ( + TestOnlyNestedBaseModel, +) def with_base_object_class_annotations( @@ -50,7 +53,7 @@ def test_pythonic_creation(client: WeaveClient): nested_obj = base_objects.TestOnlyNestedBaseObject(b=3) top_obj = base_objects.TestOnlyExample( primitive=1, - nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_base_model=TestOnlyNestedBaseModel(a=2), nested_base_object=weave.publish(nested_obj).uri(), ) ref = weave.publish(top_obj) @@ -151,7 +154,7 @@ def test_interface_creation(client): top_level_obj_id = "TestOnlyExample" top_obj = base_objects.TestOnlyExample( primitive=1, - nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_base_model=TestOnlyNestedBaseModel(a=2), nested_base_object=nested_obj_ref.uri(), ) top_obj_res = client.server.obj_create( @@ -251,7 +254,7 @@ def test_digest_equality(client): nested_ref = weave.publish(nested_obj) top_obj = base_objects.TestOnlyExample( primitive=1, - nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_base_model=TestOnlyNestedBaseModel(a=2), nested_base_object=nested_ref.uri(), ) ref = weave.publish(top_obj) @@ -287,7 +290,7 @@ def test_digest_equality(client): top_level_obj_id = "TestOnlyExample" top_obj = base_objects.TestOnlyExample( primitive=1, - nested_base_model=base_objects.TestOnlyNestedBaseModel(a=2), + nested_base_model=TestOnlyNestedBaseModel(a=2), nested_base_object=nested_obj_ref.uri(), ) top_obj_res = client.server.obj_create( diff --git a/weave/trace_server/interface/base_object_classes/test_only_example.py b/weave/trace_server/interface/base_object_classes/test_only_example.py index 87a48907eca..49afb93bf89 100644 --- a/weave/trace_server/interface/base_object_classes/test_only_example.py +++ b/weave/trace_server/interface/base_object_classes/test_only_example.py @@ -23,4 +23,4 @@ class TestOnlyExample(base_object_def.BaseObject): nested_base_object: base_object_def.RefStr -__all__ = ["TestOnlyExample", "TestOnlyNestedBaseObject", "TestOnlyNestedBaseModel"] +__all__ = ["TestOnlyExample", "TestOnlyNestedBaseObject"] From 185f247a89dc659b624ba59d182ad57321bba134 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 11:38:35 -0600 Subject: [PATCH 19/27] Added diagram --- dev_docs/BaseObjectClasses.md | 198 +++++++++++++----- .../base_object_registry.py | 13 +- 2 files changed, 160 insertions(+), 51 deletions(-) diff --git a/dev_docs/BaseObjectClasses.md b/dev_docs/BaseObjectClasses.md index d77799d98f1..7dbf2ba0673 100644 --- a/dev_docs/BaseObjectClasses.md +++ b/dev_docs/BaseObjectClasses.md @@ -42,6 +42,7 @@ This will result in an on-disk payload that looks like: { "model_name": "my_model", "model_version": "1.0", + "_type": "ModelConfig", "_class_name": "ModelConfig", "_bases": ["Object", "BaseModel"] } @@ -53,63 +54,166 @@ Effectively, this is like creating a virtual table for that class. **Terminology**: We use the term "weave Object" (capital "O") to refer to instances of classes that subclass `weave.Object`. **Technical note**: the "base_object_class" is the first subtype of "Object", not the _class_name. -For example, let's say the class heirarchy is: +For example, let's say the class hierarchy is: * `A -> Object -> BaseModel`, then the `base_object_class` filter will be "A". * `B -> A -> Object -> BaseModel`, then the `base_object_class` filter will still be "A"! Finally, the Weave library itself utilizes this mechanism for common objects like `Model`, `Dataset`, `Evaluation`, etc... This allows the user to subclass these objects to add additional metadata or functionality, while categorizing them in the same virtual table. -## The need for validation -Objects, like people, sometimes need validation. Many of our Objects (eg. `Model`) don't really need validation as they are completely free-form and up to the user to definie the properties. -However, there is an increasing need to have a well-defined schema for certain configuration objects - where those objects are tightly defined by Weave, not the user. -In such cases, it is important to have a standard, validated schema that can be shared across the python sdk, the http api, the DB, the frontend UI, and the typescript sdk. +## Validated Base Objects -### Motivating example: -Let's consider that we are defining a new concept "Leaderboard". We want to store this as a configuration object in Weave. -Let's say that it's pydantic representation is: +While many Weave Objects are free-form and user-defined, there is often a need for well-defined schemas for configuration objects that are tightly defined by Weave itself. The BaseObject system provides a way to define these schemas once and use them consistently across the entire stack. + +### Key Features + +1. **Single Source of Truth**: Define your schema once using Pydantic models +2. **Full Stack Integration**: The schema is used for: + - Python SDK validation + - Server-side HTTP API validation + - Frontend UI validation with generated TypeScript types + - Future: OpenAPI schema generation + - Future: TypeScript SDK type generation + +### Usage Example + +Here's how to define and use a validated base object: + +1. **Define your schema** (in `weave/trace_server/interface/base_object_classes/your_schema.py`): ```python -class LeaderboardColumn(BaseModel): - name: str - description: str - evaluation_obj: str # <- reference to an Evaluation object - metric_name: str - lower_is_better: bool +from pydantic import BaseModel +from weave.trace_server.interface.base_object_classes import base_object_def + +class NestedConfig(BaseModel): + setting_a: int -class Leaderboard(BaseModel): +class MyConfig(base_object_def.BaseObject): name: str - description: str - columns: List[LeaderboardColumn] + nested: NestedConfig + reference: base_object_def.RefStr + +__all__ = ["MyConfig"] +``` + +2. **Use in Python**: +```python +# Publishing +ref = weave.publish(MyConfig(...)) + +# Fetching (maintains type) +config = ref.get() +assert isinstance(config, MyConfig) +``` + +3. **Use via HTTP API**: +```bash +# Creating +curl -X POST 'https://trace.wandb.ai/obj/create' \ + -H 'Content-Type: application/json' \ + -d '{ + "obj": { + "project_id": "user/project", + "object_id": "my_config", + "val": {...}, + "set_base_object_class": "MyConfig" + } + }' + +# Querying +curl -X POST 'https://trace.wandb.ai/objs/query' \ + -d '{ + "project_id": "user/project", + "filter": { + "base_object_classes": ["MyConfig"] + } + }' ``` -We want to be able to store & query `Leaderboards` efficiently and ensure they always have a well-defined schema. Therefore we need: -1. An ability to validate the schema at insertion time -2. An ability to create such objects view the REST API -3. An ability to construct, publish, & query these objects: - * via the HTTP API (future: would be nice to have these types inside the openapi schema) - * from Python SDK (need a `weave.Object` subclass) - * from Typescript SDK (TBD - not yet supported, but need the ability to generate the correct utilities) - * from Frontend UI (need a generated Zod schema & hooks to validate the data) - * Ideally, with static type-safety. - -And all of the above should be generated from a single source of truth: the pydantic schema. - -### Solution -1. We define a set of "validated base object classes" in a common location. -2. We use that: - 1. At the DB layer to validate insertion schemas - 2. Can be published from the python sdk - * Note: figuring out the interplay with `weave.Object` is tricky here. There are a few issues: - 1. We don't want to depend on the weave sdk inside the common interface, meaning the class heirarchy is inherently divergent - 2. Without `weave.Object`: - * Our python serialization layer's `is_instance` checks will not maintain behavior - * (Seems fine?) No ref tracing or nested deserialization. Actually, i think we do want to avoid this. - * (Seems fine?) No "handle_relocatable_object" behavior - 3. We still want to have the same bases array in the final payload - - I think all of this can be resolved by extended the base_object_class to also look for `BaseObject` (objects that are pure data schema: only fields) - 3. Can be used in the frontend UI layer - * via generated Zod types & hooks - 4. Future: Exposed in the OpenAPI schema - 5. Future: Generates Typescript SDK types - +4. **Use in React**: +```typescript +// Read with type safety +const result = useBaseObjectInstances("MyConfig", ...); + +// Write with validation +const createFn = useCreateBaseObjectInstance("MyConfig"); +createFn({...}); // TypeScript enforced schema +``` + +### Keeping Frontend Types in Sync + +Run `make synchronize-base-object-schemas` to ensure the frontend TypeScript types are up to date with your Pydantic schemas. + +### Implementation Notes + +- Base objects are pure data schemas (fields only) +- The system is designed to work independently of the weave SDK to maintain clean separation of concerns +- Server-side validation ensures data integrity +- Client-side validation (both Python and TypeScript) provides early feedback +- Generated TypeScript types ensure type safety in the frontend + +### Architecture Flow + +1. Define your schema in a python file in the `weave/trace_server/interface/base_object_classes/test_only_example.py` directory. See `weave/trace_server/interface/base_object_classes/test_only_example.py` as an example. +2. Make sure to register your schemas in `weave/trace_server/interface/base_object_classes/base_object_registry.py` by calling `register_base_object`. +3. Run `make synchronize-base-object-schemas` to generate the frontend types. + * The first step (`make generate_base_object_schemas`) will run `weave/scripts/generate_base_object_schemas.py` to generate a JSON schema in `weave/scripts/generated_base_object_class_schemas.json`. + * The second step (yarn `generate-schemas`) will read this file and use it to generate the frontend types located in `weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts`. + * Note, you need to run prettier to fix the output. These files should be checked in. +4. Now, each use case uses different parts: + 1. `Python Writing`. Users can directly import these classes and use them as normal Pydantic models, which get published with `weave.publish`. The python client correct builds the requisite payload. + 2. `Python Reading`. Users can `weave.ref().get()` and the weave python SDK will return the instance with the correct type. Note: we do some special handling such that the returned object is not a WeaveObject, but literally the exact pydantic class. + 3. `HTTP Writing`. In cases where the client/user does not want to add the special type information, users can publish base objects by setting the `set_base_object_class` setting on `POST obj/create` to the name of the class. The weave server will validate the object against the schema, update the metadata fields, and store the object. + 4. `HTTP Reading`. When querying for objects, the server will return the object with the correct type if the `base_object_class` metadata field is set. + 5. `Frontend`. The frontend will read the zod schema from `weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts` and use that to provide compile time type safety when using `useBaseObjectInstances` and runtime type safety when using `useCreateBaseObjectInstance`. +* Note: it is critical that all techniques produce the same digest for the same data - which is tested in the tests. This way versions are not thrashed by different clients/users. + +```mermaid +graph TD + subgraph Schema Definition + F["weave/trace_server/interface/
base_object_classes/your_schema.py"] --> |defines| P[Pydantic BaseObject] + P --> |register_base_object| R["base_object_registry.py"] + end + + subgraph Schema Generation + M["make synchronize-base-object-schemas"] --> G["make generate_base_object_schemas"] + G --> |runs| S["weave/scripts/
generate_base_object_schemas.py"] + R --> |import registered classes| S + S --> |generates| J["generated_base_object_class_schemas.json"] + M --> |yarn generate-schemas| Z["generatedBaseObjectClasses.zod.ts"] + J --> Z + end + + subgraph "Trace Server" + subgraph "HTTP API" + R --> |validates using| HW["POST obj/create
set_base_object_class"] + HW --> DB[(Weave Object Store)] + HR["POST objs/query
base_object_classes"] --> |Filters base_object_class| DB + end + end + + subgraph "Python SDK" + PW[Client Code] --> |import & publish| W[weave.publish] + W --> |store| HW + R --> |validates using| W + PR["weave ref get()"] --> |queries| HR + R --> |deserializes using| PR + end + + subgraph "Frontend" + Z --> |import| UBI["useBaseObjectInstances"] + Z --> |import| UCI["useCreateBaseObjectInstance"] + UBI --> |Filters base_object_class| HR + UCI --> |set_base_object_class| HW + UI[React UI] --> UBI + UI --> UCI + end + + style F fill:#f9f,stroke:#333,stroke-width:2px + style P fill:#f9f,stroke:#333,stroke-width:2px + style R fill:#bbf,stroke:#333,stroke-width:2px + style DB fill:#dfd,stroke:#333,stroke-width:2px + style J fill:#ffd,stroke:#333,stroke-width:2px + style Z fill:#ffd,stroke:#333,stroke-width:2px + style M fill:#faa,stroke:#333,stroke-width:4px +``` diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py index 43191f43671..e2f891032af 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_registry.py +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -5,10 +5,15 @@ from weave.trace_server.interface.base_object_classes.base_object_def import BaseObject from weave.trace_server.interface.base_object_classes.test_only_example import * -BASE_OBJECT_REGISTRY: Dict[str, Type[BaseObject]] = { - "TestOnlyExample": TestOnlyExample, - "TestOnlyNestedBaseObject": TestOnlyNestedBaseObject, -} +BASE_OBJECT_REGISTRY: Dict[str, Type[BaseObject]] = {} + + +def register_base_object(cls: Type[BaseObject]) -> None: + BASE_OBJECT_REGISTRY[cls.__name__] = cls + + +register_base_object(TestOnlyExample) +register_base_object(TestOnlyNestedBaseObject) # TODO: Remove this helper From e44d6347ac151972ea366a35fea861c6fc02b8c1 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 11:57:09 -0600 Subject: [PATCH 20/27] Removed first hack --- dev_docs/BaseObjectClasses.md | 1 - weave-js/package.json | 2 +- weave/scripts/generate_base_object_schemas.py | 22 +++++++++++++------ .../base_object_registry.py | 14 +++++------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/dev_docs/BaseObjectClasses.md b/dev_docs/BaseObjectClasses.md index 7dbf2ba0673..fe3024c5a20 100644 --- a/dev_docs/BaseObjectClasses.md +++ b/dev_docs/BaseObjectClasses.md @@ -159,7 +159,6 @@ Run `make synchronize-base-object-schemas` to ensure the frontend TypeScript typ 3. Run `make synchronize-base-object-schemas` to generate the frontend types. * The first step (`make generate_base_object_schemas`) will run `weave/scripts/generate_base_object_schemas.py` to generate a JSON schema in `weave/scripts/generated_base_object_class_schemas.json`. * The second step (yarn `generate-schemas`) will read this file and use it to generate the frontend types located in `weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts`. - * Note, you need to run prettier to fix the output. These files should be checked in. 4. Now, each use case uses different parts: 1. `Python Writing`. Users can directly import these classes and use them as normal Pydantic models, which get published with `weave.publish`. The python client correct builds the requisite payload. 2. `Python Reading`. Users can `weave.ref().get()` and the weave python SDK will return the instance with the correct type. Note: we do some special handling such that the returned object is not a WeaveObject, but literally the exact pydantic class. diff --git a/weave-js/package.json b/weave-js/package.json index 6dc7da04766..2e3fdeaea92 100644 --- a/weave-js/package.json +++ b/weave-js/package.json @@ -21,7 +21,7 @@ "prettier-fix": "prettier --loglevel warn --config .prettierrc --write \"src/**/*.ts\" \"src/**/*.tsx\"", "lint": "yarn eslint & yarn tslint & yarn prettier & wait", "lint-fix": "yarn eslint-fix & yarn tslint-fix & yarn prettier-fix & wait", - "generate-schemas": "yarn quicktype -s schema ../weave/scripts/generated_base_object_class_schemas.json -o ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts --lang typescript-zod" + "generate-schemas": "yarn quicktype -s schema ../weave/scripts/generated_base_object_class_schemas.json -o ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts --lang typescript-zod && prettier --write ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts" }, "dependencies": { "@apollo/client": "^3.8.4", diff --git a/weave/scripts/generate_base_object_schemas.py b/weave/scripts/generate_base_object_schemas.py index 979565a09e1..fc6ae2a5d1f 100644 --- a/weave/scripts/generate_base_object_schemas.py +++ b/weave/scripts/generate_base_object_schemas.py @@ -1,9 +1,10 @@ import json from pathlib import Path +from pydantic import create_model + from weave.trace_server.interface.base_object_classes.base_object_registry import ( BASE_OBJECT_REGISTRY, - CompositeBaseObject, ) OUTPUT_PATH = Path(__file__).parent / "generated_base_object_class_schemas.json" @@ -11,17 +12,24 @@ def generate_schemas() -> None: """ - Generate JSON schemas for all registered base objects in REGISTRY. - Creates a top-level union type of all registered objects and writes the schemas - to a file named 'base_object_schemas.json'. + Generate JSON schemas for all registered base objects in BASE_OBJECT_REGISTRY. + Creates a top-level schema that includes all registered objects and writes it + to 'generated_base_object_class_schemas.json'. """ - top_level_schema = CompositeBaseObject.model_json_schema(mode="validation") + # Dynamically create a parent model with all registered objects as properties + CompositeModel = create_model( + "CompositeBaseObject", + **{name: (cls, ...) for name, cls in BASE_OBJECT_REGISTRY.items()}, + ) + + # Generate the schema using the composite model + top_level_schema = CompositeModel.model_json_schema(mode="validation") - # Write schemas to file + # Write schema to file with OUTPUT_PATH.open("w") as f: json.dump(top_level_schema, f, indent=2) - print(f"Generated union schema for {len(BASE_OBJECT_REGISTRY)} objects") + print(f"Generated schema for {len(BASE_OBJECT_REGISTRY)} objects") print(f"Wrote schema to {OUTPUT_PATH.absolute()}") diff --git a/weave/trace_server/interface/base_object_classes/base_object_registry.py b/weave/trace_server/interface/base_object_classes/base_object_registry.py index e2f891032af..8941dfa75ad 100644 --- a/weave/trace_server/interface/base_object_classes/base_object_registry.py +++ b/weave/trace_server/interface/base_object_classes/base_object_registry.py @@ -1,7 +1,5 @@ from typing import Dict, Type -from pydantic import BaseModel - from weave.trace_server.interface.base_object_classes.base_object_def import BaseObject from weave.trace_server.interface.base_object_classes.test_only_example import * @@ -9,14 +7,14 @@ def register_base_object(cls: Type[BaseObject]) -> None: + """ + Register a BaseObject class in the global registry. + + Args: + cls: The BaseObject class to register + """ BASE_OBJECT_REGISTRY[cls.__name__] = cls register_base_object(TestOnlyExample) register_base_object(TestOnlyNestedBaseObject) - - -# TODO: Remove this helper -class CompositeBaseObject(BaseModel): - TestOnlyExample: TestOnlyExample - TestOnlyNestedBaseObject: TestOnlyNestedBaseObject From c723b327b70966f73a8076435251e16639402277 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 12:03:48 -0600 Subject: [PATCH 21/27] Removed second hack --- .../pages/wfReactInterface/baseObjectClassQuery.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index b1f7b4b148f..e89b0929d54 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -3,8 +3,7 @@ import {useEffect, useRef, useState} from 'react'; import {z} from 'zod'; import { - TestOnlyExampleSchema, - TestOnlyNestedBaseObjectSchema, + GeneratedBaseObjectClassesZodSchema, } from './generatedBaseObjectClasses.zod'; import {TraceServerClient} from './traceServerClient'; import {useGetTraceServerClientContext} from './traceServerClientContext'; @@ -16,11 +15,8 @@ import { } from './traceServerClientTypes'; import {Loadable} from './wfDataModelHooksInterface'; -// TODO: This should be generated from the registry! -const baseObjectClassRegistry: Record = { - TestOnlyExample: TestOnlyExampleSchema, - TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, -}; +const baseObjectClassRegistry: Record = + GeneratedBaseObjectClassesZodSchema.shape; export const useBaseObjectInstances = < C extends keyof typeof baseObjectClassRegistry, From ec7c0b79c7ff41b9af3929de5ae9709cc69ae579 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 12:14:11 -0600 Subject: [PATCH 22/27] lint --- .../Browse3/pages/wfReactInterface/baseObjectClassQuery.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index e89b0929d54..d1d4bbaeb48 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -2,9 +2,7 @@ import {useDeepMemo} from '@wandb/weave/hookUtils'; import {useEffect, useRef, useState} from 'react'; import {z} from 'zod'; -import { - GeneratedBaseObjectClassesZodSchema, -} from './generatedBaseObjectClasses.zod'; +import {GeneratedBaseObjectClassesZodSchema} from './generatedBaseObjectClasses.zod'; import {TraceServerClient} from './traceServerClient'; import {useGetTraceServerClientContext} from './traceServerClientContext'; import { @@ -15,7 +13,7 @@ import { } from './traceServerClientTypes'; import {Loadable} from './wfDataModelHooksInterface'; -const baseObjectClassRegistry: Record = +const baseObjectClassRegistry: Record = GeneratedBaseObjectClassesZodSchema.shape; export const useBaseObjectInstances = < From c85b6f4ceec7dcba7552d9ac13e8356c9c1e8bb9 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 14:01:36 -0600 Subject: [PATCH 23/27] Fixed generation --- weave-js/package.json | 3 +- weave-js/scripts/generate-schemas.sh | 41 +++++++++++++++++++ .../wfReactInterface/baseObjectClassQuery.ts | 7 ++-- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 weave-js/scripts/generate-schemas.sh diff --git a/weave-js/package.json b/weave-js/package.json index 2e3fdeaea92..efd9a510032 100644 --- a/weave-js/package.json +++ b/weave-js/package.json @@ -19,9 +19,10 @@ "generate:watch": "graphql-codegen -w", "prettier": "prettier --config .prettierrc --check \"src/**/*.ts\" \"src/**/*.tsx\"", "prettier-fix": "prettier --loglevel warn --config .prettierrc --write \"src/**/*.ts\" \"src/**/*.tsx\"", + "direct-prettier": "prettier", "lint": "yarn eslint & yarn tslint & yarn prettier & wait", "lint-fix": "yarn eslint-fix & yarn tslint-fix & yarn prettier-fix & wait", - "generate-schemas": "yarn quicktype -s schema ../weave/scripts/generated_base_object_class_schemas.json -o ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts --lang typescript-zod && prettier --write ./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts" + "generate-schemas": "bash scripts/generate-schemas.sh" }, "dependencies": { "@apollo/client": "^3.8.4", diff --git a/weave-js/scripts/generate-schemas.sh b/weave-js/scripts/generate-schemas.sh new file mode 100644 index 00000000000..a9d6933155a --- /dev/null +++ b/weave-js/scripts/generate-schemas.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Exit on error +set -e + +SCHEMA_INPUT_PATH="../weave/scripts/generated_base_object_class_schemas.json" +SCHEMA_OUTPUT_PATH="./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts" + +echo "Generating schemas..." + +# Generate TypeScript-Zod types from schema +yarn quicktype -s schema "$SCHEMA_INPUT_PATH" -o "$SCHEMA_OUTPUT_PATH" --lang typescript-zod + +# Transform the schema to extract the type map +sed -i.bak ' + # Find the GeneratedBaseObjectClassesZodSchema definition and capture its contents + /export const GeneratedBaseObjectClassesZodSchema = z.object({/,/});/ { + # Replace the opening line with typeMap declaration + s/export const GeneratedBaseObjectClassesZodSchema = z.object({/export const baseObjectClassRegistry = ({/ + # Store the pattern + h + # If this is the last line (with closing brace), append the schema definition + /});/ { + p + s/.*// + x + s/.*// + i\ +\ +export const GeneratedBaseObjectClassesZodSchema = z.object(baseObjectClassRegistry) + } + } +' "$SCHEMA_OUTPUT_PATH" + +# Remove backup file +rm "${SCHEMA_OUTPUT_PATH}.bak" + +# Format the generated file +yarn direct-prettier --write "$SCHEMA_OUTPUT_PATH" + +echo "Schema generation completed successfully" \ No newline at end of file diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index d1d4bbaeb48..7250bb0f687 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -2,7 +2,9 @@ import {useDeepMemo} from '@wandb/weave/hookUtils'; import {useEffect, useRef, useState} from 'react'; import {z} from 'zod'; -import {GeneratedBaseObjectClassesZodSchema} from './generatedBaseObjectClasses.zod'; +import { + baseObjectClassRegistry, +} from './generatedBaseObjectClasses.zod'; import {TraceServerClient} from './traceServerClient'; import {useGetTraceServerClientContext} from './traceServerClientContext'; import { @@ -13,9 +15,6 @@ import { } from './traceServerClientTypes'; import {Loadable} from './wfDataModelHooksInterface'; -const baseObjectClassRegistry: Record = - GeneratedBaseObjectClassesZodSchema.shape; - export const useBaseObjectInstances = < C extends keyof typeof baseObjectClassRegistry, T = z.infer<(typeof baseObjectClassRegistry)[C]> From ea49fb125297eb0e0d25a07d8060f5913d6c76e6 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 14:02:13 -0600 Subject: [PATCH 24/27] Fixed generation 2 --- .../wfReactInterface/generatedBaseObjectClasses.zod.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts index ba5a38ebf65..fd27f0bc933 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts @@ -25,10 +25,15 @@ export const TestOnlyExampleSchema = z.object({ }); export type TestOnlyExample = z.infer; -export const GeneratedBaseObjectClassesZodSchema = z.object({ +export const baseObjectClassRegistry = { TestOnlyExample: TestOnlyExampleSchema, TestOnlyNestedBaseObject: TestOnlyNestedBaseObjectSchema, -}); +}; + +export const GeneratedBaseObjectClassesZodSchema = z.object( + baseObjectClassRegistry +); + export type GeneratedBaseObjectClassesZod = z.infer< typeof GeneratedBaseObjectClassesZodSchema >; From d84fb36d7b6d36553e67097a14a8c0e801291060 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 14:16:47 -0600 Subject: [PATCH 25/27] Fixed types --- .../Browse3/pages/wfReactInterface/baseObjectClassQuery.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 7250bb0f687..804c0f775f3 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -2,9 +2,7 @@ import {useDeepMemo} from '@wandb/weave/hookUtils'; import {useEffect, useRef, useState} from 'react'; import {z} from 'zod'; -import { - baseObjectClassRegistry, -} from './generatedBaseObjectClasses.zod'; +import {baseObjectClassRegistry} from './generatedBaseObjectClasses.zod'; import {TraceServerClient} from './traceServerClient'; import {useGetTraceServerClientContext} from './traceServerClientContext'; import { @@ -57,8 +55,7 @@ const getBaseObjectInstances = async < baseObjectClassName: C, req: TraceObjQueryReq ): Promise>> => { - const knownObjectClass: z.ZodType = - baseObjectClassRegistry[baseObjectClassName]; + const knownObjectClass = baseObjectClassRegistry[baseObjectClassName]; if (!knownObjectClass) { console.warn(`Unknown object class: ${baseObjectClassName}`); return []; From 99f58d17a13050755e0c5d892e1eb3740930b5e1 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 15:03:20 -0600 Subject: [PATCH 26/27] Type fixes --- .../wfReactInterface/baseObjectClassQuery.ts | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts index 804c0f775f3..5906d52ad6f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/baseObjectClassQuery.ts @@ -13,14 +13,19 @@ import { } from './traceServerClientTypes'; import {Loadable} from './wfDataModelHooksInterface'; -export const useBaseObjectInstances = < - C extends keyof typeof baseObjectClassRegistry, - T = z.infer<(typeof baseObjectClassRegistry)[C]> ->( +type BaseObjectClassRegistry = typeof baseObjectClassRegistry; +type BaseObjectClassRegistryKeys = keyof BaseObjectClassRegistry; +type BaseObjectClassType = z.infer< + BaseObjectClassRegistry[C] +>; + +export const useBaseObjectInstances = ( baseObjectClassName: C, req: TraceObjQueryReq -): Loadable>> => { - const [objects, setObjects] = useState>>([]); +): Loadable, C>>> => { + const [objects, setObjects] = useState< + Array, C>> + >([]); const getTsClient = useGetTraceServerClientContext(); const client = getTsClient(); const deepReq = useDeepMemo(req); @@ -34,7 +39,7 @@ export const useBaseObjectInstances = < getBaseObjectInstances(client, baseObjectClassName, deepReq).then( collectionObjects => { if (isMounted && currReq.current === deepReq) { - setObjects(collectionObjects as Array>); + setObjects(collectionObjects); setLoading(false); } } @@ -47,14 +52,11 @@ export const useBaseObjectInstances = < return {result: objects, loading}; }; -const getBaseObjectInstances = async < - C extends keyof typeof baseObjectClassRegistry, - T = z.infer<(typeof baseObjectClassRegistry)[C]> ->( +const getBaseObjectInstances = async ( client: TraceServerClient, baseObjectClassName: C, req: TraceObjQueryReq -): Promise>> => { +): Promise, C>>> => { const knownObjectClass = baseObjectClassRegistry[baseObjectClassName]; if (!knownObjectClass) { console.warn(`Unknown object class: ${baseObjectClassName}`); @@ -78,13 +80,17 @@ const getBaseObjectInstances = async < .filter(({parsed}) => parsed.success) .filter(({obj}) => obj.base_object_class === baseObjectClassName) .map( - ({obj, parsed}) => ({...obj, val: parsed.data} as TraceObjSchema) + ({obj, parsed}) => + ({...obj, val: parsed.data} as TraceObjSchema< + BaseObjectClassType, + C + >) ); }; export const useCreateBaseObjectInstance = < - C extends keyof typeof baseObjectClassRegistry, - T = z.infer<(typeof baseObjectClassRegistry)[C]> + C extends BaseObjectClassRegistryKeys, + T = BaseObjectClassType >( baseObjectClassName: C ): ((req: TraceObjCreateReq) => Promise) => { @@ -95,8 +101,8 @@ export const useCreateBaseObjectInstance = < }; const createBaseObjectInstance = async < - C extends keyof typeof baseObjectClassRegistry, - T = z.infer<(typeof baseObjectClassRegistry)[C]> + C extends BaseObjectClassRegistryKeys, + T = BaseObjectClassType >( client: TraceServerClient, baseObjectClassName: C, From a1556295fa304671f4f89817dcd6581cff935546 Mon Sep 17 00:00:00 2001 From: Tim Sweeney Date: Thu, 31 Oct 2024 15:33:25 -0600 Subject: [PATCH 27/27] Addressed comments --- dev_docs/BaseObjectClasses.md | 2 +- weave-js/scripts/generate-schemas.sh | 2 +- weave/scripts/generate_base_object_schemas.py | 12 ++++++- weave/trace_server/base_object_class_util.py | 31 +++++++++++++++++++ .../generated_base_object_class_schemas.json | 0 5 files changed, 44 insertions(+), 3 deletions(-) rename weave/{scripts => trace_server/interface/base_object_classes/generated}/generated_base_object_class_schemas.json (100%) diff --git a/dev_docs/BaseObjectClasses.md b/dev_docs/BaseObjectClasses.md index fe3024c5a20..a571af49755 100644 --- a/dev_docs/BaseObjectClasses.md +++ b/dev_docs/BaseObjectClasses.md @@ -157,7 +157,7 @@ Run `make synchronize-base-object-schemas` to ensure the frontend TypeScript typ 1. Define your schema in a python file in the `weave/trace_server/interface/base_object_classes/test_only_example.py` directory. See `weave/trace_server/interface/base_object_classes/test_only_example.py` as an example. 2. Make sure to register your schemas in `weave/trace_server/interface/base_object_classes/base_object_registry.py` by calling `register_base_object`. 3. Run `make synchronize-base-object-schemas` to generate the frontend types. - * The first step (`make generate_base_object_schemas`) will run `weave/scripts/generate_base_object_schemas.py` to generate a JSON schema in `weave/scripts/generated_base_object_class_schemas.json`. + * The first step (`make generate_base_object_schemas`) will run `weave/scripts/generate_base_object_schemas.py` to generate a JSON schema in `weave/trace_server/interface/base_object_classes/generated/generated_base_object_class_schemas.json`. * The second step (yarn `generate-schemas`) will read this file and use it to generate the frontend types located in `weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts`. 4. Now, each use case uses different parts: 1. `Python Writing`. Users can directly import these classes and use them as normal Pydantic models, which get published with `weave.publish`. The python client correct builds the requisite payload. diff --git a/weave-js/scripts/generate-schemas.sh b/weave-js/scripts/generate-schemas.sh index a9d6933155a..545d81e17af 100644 --- a/weave-js/scripts/generate-schemas.sh +++ b/weave-js/scripts/generate-schemas.sh @@ -3,7 +3,7 @@ # Exit on error set -e -SCHEMA_INPUT_PATH="../weave/scripts/generated_base_object_class_schemas.json" +SCHEMA_INPUT_PATH="../weave/trace_server/interface/base_object_classes/generated/generated_base_object_class_schemas.json" SCHEMA_OUTPUT_PATH="./src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/generatedBaseObjectClasses.zod.ts" echo "Generating schemas..." diff --git a/weave/scripts/generate_base_object_schemas.py b/weave/scripts/generate_base_object_schemas.py index fc6ae2a5d1f..06b958a9c33 100644 --- a/weave/scripts/generate_base_object_schemas.py +++ b/weave/scripts/generate_base_object_schemas.py @@ -7,7 +7,14 @@ BASE_OBJECT_REGISTRY, ) -OUTPUT_PATH = Path(__file__).parent / "generated_base_object_class_schemas.json" +OUTPUT_DIR = ( + Path(__file__).parent.parent + / "trace_server" + / "interface" + / "base_object_classes" + / "generated" +) +OUTPUT_PATH = OUTPUT_DIR / "generated_base_object_class_schemas.json" def generate_schemas() -> None: @@ -25,6 +32,9 @@ def generate_schemas() -> None: # Generate the schema using the composite model top_level_schema = CompositeModel.model_json_schema(mode="validation") + # make sure the output directory exists + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + # Write schema to file with OUTPUT_PATH.open("w") as f: json.dump(top_level_schema, f, indent=2) diff --git a/weave/trace_server/base_object_class_util.py b/weave/trace_server/base_object_class_util.py index 698b422aad1..ac5e572313d 100644 --- a/weave/trace_server/base_object_class_util.py +++ b/weave/trace_server/base_object_class_util.py @@ -6,6 +6,12 @@ BASE_OBJECT_REGISTRY, ) +""" +There are two standard base object classes: BaseObject and Object + +`Object` is the base class for the more advanced object-oriented `weave.Object` use cases. +`BaseObject` is the more simple schema-based base object class. +""" base_object_class_names = ["BaseObject", "Object"] @@ -26,6 +32,27 @@ def get_base_object_class(val: Any) -> Optional[str]: def process_incoming_object( val: Any, req_base_object_class: Optional[str] = None ) -> Tuple[dict, Optional[str]]: + """ + This method is responsible for accepting an incoming object from the user, validating it + against the base object class, and returning the object with the base object class + set. It does not mutate the original object, but returns a new object with values set if needed. + + Specifically,: + + 1. If the object is not a dict, it is returned as is, and the base object class is set to None. + 2. There are 2 ways to specify the base object class: + a. The `req_base_object_class` argument. + * used by non-pythonic writers of weave objects + b. The `_bases` & `_class_name` attributes of the object, which is a list of base class names. + * used by pythonic weave object writers (legacy) + 3. If the object has a base object class that does not match the requested base object class, + an error is thrown. + 4. if the object contains a base object class inside the payload, then we simply validate + the object against the base object class (if a match is found in BASE_OBJECT_REGISTRY) + 5. If the object does not have a base object class and a requested base object class is + provided, we require a match in BASE_OBJECT_REGISTRY and validate the object against + the requested base object class. Finally, we set the correct feilds. + """ if not isinstance(val, dict): if req_base_object_class is not None: raise ValueError( @@ -86,5 +113,9 @@ def _general_dump(val: Any) -> Any: return {k: _general_dump(v) for k, v in val.items()} elif isinstance(val, list): return [_general_dump(v) for v in val] + elif isinstance(val, tuple): + return tuple(_general_dump(v) for v in val) + elif isinstance(val, set): + return {_general_dump(v) for v in val} else: return val diff --git a/weave/scripts/generated_base_object_class_schemas.json b/weave/trace_server/interface/base_object_classes/generated/generated_base_object_class_schemas.json similarity index 100% rename from weave/scripts/generated_base_object_class_schemas.json rename to weave/trace_server/interface/base_object_classes/generated/generated_base_object_class_schemas.json