Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.prism.log
.vscode
_dev

__pycache__
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.0.0-alpha.23"
".": "3.0.0-alpha.24"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 16
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-886787346bfd9007dbd58542fddf6fad4592b1ccc2d1923f44378678dda7c1c1.yml
openapi_spec_hash: 1253fce7081c738f257275e9f49202b9
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-ad1692cfae7a00899ce6af1fd56b8294e5fd17d772a154a1671ff76c6ae623a9.yml
openapi_spec_hash: 007d1c70c133a31305c06a62d0319aee
config_hash: be10c837d5319a33f30809a3ec223caf
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.analysis.importFormat": "relative",
}
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## 3.0.0-alpha.24 (2025-08-10)

Full Changelog: [v3.0.0-alpha.23...v3.0.0-alpha.24](https://github.com/supermemoryai/python-sdk/compare/v3.0.0-alpha.23...v3.0.0-alpha.24)

### Features

* **api:** api update ([4aacfa8](https://github.com/supermemoryai/python-sdk/commit/4aacfa8f2e35f50ab9ac01c4ae9b5086b8dc2230))
* **api:** api update ([9fe90d9](https://github.com/supermemoryai/python-sdk/commit/9fe90d99035348910c215cb196a27390b7c595d3))
* **api:** api update ([f9c7013](https://github.com/supermemoryai/python-sdk/commit/f9c70137f404d7638d6e77dbf360a276877a55a5))
* **api:** api update ([125afc9](https://github.com/supermemoryai/python-sdk/commit/125afc957cab83c2a0c75ba003479b09e5e0f63c))
* **api:** api update ([04b249d](https://github.com/supermemoryai/python-sdk/commit/04b249d0a09d2fcbd8aecd08bcfc6ff89673fb75))
* **client:** support file upload requests ([b6c42b1](https://github.com/supermemoryai/python-sdk/commit/b6c42b10e8412ccc5dbbed23d86c36598319df00))


### Bug Fixes

* **parsing:** ignore empty metadata ([a58758c](https://github.com/supermemoryai/python-sdk/commit/a58758ce1f1ae0c87d0fa3bea43367bb2d198891))
* **parsing:** parse extra field types ([5253128](https://github.com/supermemoryai/python-sdk/commit/5253128de66dc303f8c9e4d295f133f24f770d95))


### Chores

* **internal:** fix ruff target version ([25adc14](https://github.com/supermemoryai/python-sdk/commit/25adc1412380631fa8ce53034b519e819b45dec3))
* **internal:** update comment in script ([8fc31e8](https://github.com/supermemoryai/python-sdk/commit/8fc31e8cb2058d8bb4da67c5aebbac421474c3b8))
* **project:** add settings file for vscode ([2327687](https://github.com/supermemoryai/python-sdk/commit/232768766d49d14af45667f08ad66b890cc6a230))
* update @stainless-api/prism-cli to v5.15.0 ([7f4ff8b](https://github.com/supermemoryai/python-sdk/commit/7f4ff8b2712055be8a6100a2c132b514cf7e2e6d))

## 3.0.0-alpha.23 (2025-07-15)

Full Changelog: [v3.0.0-alpha.22...v3.0.0-alpha.23](https://github.com/supermemoryai/python-sdk/compare/v3.0.0-alpha.22...v3.0.0-alpha.23)
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "supermemory"
version = "3.0.0-alpha.23"
version = "3.0.0-alpha.24"
description = "The official Python library for the supermemory API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -159,7 +159,7 @@ reportPrivateUsage = false
[tool.ruff]
line-length = 120
output-format = "grouped"
target-version = "py37"
target-version = "py38"

[tool.ruff.format]
docstring-code-format = true
Expand Down
4 changes: 2 additions & 2 deletions scripts/mock
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}"

# Run prism mock on the given spec
if [ "$1" == "--daemon" ]; then
npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log &
npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log &

# Wait for server to come online
echo -n "Waiting for server"
Expand All @@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then

echo
else
npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL"
npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL"
fi
2 changes: 1 addition & 1 deletion scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ elif ! prism_is_running ; then
echo -e "To run the server, pass in the path or url of your OpenAPI"
echo -e "spec to the prism command:"
echo
echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}"
echo

exit 1
Expand Down
5 changes: 4 additions & 1 deletion src/supermemory/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,10 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"

if is_body_allowed:
kwargs["json"] = json_data if is_given(json_data) else None
if isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
Expand Down
8 changes: 4 additions & 4 deletions src/supermemory/_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes:
return file

if is_tuple_t(file):
return (file[0], _read_file_content(file[1]), *file[2:])
return (file[0], read_file_content(file[1]), *file[2:])

raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


def _read_file_content(file: FileContent) -> HttpxFileContent:
def read_file_content(file: FileContent) -> HttpxFileContent:
if isinstance(file, os.PathLike):
return pathlib.Path(file).read_bytes()
return file
Expand Down Expand Up @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
return file

if is_tuple_t(file):
return (file[0], await _async_read_file_content(file[1]), *file[2:])
return (file[0], await async_read_file_content(file[1]), *file[2:])

raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")


async def _async_read_file_content(file: FileContent) -> HttpxFileContent:
async def async_read_file_content(file: FileContent) -> HttpxFileContent:
if isinstance(file, os.PathLike):
return await anyio.Path(file).read_bytes()

Expand Down
27 changes: 24 additions & 3 deletions src/supermemory/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride]
else:
fields_values[name] = field_get_default(field)

extra_field_type = _get_extra_fields_type(__cls)

_extra = {}
for key, value in values.items():
if key not in model_fields:
parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value

if PYDANTIC_V2:
_extra[key] = value
_extra[key] = parsed
else:
_fields_set.add(key)
fields_values[key] = value
fields_values[key] = parsed

object.__setattr__(m, "__dict__", fields_values)

Expand Down Expand Up @@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))


def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
if not PYDANTIC_V2:
# TODO
return None

schema = cls.__pydantic_core_schema__
if schema["type"] == "model":
fields = schema["schema"]
if fields["type"] == "model-fields":
extras = fields.get("extras_schema")
if extras and "cls" in extras:
# mypy can't narrow the type
return extras["cls"] # type: ignore[no-any-return]

return None


def is_basemodel(type_: type) -> bool:
"""Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
if is_union(type_):
Expand Down Expand Up @@ -439,7 +460,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]
type_ = type_.__value__ # type: ignore[unreachable]

# unwrap `Annotated[T, ...]` -> `T`
if metadata is not None:
if metadata is not None and len(metadata) > 0:
meta: tuple[Any, ...] = tuple(metadata)
elif is_annotated_type(type_):
meta = get_args(type_)[1:]
Expand Down
2 changes: 1 addition & 1 deletion src/supermemory/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "supermemory"
__version__ = "3.0.0-alpha.23" # x-release-please-version
__version__ = "3.0.0-alpha.24" # x-release-please-version
4 changes: 2 additions & 2 deletions src/supermemory/resources/memories.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> None:
"""
Delete a memory
Delete a memory by ID

Args:
extra_headers: Send extra headers
Expand Down Expand Up @@ -465,7 +465,7 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> None:
"""
Delete a memory
Delete a memory by ID

Args:
extra_headers: Send extra headers
Expand Down
5 changes: 3 additions & 2 deletions src/supermemory/types/connection_get_by_id_response.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, Optional
from datetime import datetime

from pydantic import Field as FieldInfo

Expand All @@ -12,14 +13,14 @@
class ConnectionGetByIDResponse(BaseModel):
id: str

created_at: float = FieldInfo(alias="createdAt")
created_at: datetime = FieldInfo(alias="createdAt")

provider: str

document_limit: Optional[float] = FieldInfo(alias="documentLimit", default=None)

email: Optional[str] = None

expires_at: Optional[float] = FieldInfo(alias="expiresAt", default=None)
expires_at: Optional[datetime] = FieldInfo(alias="expiresAt", default=None)

metadata: Optional[Dict[str, object]] = None
5 changes: 3 additions & 2 deletions src/supermemory/types/connection_get_by_tags_response.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, Optional
from datetime import datetime

from pydantic import Field as FieldInfo

Expand All @@ -12,14 +13,14 @@
class ConnectionGetByTagsResponse(BaseModel):
id: str

created_at: float = FieldInfo(alias="createdAt")
created_at: datetime = FieldInfo(alias="createdAt")

provider: str

document_limit: Optional[float] = FieldInfo(alias="documentLimit", default=None)

email: Optional[str] = None

expires_at: Optional[float] = FieldInfo(alias="expiresAt", default=None)
expires_at: Optional[datetime] = FieldInfo(alias="expiresAt", default=None)

metadata: Optional[Dict[str, object]] = None
5 changes: 3 additions & 2 deletions src/supermemory/types/connection_list_response.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import Dict, List, Optional
from datetime import datetime
from typing_extensions import TypeAlias

from pydantic import Field as FieldInfo
Expand All @@ -13,15 +14,15 @@
class ConnectionListResponseItem(BaseModel):
id: str

created_at: float = FieldInfo(alias="createdAt")
created_at: datetime = FieldInfo(alias="createdAt")

provider: str

document_limit: Optional[float] = FieldInfo(alias="documentLimit", default=None)

email: Optional[str] = None

expires_at: Optional[float] = FieldInfo(alias="expiresAt", default=None)
expires_at: Optional[datetime] = FieldInfo(alias="expiresAt", default=None)

metadata: Optional[Dict[str, object]] = None

Expand Down
3 changes: 3 additions & 0 deletions src/supermemory/types/search_execute_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Result(BaseModel):
title: Optional[str] = None
"""Document title"""

type: Optional[str] = None
"""Document type"""

updated_at: datetime = FieldInfo(alias="updatedAt")
"""Document last update date"""

Expand Down
29 changes: 28 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from typing import Any, Dict, List, Union, Optional, cast
from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
from datetime import datetime, timezone
from typing_extensions import Literal, Annotated, TypeAliasType

Expand Down Expand Up @@ -934,3 +934,30 @@ class Type2(BaseModel):
)
assert isinstance(model, Type1)
assert isinstance(model.value, InnerType2)


@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now")
def test_extra_properties() -> None:
class Item(BaseModel):
prop: int

class Model(BaseModel):
__pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]

other: str

if TYPE_CHECKING:

def __getattr__(self, attr: str) -> Item: ...

model = construct_type(
type_=Model,
value={
"a": {"prop": 1},
"other": "foo",
},
)
assert isinstance(model, Model)
assert model.a.prop == 1
assert isinstance(model.a, Item)
assert model.other == "foo"