Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate uuids #282

Merged
merged 2 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 35 additions & 30 deletions python/langsmith/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ def _hide_outputs(outputs: Dict[str, Any]) -> Dict[str, Any]:
return outputs


def _as_uuid(value: ID_TYPE) -> uuid.UUID:
return uuid.UUID(value) if not isinstance(value, uuid.UUID) else value


class Client:
"""Client for interacting with the LangSmith API."""

Expand Down Expand Up @@ -724,7 +728,7 @@ def update_run(
data["events"] = events
self.request_with_retries(
"patch",
f"{self.api_url}/runs/{run_id}",
f"{self.api_url}/runs/{_as_uuid(run_id)}",
request_kwargs={
"data": json.dumps(data, default=_serialize_json),
"headers": headers,
Expand Down Expand Up @@ -786,7 +790,7 @@ def read_run(
Run
The run.
"""
response = self._get_with_retries(f"/runs/{run_id}")
response = self._get_with_retries(f"/runs/{_as_uuid(run_id)}")
run = ls_schemas.Run(**response.json(), _host_url=self._host_url)
if load_child_runs and run.child_run_ids:
run = self._load_child_runs(run)
Expand Down Expand Up @@ -910,18 +914,19 @@ def get_run_url(
)
session_id = self.read_project(project_name=project_name).id
return (
f"{self._host_url}/o/{self._get_tenant_id()}/projects/p/{session_id}/"
f"{self._host_url}/o/{self._get_tenant_id()}/projects/p/{_as_uuid(session_id)}/"
f"r/{run.id}?poll=true"
)

def share_run(self, run_id: ID_TYPE, *, share_id: Optional[ID_TYPE] = None) -> str:
"""Get a share link for a run."""
run_id_ = _as_uuid(run_id)
data = {
"run_id": str(run_id),
"run_id": str(run_id_),
"share_token": share_id or str(uuid.uuid4()),
}
response = self.session.put(
f"{self.api_url}/runs/{run_id}/share",
f"{self.api_url}/runs/{run_id_}/share",
headers=self._headers,
json=data,
)
Expand All @@ -932,14 +937,14 @@ def share_run(self, run_id: ID_TYPE, *, share_id: Optional[ID_TYPE] = None) -> s
def unshare_run(self, run_id: ID_TYPE) -> None:
"""Delete share link for a run."""
response = self.session.delete(
f"{self.api_url}/runs/{run_id}/share",
f"{self.api_url}/runs/{_as_uuid(run_id)}/share",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)

def read_run_shared_link(self, run_id: ID_TYPE) -> Optional[str]:
response = self.session.get(
f"{self.api_url}/runs/{run_id}/share",
f"{self.api_url}/runs/{_as_uuid(run_id)}/share",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand All @@ -950,16 +955,16 @@ def read_run_shared_link(self, run_id: ID_TYPE) -> Optional[str]:

def run_is_shared(self, run_id: ID_TYPE) -> bool:
"""Get share state for a run."""
link = self.read_run_shared_link(run_id)
link = self.read_run_shared_link(_as_uuid(run_id))
return link is not None

def list_shared_runs(
self, share_token: str, run_ids: Optional[List[str]] = None
self, share_token: ID_TYPE, run_ids: Optional[List[str]] = None
) -> List[ls_schemas.Run]:
"""Get shared runs."""
params = {"id": run_ids, "share_token": share_token}
params = {"id": run_ids, "share_token": str(share_token)}
response = self.session.get(
f"{self.api_url}/public/{share_token}/runs",
f"{self.api_url}/public/{_as_uuid(share_token)}/runs",
headers=self._headers,
params=params,
)
Expand All @@ -979,14 +984,14 @@ def read_dataset_shared_schema(
if dataset_id is None:
dataset_id = self.read_dataset(dataset_name=dataset_name).id
response = self.session.get(
f"{self.api_url}/datasets/{dataset_id}/share",
f"{self.api_url}/datasets/{_as_uuid(dataset_id)}/share",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
d = response.json()
return cast(
ls_schemas.DatasetShareSchema,
{**d, "url": f"{self._host_url}/public/{d['share_token']}/d"},
{**d, "url": f"{self._host_url}/public/{_as_uuid(d['share_token'])}/d"},
)

def share_dataset(
Expand All @@ -1004,7 +1009,7 @@ def share_dataset(
"dataset_id": str(dataset_id),
}
response = self.session.put(
f"{self.api_url}/datasets/{dataset_id}/share",
f"{self.api_url}/datasets/{_as_uuid(dataset_id)}/share",
headers=self._headers,
json=data,
)
Expand All @@ -1018,7 +1023,7 @@ def share_dataset(
def unshare_dataset(self, dataset_id: ID_TYPE) -> None:
"""Delete share link for a dataset."""
response = self.session.delete(
f"{self.api_url}/datasets/{dataset_id}/share",
f"{self.api_url}/datasets/{_as_uuid(dataset_id)}/share",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand All @@ -1029,7 +1034,7 @@ def read_shared_dataset(
) -> ls_schemas.Dataset:
"""Get shared datasets."""
response = self.session.get(
f"{self.api_url}/public/{share_token}/datasets",
f"{self.api_url}/public/{_as_uuid(share_token)}/datasets",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand All @@ -1043,7 +1048,7 @@ def list_shared_examples(
if example_ids is not None:
params["id"] = [str(id) for id in example_ids]
response = self.session.get(
f"{self.api_url}/public/{share_token}/examples",
f"{self.api_url}/public/{_as_uuid(share_token)}/examples",
headers=self._headers,
params=params,
)
Expand All @@ -1056,7 +1061,7 @@ def list_shared_examples(
def list_shared_projects(
self,
*,
dataset_share_token: Optional[str] = None,
dataset_share_token: str,
project_ids: Optional[List[ID_TYPE]] = None,
name: Optional[str] = None,
name_contains: Optional[str] = None,
Expand All @@ -1065,7 +1070,7 @@ def list_shared_projects(
yield from [
ls_schemas.TracerSession(**dataset, _host_url=self._host_url)
for dataset in self._get_paginated_list(
f"/public/{dataset_share_token}/datasets/sessions",
f"/public/{_as_uuid(dataset_share_token)}/datasets/sessions",
params=params,
)
]
Expand Down Expand Up @@ -1149,7 +1154,7 @@ def read_project(
path = "/sessions"
params: Dict[str, Any] = {"limit": 1}
if project_id is not None:
path += f"/{project_id}"
path += f"/{_as_uuid(project_id)}"
elif project_name is not None:
params["name"] = project_name
else:
Expand Down Expand Up @@ -1240,7 +1245,7 @@ def delete_project(
elif project_id is None:
raise ValueError("Must provide project_name or project_id")
response = self.session.delete(
self.api_url + f"/sessions/{project_id}",
self.api_url + f"/sessions/{_as_uuid(project_id)}",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand Down Expand Up @@ -1305,7 +1310,7 @@ def read_dataset(
path = "/datasets"
params: Dict[str, Any] = {"limit": 1}
if dataset_id is not None:
path += f"/{dataset_id}"
path += f"/{_as_uuid(dataset_id)}"
elif dataset_name is not None:
params["name"] = dataset_name
else:
Expand Down Expand Up @@ -1347,7 +1352,7 @@ def read_dataset_openai_finetuning(
else:
raise ValueError("Must provide dataset_name or dataset_id")
response = self._get_with_retries(
f"{path}/{dataset_id}/openai_ft",
f"{path}/{_as_uuid(dataset_id)}/openai_ft",
)
dataset = [json.loads(line) for line in response.text.strip().split("\n")]
return dataset
Expand Down Expand Up @@ -1403,7 +1408,7 @@ def delete_dataset(
if dataset_id is None:
raise ValueError("Must provide either dataset name or ID")
response = self.session.delete(
f"{self.api_url}/datasets/{dataset_id}",
f"{self.api_url}/datasets/{_as_uuid(dataset_id)}",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand Down Expand Up @@ -1672,7 +1677,7 @@ def read_example(self, example_id: ID_TYPE) -> ls_schemas.Example:
Example
The example.
"""
response = self._get_with_retries(f"/examples/{example_id}")
response = self._get_with_retries(f"/examples/{_as_uuid(example_id)}")
return ls_schemas.Example(**response.json())

def list_examples(
Expand Down Expand Up @@ -1744,7 +1749,7 @@ def update_example(
dataset_id=dataset_id,
)
response = self.session.patch(
f"{self.api_url}/examples/{example_id}",
f"{self.api_url}/examples/{_as_uuid(example_id)}",
headers={**self._headers, "Content-Type": "application/json"},
data=example.json(exclude_none=True),
)
Expand All @@ -1760,7 +1765,7 @@ def delete_example(self, example_id: ID_TYPE) -> None:
The ID of the example to delete.
"""
response = self.session.delete(
f"{self.api_url}/examples/{example_id}",
f"{self.api_url}/examples/{_as_uuid(example_id)}",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand Down Expand Up @@ -2094,7 +2099,7 @@ def update_feedback(
if comment is not None:
feedback_update["comment"] = comment
response = self.session.patch(
self.api_url + f"/feedback/{feedback_id}",
self.api_url + f"/feedback/{_as_uuid(feedback_id)}",
headers={**self._headers, "Content-Type": "application/json"},
data=json.dumps(feedback_update, default=_serialize_json),
)
Expand All @@ -2113,7 +2118,7 @@ def read_feedback(self, feedback_id: ID_TYPE) -> ls_schemas.Feedback:
Feedback
The feedback.
"""
response = self._get_with_retries(f"/feedback/{feedback_id}")
response = self._get_with_retries(f"/feedback/{_as_uuid(feedback_id)}")
return ls_schemas.Feedback(**response.json())

def list_feedback(
Expand Down Expand Up @@ -2166,7 +2171,7 @@ def delete_feedback(self, feedback_id: ID_TYPE) -> None:
The ID of the feedback to delete.
"""
response = self.session.delete(
f"{self.api_url}/feedback/{feedback_id}",
f"{self.api_url}/feedback/{_as_uuid(feedback_id)}",
headers=self._headers,
)
ls_utils.raise_for_status_with_text(response)
Expand Down
3 changes: 3 additions & 0 deletions python/langsmith/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,6 @@ class DatasetShareSchema(TypedDict, total=False):
dataset_id: UUID
share_token: UUID
url: str


Example.update_forward_refs()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this intentional?

Copy link
Collaborator Author

@hinthornw hinthornw Nov 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I can separate to diff pr but was getting an issue with a kinda strange usage pattern

Loading