diff --git a/Makefile b/Makefile
index 6c1ed017c0f9..364e61b53b32 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,9 @@ docs:
build:
uv build
+prerelease-dry-run:
+ uv run ./weave/scripts/prerelease_dry_run.py
+
prepare-release: docs build
synchronize-base-object-schemas:
diff --git a/dev_docs/RELEASE.md b/dev_docs/RELEASE.md
index 6514f9d6730b..dcbba003e864 100644
--- a/dev_docs/RELEASE.md
+++ b/dev_docs/RELEASE.md
@@ -4,7 +4,9 @@ This document outlines how to publish a new Weave release to our public [PyPI pa
1. Verify the head of master is ready for release and announce merge freeze to the Weave team while the release is being published (Either ask an admin on the Weave repo to place a freeze on https://www.mergefreeze.com/ or use the mergefreeze Slack app if it is set up or just post in Slack)
-2. You should also run through this [sample notebook](https://colab.research.google.com/drive/1DmkLzhFCFC0OoN-ggBDoG1nejGw2jQZy#scrollTo=29hJrcJQA7jZ) remember to install from master. You can also just run the [quickstart](http://wandb.me/weave_colab).
+2. Manual Verifications:
+ - Run `make prerelease-dry-run` to verify that the dry run script works.
+ - You should also run through this [sample notebook](https://colab.research.google.com/drive/1DmkLzhFCFC0OoN-ggBDoG1nejGw2jQZy#scrollTo=29hJrcJQA7jZ) remember to install from master. You can also just run the [quickstart](http://wandb.me/weave_colab).
3. To prepare a PATCH release, go to GitHub Actions and run the [bump-python-sdk-version](https://github.com/wandb/weave/actions/workflows/bump_version.yaml) workflow on master. This will:
diff --git a/docs/docs/guides/tracking/objects.md b/docs/docs/guides/tracking/objects.md
index ffb26972f4cd..23e806d60b1f 100644
--- a/docs/docs/guides/tracking/objects.md
+++ b/docs/docs/guides/tracking/objects.md
@@ -58,6 +58,28 @@ Saving an object with a name will create the first version of that object if it
+## Deleting an object
+
+
+
+ To delete a version of an object, call `.delete()` on the object ref.
+
+ ```python
+ weave.init('intro-example')
+ cat_names_ref = weave.ref('cat-names:v1')
+ cat_names_ref.delete()
+ ```
+
+ Trying to access a deleted object will result in an error. Resolving an object that has a reference to a deleted object will return a `DeletedRef` object in place of the deleted object.
+
+
+
+ ```plaintext
+ This feature is not available in TypeScript yet. Stay tuned!
+ ```
+
+
+
## Ref styles
A fully qualified weave object ref uri looks like this:
diff --git a/docs/docs/guides/tracking/ops.md b/docs/docs/guides/tracking/ops.md
index b5751ff57617..c78aacbb0efb 100644
--- a/docs/docs/guides/tracking/ops.md
+++ b/docs/docs/guides/tracking/ops.md
@@ -156,3 +156,25 @@ If you want to suppress the printing of call links during logging, you can set t
```bash
export WEAVE_PRINT_CALL_LINK=false
```
+
+## Deleting an op
+
+
+
+ To delete a version of an op, call `.delete()` on the op ref.
+
+ ```python
+ weave.init('intro-example')
+ my_op_ref = weave.ref('track_me:v1')
+ my_op_ref.delete()
+ ```
+
+ Trying to access a deleted op will result in an error.
+
+
+
+ ```plaintext
+ This feature is not available in TypeScript yet. Stay tuned!
+ ```
+
+
diff --git a/docs/docs/guides/tracking/tracing.mdx b/docs/docs/guides/tracking/tracing.mdx
index fbdf6bea96f9..e0efd4cd4453 100644
--- a/docs/docs/guides/tracking/tracing.mdx
+++ b/docs/docs/guides/tracking/tracing.mdx
@@ -5,6 +5,7 @@ import TracingCallsMacroImage from '@site/static/img/screenshots/calls_macro.png
import TracingCallsFilterImage from '@site/static/img/screenshots/calls_filter.png';
import BasicCallImage from '@site/static/img/screenshots/basic_call.png';
+
# Calls
0|[1-9]\\d*)\\.
(?P0|[1-9]\\d*)\\.
diff --git a/tests/trace/image_patch_test.py b/tests/trace/image_patch_test.py
new file mode 100644
index 000000000000..6eca3482b5b9
--- /dev/null
+++ b/tests/trace/image_patch_test.py
@@ -0,0 +1,22 @@
+from tempfile import NamedTemporaryFile
+
+from weave.initialization import pil_image_thread_safety
+
+
+def test_patching_import_order():
+ # This test verifies the correct behavior if patching occurs after the construction
+ # of an image
+ assert pil_image_thread_safety._patched
+ pil_image_thread_safety.undo_threadsafe_patch_to_pil_image()
+ assert not pil_image_thread_safety._patched
+ import PIL
+
+ image = PIL.Image.new("RGB", (10, 10))
+ with NamedTemporaryFile(suffix=".png") as f:
+ image.save(f.name)
+ image = PIL.Image.open(f.name)
+
+ pil_image_thread_safety.apply_threadsafe_patch_to_pil_image()
+ assert pil_image_thread_safety._patched
+
+ image.crop((0, 0, 10, 10))
diff --git a/tests/trace/test_dictifiable.py b/tests/trace/test_dictifiable.py
new file mode 100644
index 000000000000..e99ecf29e130
--- /dev/null
+++ b/tests/trace/test_dictifiable.py
@@ -0,0 +1,45 @@
+import weave
+
+
+def test_dictifiable(client):
+ class NonDictifiable:
+ attr: int
+
+ def __init__(self, attr: int):
+ self.attr = attr
+
+ class Dictifiable:
+ attr: int
+
+ def __init__(self, attr: int):
+ self.attr = attr
+
+ def to_dict(self):
+ return {"attr": self.attr}
+
+ @weave.op
+ def func(d: Dictifiable, nd: NonDictifiable) -> dict:
+ return {
+ "d": Dictifiable(d.attr),
+ "nd": NonDictifiable(nd.attr),
+ }
+
+ val = 42
+ d = Dictifiable(val)
+ nd = NonDictifiable(val)
+ res = func(d, nd)
+ assert isinstance(res["d"], Dictifiable)
+ assert res["d"].attr == val
+ assert isinstance(res["nd"], NonDictifiable)
+ assert res["nd"].attr == val
+
+ call = func.calls()[0]
+
+ assert call.inputs["d"] == {"attr": val}
+ assert call.inputs["nd"].startswith(
+ ".NonDictifiable object at"
+ )
+ assert call.output["d"] == {"attr": val}
+ assert call.output["nd"].startswith(
+ ".NonDictifiable object at"
+ )
diff --git a/tests/trace/type_handlers/Image/image_test.py b/tests/trace/type_handlers/Image/image_test.py
index 316bbef917ab..a5dcbc37dc3a 100644
--- a/tests/trace/type_handlers/Image/image_test.py
+++ b/tests/trace/type_handlers/Image/image_test.py
@@ -149,20 +149,19 @@ def accept_image_jpg_pillow(val):
file_path.unlink()
+def make_random_image(image_size: tuple[int, int] = (1024, 1024)):
+ random_colour = (
+ random.randint(0, 255),
+ random.randint(0, 255),
+ random.randint(0, 255),
+ )
+ return Image.new("RGB", image_size, random_colour)
+
+
@pytest.fixture
def dataset_ref(client):
# This fixture represents a saved dataset containing images
- IMAGE_SIZE = (1024, 1024)
N_ROWS = 50
-
- def make_random_image():
- random_colour = (
- random.randint(0, 255),
- random.randint(0, 255),
- random.randint(0, 255),
- )
- return Image.new("RGB", IMAGE_SIZE, random_colour)
-
rows = [{"img": make_random_image()} for _ in range(N_ROWS)]
dataset = weave.Dataset(rows=rows)
ref = weave.publish(dataset)
@@ -202,3 +201,18 @@ async def test_many_images_will_consistently_log():
# But if there's an issue, the stderr will contain `Task failed:`
assert "Task failed" not in res.stderr
+
+
+def test_images_in_load_of_dataset(client):
+ N_ROWS = 50
+ rows = [{"img": make_random_image()} for _ in range(N_ROWS)]
+ dataset = weave.Dataset(rows=rows)
+ ref = weave.publish(dataset)
+
+ dataset = ref.get()
+ for gotten_row, local_row in zip(dataset, rows):
+ assert isinstance(gotten_row["img"], Image.Image)
+ assert gotten_row["img"].size == local_row["img"].size
+ assert gotten_row["img"].tobytes() == local_row["img"].tobytes()
+
+ return ref
diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx
index 488f5170fe14..68112d9b904a 100644
--- a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx
+++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx
@@ -5,6 +5,7 @@ import React, {useMemo} from 'react';
import {Icon} from '../../../../Icon';
import {LoadingDots} from '../../../../LoadingDots';
import {Tailwind} from '../../../../Tailwind';
+import {Timestamp} from '../../../../Timestamp';
import {WeaveCHTableSourceRefContext} from '../pages/CallPage/DataTableView';
import {ObjectViewerSection} from '../pages/CallPage/ObjectViewerSection';
import {objectVersionText} from '../pages/common/Links';
@@ -30,6 +31,7 @@ export const DatasetVersionPage: React.FC<{
const projectName = objectVersion.project;
const objectName = objectVersion.objectId;
const objectVersionIndex = objectVersion.versionIndex;
+ const {createdAtMs} = objectVersion;
const objectVersions = useRootObjectVersions(
entityName,
@@ -76,7 +78,7 @@ export const DatasetVersionPage: React.FC<{
}
headerContent={
-