From 422352f7c5f18a2f77f49d773f7ffc99af816b7c Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Thu, 4 Dec 2025 16:20:58 +0000 Subject: [PATCH 1/5] Adding data sources and updating databases for new Notion API version --- python_notion_api/async_api/__init__.py | 2 + python_notion_api/async_api/api.py | 25 ++- .../async_api/notion_data_source.py | 172 ++++++++++++++++++ .../async_api/notion_database.py | 149 ++------------- python_notion_api/async_api/notion_page.py | 10 +- .../async_api/tests/test_async_api.py | 17 +- .../async_api/tests/test_async_data_source.py | 66 +++++++ .../async_api/tests/test_async_database.py | 50 ++--- .../async_api/tests/test_properties.py | 61 ++++--- python_notion_api/async_api/utils.py | 4 +- python_notion_api/models/__init__.py | 2 + python_notion_api/models/common.py | 8 +- python_notion_api/models/configurations.py | 3 + python_notion_api/models/fields.py | 1 + python_notion_api/models/objects.py | 41 ++++- python_notion_api/models/paginations.py | 13 +- python_notion_api/sync_api/api.py | 156 ++++++++++++---- python_notion_api/sync_api/test_sync.py | 85 ++++++--- 18 files changed, 587 insertions(+), 278 deletions(-) create mode 100644 python_notion_api/async_api/notion_data_source.py create mode 100644 python_notion_api/async_api/tests/test_async_data_source.py diff --git a/python_notion_api/async_api/__init__.py b/python_notion_api/async_api/__init__.py index 74a4f26..5741c0e 100644 --- a/python_notion_api/async_api/__init__.py +++ b/python_notion_api/async_api/__init__.py @@ -6,6 +6,7 @@ create_property_iterator, ) from python_notion_api.async_api.notion_block import NotionBlock +from python_notion_api.async_api.notion_data_source import NotionDataSource from python_notion_api.async_api.notion_database import NotionDatabase from python_notion_api.async_api.notion_page import NotionPage @@ -14,6 +15,7 @@ "NotionBlock", "NotionPage", "NotionDatabase", + "NotionDataSource", "AsyncPropertyItemIterator", "AsyncRollupPropertyItemIterator", "AsyncBlockIterator", diff --git a/python_notion_api/async_api/api.py b/python_notion_api/async_api/api.py index d0e9ae4..154cdb4 100644 --- a/python_notion_api/async_api/api.py +++ b/python_notion_api/async_api/api.py @@ -8,6 +8,7 @@ from loguru import logger from python_notion_api.async_api.notion_block import NotionBlock +from python_notion_api.async_api.notion_data_source import NotionDataSource from python_notion_api.async_api.notion_database import NotionDatabase from python_notion_api.async_api.notion_page import NotionPage from python_notion_api.async_api.retry_strategy import RetryStrategy @@ -35,7 +36,7 @@ class AsyncNotionAPI: def __init__( self, access_token: str, - api_version: str = "2022-06-28", + api_version: str = "2025-09-03", page_limit: int = 20, rate_limit: tuple[int, int] = (500, 200), ): @@ -74,13 +75,25 @@ async def get_database(self, database_id: str) -> NotionDatabase: return database - async def get_page( - self, page_id: str, page_cast: type[NotionPage] = NotionPage - ) -> NotionPage: - """Gets Notion page. + async def get_data_source(self, data_source_id: str) -> NotionDataSource: + """Gets Notion DataSource Args: - page_id: Id of the database to fetch. + data_source_id: Id of the data source to fetch. + + Returns: + A Notion DataSource with the given id. + """ + data_source = NotionDataSource(self, data_source_id) + await data_source.reload() + + return data_source + + async def get_page(self, page_id, page_cast=NotionPage) -> NotionPage: + """Gets Notion Page + + Args: + page_id: Id of the page to fetch. page_cast: A subclass of a NotionPage. Allows custom property retrieval. Returns: diff --git a/python_notion_api/async_api/notion_data_source.py b/python_notion_api/async_api/notion_data_source.py new file mode 100644 index 0000000..650ff92 --- /dev/null +++ b/python_notion_api/async_api/notion_data_source.py @@ -0,0 +1,172 @@ +from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING + +from pydantic.v1 import BaseModel + +from python_notion_api.async_api.notion_page import NotionPage +from python_notion_api.async_api.utils import ensure_loaded +from python_notion_api.models.common import FileObject, ParentObject +from python_notion_api.models.configurations import ( + NotionPropertyConfiguration, + RelationPropertyConfiguration, +) +from python_notion_api.models.filters import FilterItem +from python_notion_api.models.objects import DataSource +from python_notion_api.models.sorts import Sort +from python_notion_api.models.values import PropertyValue, generate_value + +if TYPE_CHECKING: + from python_notion_api.async_api.api import AsyncNotionAPI + + +class NotionDataSource: + """Wrapper for a Notion datasource object. + + Args: + api: Instance of the NotionAPI. + data_source_id: Id of the data source. + """ + + class CreatePageRequest(BaseModel): + parent: ParentObject + properties: Dict[str, PropertyValue] + cover: Optional[FileObject] + + def __init__(self, api: "AsyncNotionAPI", data_source_id: str): + self._api = api + self._data_source_id = data_source_id + self._object = None + self._properties = None + self._title = None + + @ensure_loaded + def __getattr__(self, attr_key): + return getattr(self._object, attr_key) + + @property + def data_source_id(self) -> str: + return self._data_source_id.replace("-", "") + + async def reload(self): + self._object = await self._api._get( + endpoint=f"data_sources/{self._data_source_id}", + cast_cls=DataSource, + ) + + if self._object is None: + raise Exception( + f"Error loading data source {self._data_source_id}" + ) + + self._properties = { + key: NotionPropertyConfiguration.from_obj(val) + for key, val in self._object.properties.items() + } + self._title = "".join(rt.plain_text for rt in self._object.title) + + async def query( + self, + filters: Optional[FilterItem] = None, + sorts: Optional[List[Sort]] = None, + page_limit: Optional[int] = None, + cast_cls=NotionPage, + ) -> Generator[NotionPage, None, None]: + """A wrapper for 'Query a data source' action. + + Retrieves all pages belonging to the data source. + + Args: + filters: + sorts: + cast_cls: A subclass of a NotionPage. Allows custom + property retrieval + + """ + data = {} + if filters is not None: + filters = filters.dict(by_alias=True, exclude_unset=True) + data["filter"] = filters + + if sorts is not None: + data["sorts"] = [ + sort.dict(by_alias=True, exclude_unset=True) for sort in sorts + ] + + async for item in self._api._post_iterate( + endpoint=f"data_sources/{self._data_source_id}/query", + data=data, + page_limit=page_limit, + ): + yield cast_cls( + api=self._api, data_source=self, page_id=item.page_id, obj=item + ) + + @property + @ensure_loaded + def title(self) -> str: + """Get the title of the data source.""" + return self._title + + @property + @ensure_loaded + def properties(self) -> Dict[str, NotionPropertyConfiguration]: + """Get all property configurations of the data source.""" + return self._properties + + @property + @ensure_loaded + def relations(self) -> Dict[str, RelationPropertyConfiguration]: + """Get all property configurations of the data source that are + relations. + """ + return { + key: val + for key, val in self._properties.items() + if isinstance(val, RelationPropertyConfiguration) + } + + async def create_page( + self, + properties: Dict[str, Any] = {}, + cover_url: Optional[str] = None, + ) -> NotionPage: + """Creates a new page in the Data Source and updates the new page with + the properties. + + Args: + properties: Dictionary of property names and values. Value types + will depend on the property type. Can be the raw value + (e.g. string, float) or an object (e.g. SelectValue, + NumberPropertyItem) + cover_url: URL of an image for the page cover. + """ + + validated_properties = {} + for prop_name, prop_value in properties.items(): + prop = self.properties.get(prop_name, None) + if prop is None: + raise ValueError(f"Unknown property: {prop_name}") + value = generate_value(prop.config_type, prop_value) + validated_properties[prop_name] = value + + request = NotionDataSource.CreatePageRequest( + parent=ParentObject( + type="data_source_id", data_source_id=self.data_source_id + ), + properties=validated_properties, + cover=( + FileObject.from_url(cover_url) + if cover_url is not None + else None + ), + ) + + data = request.json(by_alias=True, exclude_unset=True) + + new_page = await self._api._post("pages", data=data) + + return NotionPage( + api=self._api, + page_id=new_page.page_id, + obj=new_page, + data_source=self, + ) diff --git a/python_notion_api/async_api/notion_database.py b/python_notion_api/async_api/notion_database.py index 9ed7364..1ecc808 100644 --- a/python_notion_api/async_api/notion_database.py +++ b/python_notion_api/async_api/notion_database.py @@ -2,17 +2,9 @@ from pydantic.v1 import BaseModel -from python_notion_api.async_api.notion_page import NotionPage from python_notion_api.async_api.utils import ensure_loaded -from python_notion_api.models.common import FileObject, ParentObject -from python_notion_api.models.configurations import ( - NotionPropertyConfiguration, - RelationPropertyConfiguration, -) -from python_notion_api.models.filters import FilterItem +from python_notion_api.models.common import DataSourceObject from python_notion_api.models.objects import Database -from python_notion_api.models.sorts import Sort -from python_notion_api.models.values import PropertyValue, generate_value if TYPE_CHECKING: from python_notion_api.async_api.api import AsyncNotionAPI @@ -26,16 +18,11 @@ class NotionDatabase: database_id: Id of the database. """ - class CreatePageRequest(BaseModel): - parent: ParentObject - properties: Dict[str, PropertyValue] - cover: Optional[FileObject] - def __init__(self, api: "AsyncNotionAPI", database_id: str): self._api = api self._database_id = database_id self._object = None - self._properties = None + self._data_sources = None self._title = None @ensure_loaded @@ -47,62 +34,6 @@ def database_id(self) -> str: """Gets the database id.""" return self._database_id.replace("-", "") - async def reload(self): - """Reloads the database from Notion.""" - self._object = await self._api._get( - endpoint=f"databases/{self._database_id}", cast_cls=Database - ) - - if self._object is None: - raise Exception(f"Error loading database {self._database_id}") - - self._properties = { - key: NotionPropertyConfiguration.from_obj(val) - for key, val in self._object.properties.items() - } - self._title = "".join(rt.plain_text for rt in self._object.title) - - async def query( - self, - filters: Optional[FilterItem] = None, - sorts: Optional[List[Sort]] = None, - page_limit: Optional[int] = None, - cast_cls=NotionPage, - ) -> AsyncGenerator[NotionPage, None]: - """Queries the database. - - Retrieves all pages belonging to the database that satisfy the given filters - in the order specified by the sorts. - - Args: - filters: Filters to apply to the query. - sorts: Sorts to apply to the query. - cast_cls: A subclass of a NotionPage. Allows custom - property retrieval. - - Returns: - Generator of NotionPage objects. - """ - data: dict[str, Any] = {} - - if filters is not None: - filters = filters.dict(by_alias=True, exclude_unset=True) - data["filter"] = filters - - if sorts is not None: - data["sorts"] = [ - sort.dict(by_alias=True, exclude_unset=True) for sort in sorts - ] - - async for item in self._api._post_iterate( - endpoint=f"databases/{self._database_id}/query", - data=data, - page_limit=page_limit, - ): - yield cast_cls( - api=self._api, database=self, page_id=item.page_id, obj=item - ) - @property @ensure_loaded def title(self) -> str: @@ -112,70 +43,20 @@ def title(self) -> str: @property @ensure_loaded - def properties(self) -> Dict[str, NotionPropertyConfiguration]: - """Gets all property configurations of the database.""" - assert self._properties is not None - return self._properties - - @property - @ensure_loaded - def relations(self) -> Dict[str, RelationPropertyConfiguration]: - """Gets all property configurations of the database that are - relations. - """ - assert self._properties is not None - return { - key: val - for key, val in self._properties.items() - if isinstance(val, RelationPropertyConfiguration) - } - - async def create_page( - self, - properties: Dict[str, Any] = {}, - cover_url: Optional[str] = None, - ) -> NotionPage: - """Creates a new page in the Database and updates the new page with - the properties. - - Args: - properties: Dictionary of property names and values. Value types - will depend on the property type. Can be the raw value - (e.g. string, float) or an object (e.g. SelectValue, - NumberPropertyItem) - cover: URL of an image for the page cover. - - Returns: - A new page. - """ - - validated_properties = {} - for prop_name, prop_value in properties.items(): - prop = self.properties.get(prop_name, None) - if prop is None: - raise ValueError(f"Unknown property: {prop_name}") - value = generate_value(prop.config_type, prop_value) - validated_properties[prop_name] = value + def data_sources(self) -> List[DataSourceObject]: + """Gets all data sources of the database.""" + return self._data_sources - request = NotionDatabase.CreatePageRequest( - parent=ParentObject( - type="database_id", database_id=self.database_id - ), - properties=validated_properties, - cover=( - FileObject.from_url(cover_url) - if cover_url is not None - else None - ), + async def reload(self): + self._object = await self._api._get( + endpoint=f"databases/{self._database_id}", + cast_cls=Database, ) - data = request.json(by_alias=True, exclude_unset=True) - - new_page = await self._api._post("pages", data=data) + if self._object is None: + raise Exception(f"Error loading database {self._database_id}") - return NotionPage( - api=self._api, - page_id=new_page.page_id, - obj=new_page, - database=self, - ) + self._data_sources = [ + DataSourceObject(**val) for val in self._object.data_sources + ] + self._title = "".join(rt.plain_text for rt in self._object.title) diff --git a/python_notion_api/async_api/notion_page.py b/python_notion_api/async_api/notion_page.py index 817b59f..ea789bc 100644 --- a/python_notion_api/async_api/notion_page.py +++ b/python_notion_api/async_api/notion_page.py @@ -9,7 +9,7 @@ create_property_iterator, ) from python_notion_api.async_api.utils import ensure_loaded -from python_notion_api.models.objects import Block, Database, Page, Pagination +from python_notion_api.models.objects import Block, DataSource, Page, Pagination from python_notion_api.models.properties import PropertyItem from python_notion_api.models.values import PropertyValue, generate_value @@ -40,20 +40,20 @@ def __init__( api: "AsyncNotionAPI", page_id: str, obj: Optional[Page] = None, - database: Optional[Database] = None, + data_source: Optional[DataSource] = None, ): self._api = api self._page_id = page_id self._object = obj - self.database = database + self.data_source = data_source async def reload(self): """Reloads page from Notion.""" self._object = await self._api._get(endpoint=f"pages/{self._page_id}") if self._object is not None: - parent_id = self.parent.database_id + parent_id = self.parent.data_source_id if parent_id is not None: - self.database = await self._api.get_database(parent_id) + self.data_source = await self._api.get_data_source(parent_id) @ensure_loaded def __getattr__(self, attr_key: str): diff --git a/python_notion_api/async_api/tests/test_async_api.py b/python_notion_api/async_api/tests/test_async_api.py index 8132e70..4f91c4e 100644 --- a/python_notion_api/async_api/tests/test_async_api.py +++ b/python_notion_api/async_api/tests/test_async_api.py @@ -2,10 +2,12 @@ from pytest_asyncio import fixture as async_fixture from python_notion_api.async_api.notion_block import NotionBlock +from python_notion_api.async_api.notion_data_source import NotionDataSource from python_notion_api.async_api.notion_database import NotionDatabase from python_notion_api.async_api.notion_page import NotionPage TEST_DATABASE_ID = "401076f6c7c04ae796bf3e4c847361e1" +TEST_DATA_SOURCE_ID = "924fbc0cb38f4a09ac2f967266f5c743" TEST_BLOCK_ID = "f572e889cd374edbbd15d8bf13174bbc" @@ -19,6 +21,11 @@ async def database(async_api): return await async_api.get_database(database_id=TEST_DATABASE_ID) +@async_fixture +async def data_source(async_api): + return await async_api.get_data_source(data_source_id=TEST_DATA_SOURCE_ID) + + @async_fixture async def block(async_api): return await async_api.get_block(block_id=TEST_BLOCK_ID) @@ -32,14 +39,20 @@ async def test_api_is_valid(self, async_api): async def test_page_is_valid(self, page): assert isinstance(page, NotionPage) assert page._object is not None - assert isinstance(page.database, NotionDatabase) + assert isinstance(page.data_source, NotionDataSource) async def test_database_is_valid(self, database): assert isinstance(database, NotionDatabase) assert database._object is not None - assert database._properties is not None + assert database._data_sources is not None assert database._title is not None + async def test_data_source_is_valid(self, data_source): + assert isinstance(data_source, NotionDataSource) + assert data_source._object is not None + assert data_source._properties is not None + assert data_source._title is not None + async def test_block_is_valid(self, block): assert isinstance(block, NotionBlock) assert block._object is not None diff --git a/python_notion_api/async_api/tests/test_async_data_source.py b/python_notion_api/async_api/tests/test_async_data_source.py new file mode 100644 index 0000000..d67d033 --- /dev/null +++ b/python_notion_api/async_api/tests/test_async_data_source.py @@ -0,0 +1,66 @@ +import random + +from pytest import mark +from pytest_asyncio import fixture as async_fixture + +from python_notion_api.async_api.notion_data_source import NotionDataSource +from python_notion_api.async_api.notion_page import NotionPage + +TEST_DATA_SOURCE_ID = "924fbc0cb38f4a09ac2f967266f5c743" + + +@mark.asyncio +class TestAsyncDataSource: + @async_fixture + async def data_source(self, async_api): + data_source = NotionDataSource( + data_source_id=TEST_DATA_SOURCE_ID, api=async_api + ) + await data_source.reload() + return data_source + + async def test_load_data_source(self, data_source): + assert data_source is not None + assert data_source._object is not None + assert data_source.title is not None + assert data_source.properties is not None + assert data_source.relations is not None + + async def test_create_data_source_page(self, data_source): + new_page = await data_source.create_page(properties={}) + assert isinstance(new_page, NotionPage) + assert new_page._object is not None + + async def test_create_data_source_page_with_properties(self, data_source): + properties = { + "Text": "".join([random.choice("abcd") for _ in range(10)]), + "Number": int("".join([random.choice("1234") for _ in range(3)])), + } + new_page = await data_source.create_page(properties=properties) + + assert await new_page.get("Text") == properties["Text"] + assert await new_page.get("Number") == properties["Number"] + + async def test_query_data_source(self, data_source): + pages = data_source.query() + page = await anext(pages) + assert isinstance(page, NotionPage) + + async def test_get_object_property(self, data_source): + created_time = data_source.created_time + assert created_time is not None + + async def test_get_title(self, data_source): + title = data_source.title + assert title is not None + + async def test_get_properties(self, data_source): + properties = data_source.properties + assert isinstance(properties, dict) + + async def test_get_relations(self, data_source): + relations = data_source.relations + assert isinstance(relations, dict) + + for _, relation in relations.items(): + assert relation.config_type == "relation" diff --git a/python_notion_api/async_api/tests/test_async_database.py b/python_notion_api/async_api/tests/test_async_database.py index edf940a..d1038b5 100644 --- a/python_notion_api/async_api/tests/test_async_database.py +++ b/python_notion_api/async_api/tests/test_async_database.py @@ -1,12 +1,14 @@ -import random - from pytest import mark from pytest_asyncio import fixture as async_fixture from python_notion_api.async_api.notion_database import NotionDatabase -from python_notion_api.async_api.notion_page import NotionPage +from python_notion_api.models.common import DataSourceObject TEST_DATABASE_ID = "401076f6c7c04ae796bf3e4c847361e1" +TEST_DATA_SOURCE_IDS = [ + "28d751c8a89180ed80c8000b05bd4bb1", + "924fbc0cb38f4a09ac2f967266f5c743", +] @mark.asyncio @@ -21,28 +23,7 @@ async def test_load_database(self, database): assert database is not None assert database._object is not None assert database.title is not None - assert database.properties is not None - assert database.relations is not None - - async def test_create_database_page(self, database): - new_page = await database.create_page(properties={}) - assert isinstance(new_page, NotionPage) - assert new_page._object is not None - - async def test_create_database_page_with_properties(self, database): - properties = { - "Text": "".join([random.choice("abcd") for _ in range(10)]), - "Number": int("".join([random.choice("1234") for _ in range(3)])), - } - new_page = await database.create_page(properties=properties) - - assert await new_page.get("Text") == properties["Text"] - assert await new_page.get("Number") == properties["Number"] - - async def test_query_database(self, database): - pages = database.query() - page = await anext(pages) - assert isinstance(page, NotionPage) + assert database.data_sources is not None async def test_get_object_property(self, database): created_time = database.created_time @@ -52,13 +33,16 @@ async def test_get_title(self, database): title = database.title assert title is not None - async def test_get_properties(self, database): - properties = database.properties - assert isinstance(properties, dict) + async def test_get_data_sources(self, database): + data_sources = database.data_sources + assert isinstance(data_sources, list) - async def test_get_relations(self, database): - relations = database.relations - assert isinstance(relations, dict) + data_sources.sort(key=lambda x: x.data_source_id) - for _, relation in relations.items(): - assert relation.config_type == "relation" + for i, data_source in enumerate(data_sources): + assert isinstance(data_source, DataSourceObject) + assert ( + data_source.data_source_id.replace("-", "") + == TEST_DATA_SOURCE_IDS[i] + ) + assert data_source.data_source_name is not None diff --git a/python_notion_api/async_api/tests/test_properties.py b/python_notion_api/async_api/tests/test_properties.py index 67caac3..a80c2ad 100644 --- a/python_notion_api/async_api/tests/test_properties.py +++ b/python_notion_api/async_api/tests/test_properties.py @@ -16,6 +16,7 @@ from python_notion_api.models.sorts import Sort TEST_DB = "401076f6c7c04ae796bf3e4c847361e1" +TEST_DS = "924fbc0cb38f4a09ac2f967266f5c743" TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 @@ -36,17 +37,25 @@ async def database(async_api): return await async_api.get_database(database_id=TEST_DB) +@async_fixture +async def data_source(async_api): + return await async_api.get_data_source(data_source_id=TEST_DS) + + @mark.asyncio class TestCore: async def test_get_database(self, database): assert database.database_id == TEST_DB - async def test_create_empty_page(self, database): - new_page = await database.create_page() + async def test_get_data_source(self, data_source): + assert data_source.data_source_id == TEST_DS + + async def test_create_empty_page(self, data_source): + new_page = await data_source.create_page() assert new_page is not None - async def test_create_empty_page_with_cover(self, database, cover_url): - new_page = await database.create_page(cover_url=cover_url) + async def test_create_empty_page_with_cover(self, data_source, cover_url): + new_page = await data_source.create_page(cover_url=cover_url) assert new_page is not None async def test_get_page(self, async_api, example_page_id): @@ -71,6 +80,14 @@ def api(cls): ) return cls.async_api + @async_fixture(scope="class") + async def data_source(cls, api): + if not hasattr(cls, "async_data_source"): + cls.async_data_source = await cls.async_api.get_data_source( + data_source_id=TEST_DS + ) + return cls.async_data_source + @async_fixture(scope="class") async def database(cls, api): if not hasattr(cls, "async_database"): @@ -80,9 +97,9 @@ async def database(cls, api): return cls.async_database @async_fixture(scope="class") - async def page(cls, database): + async def page(cls, data_source): if not hasattr(cls, "async_page"): - cls.async_page = await cls.async_database.create_page() + cls.async_page = await cls.async_data_source.create_page() return cls.async_page @mark.parametrize( @@ -155,8 +172,8 @@ async def test_set_relation(self, page): @mark.skip( reason="This test will create a notification for the TEST_PEOPLE" ) - async def test_create_new_page(self, database): - new_page = await database.create_page( + async def test_create_new_page(self, data_source): + new_page = await data_source.create_page( properties={ "Name": TEST_TITLE, "Text": TEST_TEXT, @@ -237,20 +254,20 @@ async def test_empty_rollup(self, async_api): @mark.asyncio -class TestDatabase: - async def test_query_database(self, database): - database.query() +class TestDataSource: + async def test_query_database(self, data_source): + data_source.query() - async def test_prop_filter(self, database): - pages = database.query( + async def test_prop_filter(self, data_source): + pages = data_source.query( filters=SelectFilter(property="Select", equals=TEST_SELECT) ) page = await anext(pages) value = await page.get("Select") assert value == TEST_SELECT - async def test_and_filter(self, database): - pages = database.query( + async def test_and_filter(self, data_source): + pages = data_source.query( filters=and_filter( [ SelectFilter(property="Select", equals=TEST_SELECT), @@ -262,8 +279,8 @@ async def test_and_filter(self, database): value = await page.get("Select") assert value == TEST_SELECT - async def test_or_filter(self, database): - pages = database.query( + async def test_or_filter(self, data_source): + pages = data_source.query( filters=or_filter( [ SelectFilter(property="Select", equals=TEST_SELECT), @@ -275,12 +292,14 @@ async def test_or_filter(self, database): value = await page.get("Select") assert value == TEST_SELECT - async def test_sort(self, database): - pages = database.query(sorts=[Sort(property="Date")]) + async def test_sort(self, data_source): + pages = data_source.query(sorts=[Sort(property="Date")]) page = await anext(pages) assert page is not None - async def test_descending_sort(self, database): - pages = database.query(sorts=[Sort(property="Date", descending=True)]) + async def test_descending_sort(self, data_source): + pages = data_source.query( + sorts=[Sort(property="Date", descending=True)] + ) page = await anext(pages) assert page is not None diff --git a/python_notion_api/async_api/utils.py b/python_notion_api/async_api/utils.py index 15108ef..7561000 100644 --- a/python_notion_api/async_api/utils.py +++ b/python_notion_api/async_api/utils.py @@ -3,10 +3,10 @@ def ensure_loaded(fn): """Checks that the `_object` of the method's class is fetched before - perfomign operations on it. + performing operations on it. Args: - fn: method of `NotionPage` or `NodtionDatabase` or other class + fn: method of `NotionPage` or `NotionDatabase` or other class that has `_object` attribute. """ is_coroutine = inspect.iscoroutinefunction(fn) diff --git a/python_notion_api/models/__init__.py b/python_notion_api/models/__init__.py index da1104a..b0bcbad 100644 --- a/python_notion_api/models/__init__.py +++ b/python_notion_api/models/__init__.py @@ -106,6 +106,7 @@ from python_notion_api.models.objects import ( Block, Database, + DataSource, NotionObject, NotionObjectBase, Page, @@ -177,6 +178,7 @@ "User", "Pagination", "Database", + "DataSource", "Page", "Block", "PropertyItem", diff --git a/python_notion_api/models/common.py b/python_notion_api/models/common.py index 0535b04..4ad7419 100644 --- a/python_notion_api/models/common.py +++ b/python_notion_api/models/common.py @@ -3,7 +3,7 @@ from pydantic.v1 import BaseModel -from python_notion_api.models.fields import idField, typeField +from python_notion_api.models.fields import idField, nameField, typeField class LinkObject(BaseModel): @@ -93,6 +93,7 @@ class ParentObject(BaseModel): parent_type: str = typeField page_id: Optional[str] database_id: Optional[str] + data_source_id: Optional[str] class SelectObject(BaseModel): @@ -136,3 +137,8 @@ class RollupObject(BaseModel): class UniqueIDObject(BaseModel): prefix: Optional[str] number: int + + +class DataSourceObject(BaseModel): + data_source_id: str = idField + data_source_name: str = nameField diff --git a/python_notion_api/models/configurations.py b/python_notion_api/models/configurations.py index b34fb3a..75eb5d8 100644 --- a/python_notion_api/models/configurations.py +++ b/python_notion_api/models/configurations.py @@ -13,6 +13,7 @@ class NotionPropertyConfiguration(NotionObjectBase): config_id: str = idField config_type: str = typeField name: str + description: Optional[str] _class_map = { "title": "TitlePropertyConfiguration", @@ -146,6 +147,7 @@ def _class_key_field(self): class SinglePropertyConfigurationObject(BaseModel): database_id: str + data_source_id: str relation_type: str = typeField single_property: Dict @@ -163,6 +165,7 @@ class SyncedPropertyConfigurationObject(BaseModel): class DualPropertyConfigurationObject(BaseModel): database_id: str + data_source_id: str dual_property: SyncedPropertyConfigurationObject diff --git a/python_notion_api/models/fields.py b/python_notion_api/models/fields.py index 0e7e731..d46dd5c 100644 --- a/python_notion_api/models/fields.py +++ b/python_notion_api/models/fields.py @@ -8,3 +8,4 @@ filterField = Field(alias="filter") andField = Field(alias="and") orField = Field(alias="or") +nameField = Field(alias="name") diff --git a/python_notion_api/models/objects.py b/python_notion_api/models/objects.py index 6bbb579..054bc0f 100644 --- a/python_notion_api/models/objects.py +++ b/python_notion_api/models/objects.py @@ -60,6 +60,7 @@ class NotionObject(NotionObjectBase, extra=Extra.allow): "list": "Pagination", "property_item": "PropertyItem", "database": "Database", + "data_source": "DataSource", "page": "Page", "user": "User", "block": "Block", @@ -103,14 +104,14 @@ class Pagination(NotionObject): "user", "database", "property_item", - "page_or_database", + "page_or_data_source", ] = typeField _class_map = { "property_item": "PropertyItemPagination", "page": "PagePagination", "block": "BlockPagination", - "page_or_database": "PageOrDatabasePagination", + "page_or_data_source": "PageOrDataSourcePagination", } @property @@ -118,24 +119,45 @@ def _class_key_field(self): return self.pagination_type +class DataSource(NotionObject): + _class_key_field = None + + ds_object: str = Optional[objectField] + ds_id: str = idField + created_time: Optional[str] + created_by: Optional[User] + last_edited_time: Optional[str] + last_edited_by: Optional[User] + properties: Optional[Dict] + parent: Optional[ParentObject] + database_parent: Optional[ParentObject] + title: Optional[List[RichTextObject]] + description: Optional[List[RichTextObject]] + icon: Optional[Union[FileObject, EmojiObject]] + cover: Optional[Union[FileObject, Dict[str, Union[str, FileObject]]]] + url: Optional[str] + archived: Optional[bool] + + class Database(NotionObject): _class_key_field = None db_object: str = objectField db_id: str = idField created_time: str - created_by: User + created_by: Optional[User] last_edited_time: str - last_edited_by: User + last_edited_by: Optional[User] title: List[RichTextObject] description: List[RichTextObject] icon: Optional[Union[FileObject, EmojiObject]] cover: Optional[Union[FileObject, Dict[str, Union[str, FileObject]]]] - properties: Dict - parent: Dict + parent: ParentObject url: str - archived: bool + in_trash: bool is_inline: bool + public_url: Optional[str] + data_sources: List[Dict] class Page(NotionObject): @@ -151,6 +173,11 @@ class Page(NotionObject): properties: Dict[str, Dict] parent: ParentObject archived: bool + in_trash: bool + is_locked: bool + url: str + public_url: Optional[str] + icon: Optional[Union[FileObject, EmojiObject]] class Block(NotionObject): diff --git a/python_notion_api/models/paginations.py b/python_notion_api/models/paginations.py index fe5b572..5b293f6 100644 --- a/python_notion_api/models/paginations.py +++ b/python_notion_api/models/paginations.py @@ -1,6 +1,11 @@ from typing import Dict, List, Union -from python_notion_api.models.objects import Block, Database, Page, Pagination +from python_notion_api.models.objects import ( + Block, + DataSource, + Page, + Pagination, +) from python_notion_api.models.properties import PropertyItem @@ -11,11 +16,11 @@ class PagePagination(Pagination): results: List[Page] -class PageOrDatabasePagination(Pagination): +class PageOrDataSourcePagination(Pagination): _class_key_field = None - page_or_database: Dict - results: List[Union[Page, Database]] + page_or_data_source: Dict + results: List[Union[Page, DataSource]] class PropertyItemPagination(Pagination): diff --git a/python_notion_api/sync_api/api.py b/python_notion_api/sync_api/api.py index 1644dd9..1dbdd41 100644 --- a/python_notion_api/sync_api/api.py +++ b/python_notion_api/sync_api/api.py @@ -10,7 +10,11 @@ from requests.packages.urllib3.exceptions import MaxRetryError from requests.packages.urllib3.util.retry import Retry -from python_notion_api.models.common import FileObject, ParentObject +from python_notion_api.models.common import ( + DataSourceObject, + FileObject, + ParentObject, +) from python_notion_api.models.configurations import ( NotionPropertyConfiguration, RelationPropertyConfiguration, @@ -24,6 +28,7 @@ from python_notion_api.models.objects import ( Block, Database, + DataSource, NotionObjectBase, Page, Pagination, @@ -57,12 +62,12 @@ def __init__( api: NotionAPI, page_id: str, obj: Optional[Page] = None, - database: Optional[NotionDatabase] = None, + data_source: Optional[NotionDataSource] = None, ): self._api = api self._page_id = page_id self._object = obj - self.database = database + self.data_source = data_source if self._object is None: self.reload() @@ -72,9 +77,10 @@ def __init__( if self._object is None: raise ValueError(f"Page {page_id} could not be found") - if database is None: - parent_id = self.parent.database_id - self.database = self._api.get_database(parent_id) + if data_source is None: + parent_id = self.parent.data_source_id + if parent_id is not None: + self.data_source = self._api.get_data_source(parent_id) def __getattr__(self, attr_key: str): return getattr(self._object, attr_key) @@ -455,11 +461,6 @@ class NotionDatabase: database_id: Id of the database. """ - class CreatePageRequest(BaseModel): - parent: ParentObject - properties: dict[str, PropertyValue] - cover: Optional[FileObject] - def __init__(self, api: NotionAPI, database_id: str): self._api = api self._database_id = database_id @@ -470,10 +471,9 @@ def __init__(self, api: NotionAPI, database_id: str): if self._object is None: raise Exception(f"Error accessing database {self._database_id}") - self._properties = { - key: NotionPropertyConfiguration.from_obj(val) - for key, val in self._object.properties.items() - } + self._data_sources = [ + DataSourceObject(**val) for val in self._object.data_sources + ] self._title = "".join(rt.plain_text for rt in self._object.title) @property @@ -491,20 +491,51 @@ def title(self) -> str: return self._title @property - def properties(self) -> dict[str, NotionPropertyConfiguration]: - """Gets all property configurations of the database.""" - return self._properties + def data_sources(self) -> list[DataSourceObject]: + """Gets all data sources of the database.""" + return self._data_sources + + +class NotionDataSource: + """Wrapper for a Notion data source object. + + Args: + api: Instance of the NotionAPI. + data_source_id: Id of the data source. + """ + + class CreatePageRequest(BaseModel): + parent: ParentObject + properties: dict[str, PropertyValue] + cover: Optional[FileObject] + + def __init__(self, api: NotionAPI, data_source_id: str): + self._api = api + self._data_source_id = data_source_id + self._object = self._api._get( + endpoint=f"data_sources/{self._data_source_id}", + cast_cls=DataSource, + ) + + if self._object is None: + raise Exception( + f"Error accessing data source {self._data_source_id}" + ) + + self._properties = { + key: NotionPropertyConfiguration.from_obj(val) + for key, val in self._object.properties.items() + } + self._title = "".join(rt.plain_text for rt in self._object.title) @property - def relations(self) -> dict[str, RelationPropertyConfiguration]: - """Gets all property configurations of the database that are - relations. + def data_source_id(self) -> str: + """Gets data source id. + + Returns: + Id of the data source. """ - return { - key: val - for key, val in self._properties.items() - if isinstance(val, RelationPropertyConfiguration) - } + return self._data_source_id.replace("-", "") def query( self, @@ -513,9 +544,9 @@ def query( cast_cls=NotionPage, page_limit: Optional[int] = None, ) -> Generator[NotionPage, None, None]: - """Queries the database. + """Queries the data source. - Retrieves all pages belonging to the database that satisfy the given filters + Retrieves all pages belonging to the data source that satisfy the given filters in the order specified by the sorts. Args: @@ -539,21 +570,63 @@ def query( ] for item in self._api._post_iterate( - endpoint=f"databases/{self._database_id}/query", + endpoint=f"data_sources/{self._data_source_id}/query", data=data, retry_strategy=self._api.post_retry_strategy, page_limit=page_limit, ): yield cast_cls( - api=self._api, database=self, page_id=item.page_id, obj=item + api=self._api, data_source=self, page_id=item.page_id, obj=item ) + @property + def title(self) -> str: + """Get the title of the data source.""" + return self._title + + @property + def properties(self) -> dict[str, NotionPropertyConfiguration]: + """Gets all property configurations of the data source.""" + return self._properties + + @property + def relations(self) -> dict[str, RelationPropertyConfiguration]: + """Gets all property configurations of the data source that are + relations. + """ + return { + key: val + for key, val in self._properties.items() + if isinstance(val, RelationPropertyConfiguration) + } + + def get_property(self, prop_config: Any, prop_value: str) -> Any: + """Create property for a given property configuration.""" + + if isinstance(prop_value, (PropertyItem, PropertyItemIterator)): + type_ = prop_value.property_type + + if type_ != prop_config.config_type: + # Have a mismatch between the property type and the + # given item + raise TypeError( + f"Item {prop_value.__class__} given as " + f"the value for property " + f"{prop_config.__class__}" + ) + new_prop = prop_value + + else: + new_prop = prop_config.create_property(prop_value) + + return new_prop + def create_page( self, properties: dict[str, Any] = {}, cover_url: Optional[str] = None, ) -> NotionPage: - """Creates a new page in the Database and updates the new page with + """Creates a new page in the data source and updates the new page with the properties. Args: @@ -561,7 +634,7 @@ def create_page( will depend on the property type. Can be the raw value (e.g. string, float) or an object (e.g. SelectValue, NumberPropertyItem) - cover: URL of an image for the page cover. + cover_url: URL of an image for the page cover. Returns: A new page. @@ -575,9 +648,9 @@ def create_page( value = generate_value(prop.config_type, prop_value) validated_properties[prop_name] = value - request = NotionDatabase.CreatePageRequest( + request = NotionDataSource.CreatePageRequest( parent=ParentObject( - type="database_id", database_id=self.database_id + type="data_source_id", data_source_id=self.data_source_id ), properties=validated_properties, cover=( @@ -599,7 +672,7 @@ def create_page( api=self._api, page_id=new_page.page_id, obj=new_page, - database=self, + data_source=self, ) @@ -615,7 +688,7 @@ class NotionAPI: def __init__( self, access_token: str, - api_version: str = "2022-06-28", + api_version: str = "2025-09-03", page_limit: int = 20, ): self._access_token = access_token @@ -888,19 +961,26 @@ def get_database(self, database_id: str) -> NotionDatabase: Args: database_id: Id of the database to fetch. - Returns: A Notion database with the given id. """ return NotionDatabase(self, database_id) + def get_data_source(self, data_source_id: str) -> NotionDataSource: + """Wrapper for 'Retrieve a data source' action. + + Args: + data_source_id: Id of the data source to fetch. + """ + return NotionDataSource(self, data_source_id) + def get_page( self, page_id: str, page_cast: Type[NotionPage] = NotionPage ) -> NotionPage: """Gets Notion page. Args: - page_id: Id of the database to fetch. + page_id: Id of the page to fetch. page_cast: A subclass of a NotionPage. Allows custom property retrieval. diff --git a/python_notion_api/sync_api/test_sync.py b/python_notion_api/sync_api/test_sync.py index 32e9696..c9c4596 100644 --- a/python_notion_api/sync_api/test_sync.py +++ b/python_notion_api/sync_api/test_sync.py @@ -14,7 +14,8 @@ from python_notion_api.models.sorts import Sort TEST_DB = "401076f6c7c04ae796bf3e4c847361e1" - +TEST_DS = "924fbc0cb38f4a09ac2f967266f5c743" +TEST_DS2 = "28d751c8a89180ed80c8000b05bd4bb1" TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 @@ -40,16 +41,24 @@ def database(api): return api.get_database(database_id=TEST_DB) +@fixture +def data_source(api): + return api.get_data_source(data_source_id=TEST_DS) + + class TestCore: def test_database_id(self, database): assert database.database_id == TEST_DB - def test_create_empty_page(self, database): - new_page = database.create_page() + def test_get_data_source(self, data_source): + assert data_source.data_source_id == TEST_DS + + def test_create_empty_page(self, data_source): + new_page = data_source.create_page() assert new_page is not None - def test_create_empty_page_with_cover(self, database, cover_url): - new_page = database.create_page(cover_url=cover_url) + def test_create_empty_page_with_cover(self, data_source, cover_url): + new_page = data_source.create_page(cover_url=cover_url) assert new_page is not None def test_get_page(self, api, example_page_id): @@ -67,8 +76,12 @@ def database(cls, api): return api.get_database(database_id=TEST_DB) @fixture(scope="class") - def new_page(cls, database): - return database.create_page() + def data_source(cls, api): + return api.get_data_source(data_source_id=TEST_DS) + + @fixture(scope="class") + def new_page(cls, data_source): + return data_source.create_page() @mark.parametrize( "property,value", @@ -150,8 +163,8 @@ def test_set_alive(self, new_page): @mark.skip( reason="This test will create a notification for the TEST_PEOPLE" ) - def test_create_new_page(self, database): - new_page = database.create_page( + def test_create_new_page(self, data_source): + new_page = data_source.create_page( properties={ "Name": TEST_TITLE, "Text": TEST_TEXT, @@ -238,18 +251,38 @@ def api(cls): def database(cls, api): return api.get_database(database_id=TEST_DB) - def test_query_database(self, database): - database.query() + def test_get_datasources(self, database): + database.data_sources.sort(key=lambda x: x.data_source_id) + assert ( + database.data_sources[0].data_source_id.replace("-", "") + == TEST_DS2 + ) + assert ( + database.data_sources[1].data_source_id.replace("-", "") == TEST_DS + ) + + +class TestDatasource: + @fixture(scope="class") + def api(cls): + return NotionAPI(access_token=os.environ.get("NOTION_TOKEN")) + + @fixture(scope="class") + def data_source(cls, api): + return api.get_data_source(data_source_id=TEST_DS) - def test_prop_filter(self, database): - pages = database.query( + def test_query_database(self, data_source): + data_source.query() + + def test_prop_filter(self, data_source): + pages = data_source.query( filters=SelectFilter(property="Select", equals=TEST_SELECT) ) page = next(pages) assert page.get("Select").value == TEST_SELECT - def test_and_filter(self, database): - pages = database.query( + def test_and_filter(self, data_source): + pages = data_source.query( filters=and_filter( [ SelectFilter(property="Select", equals=TEST_SELECT), @@ -260,8 +293,8 @@ def test_and_filter(self, database): page = next(pages) assert page.get("Select").value == TEST_SELECT - def test_large_and_filter(self, database): - pages = database.query( + def test_large_and_filter(self, data_source): + pages = data_source.query( filters=and_filter( [NumberFilter(property="Number", equals=TEST_NUMBER)] + [ @@ -275,8 +308,8 @@ def test_large_and_filter(self, database): page = next(pages) assert page.get("Number").value == TEST_NUMBER - def test_or_filter(self, database): - pages = database.query( + def test_or_filter(self, data_source): + pages = data_source.query( filters=or_filter( [ SelectFilter(property="Select", equals=TEST_SELECT), @@ -287,8 +320,8 @@ def test_or_filter(self, database): page = next(pages) assert page.get("Select").value == TEST_SELECT - def test_large_or_filter(self, database): - pages = database.query( + def test_large_or_filter(self, data_source): + pages = data_source.query( filters=or_filter( [ SelectFilter(property="Select", equals=TEST_SELECT), @@ -302,13 +335,15 @@ def test_large_or_filter(self, database): page = next(pages) assert page.get("Select").value == TEST_SELECT - def test_sort(self, database): - pages = database.query(sorts=[Sort(property="Date")]) + def test_sort(self, data_source): + pages = data_source.query(sorts=[Sort(property="Date")]) page = next(pages) assert page is not None - def test_descending_sort(self, database): - pages = database.query(sorts=[Sort(property="Date", descending=True)]) + def test_descending_sort(self, data_source): + pages = data_source.query( + sorts=[Sort(property="Date", descending=True)] + ) page = next(pages) assert page is not None From df068ea2bfc12b2f12d0fed2de9d043a80e121e3 Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Thu, 4 Dec 2025 16:30:07 +0000 Subject: [PATCH 2/5] Updating docs --- docs/get_started/data_sources.md | 154 +++++++++++++++++++++++++++++++ docs/get_started/databases.md | 138 --------------------------- docs/get_started/pages.md | 16 ++-- 3 files changed, 162 insertions(+), 146 deletions(-) create mode 100644 docs/get_started/data_sources.md diff --git a/docs/get_started/data_sources.md b/docs/get_started/data_sources.md new file mode 100644 index 0000000..b654e0e --- /dev/null +++ b/docs/get_started/data_sources.md @@ -0,0 +1,154 @@ +## Retrieve a data source + +=== "Async" + + ```python + async def main(): + async_api = AsyncNotionAPI(access_token='') + data_source = await async_api.get_data_source(data_source_id='') + ``` + +=== "Sync" + + ```python + api = NotionAPI(access_token='') + data_source = api.get_data_source(data_source_id='') + ``` + +## Query + +=== "Async" + + ```python + async def main(): + async_api = AsyncNotionAPI(access_token='') + data_source = await async_api.get_data_source(data_source_id='') + + async for page in data_source.query(): + ... + ``` + +=== "Sync" + + ```python + api = NotionAPI(access_token='') + data_source = api.get_data_source(data_source_id='') + + for page in data_source.query(): + ... + ``` + +### Filters + +You can use filter classes in `python_notion_api.models.filters` to create property filters and pass them to the query. + +=== "Async" + + ```python + from python_notion_api.models.filters import SelectFilter + + async def main(): + async_api = AsyncNotionAPI(access_token='') + data_source = await async_api.get_data_source(data_source_id='') + + await for page in data_source.query( + filters=SelectFilter(property='', equals='') + ): + ... + ``` + +=== "Sync" + + ```python + from python_notion_api.models.filters import SelectFilter + + api = NotionAPI(access_token='') + data_source = api.get_data_source(data_source_id='') + + for page in data_source.query( + filters=SelectFilter(property='', equals='') + ): + ... + ``` + +'and' and 'or' filters are supported: + +=== "Async" + + ```python + from python_notion_api.models.filters import SelectFilter, or_filter, and_filter + + async def main(): + async_api = AsyncNotionAPI(access_token='') + data_source = await async_api.get_data_source(data_source_id='') + + await for page in data_source.query( + filters=or_filter([ + SelectFilter(property="Select", equals="xxx"), + and_filter([ + NumberFilter(property="Number", greater_than=10), + CheckboxFilter(property="Checkbox", equals=True) + ]) + ]) + ): + ... + ``` + +=== "Sync" + + ```python + from python_notion_api.models.filters import SelectFilter, or_filter, and_filter + + api = NotionAPI(access_token='') + data_source = api.get_data_source(data_source_id='') + + for page in data_source.query( + filters=or_filter([ + SelectFilter(property="Select", equals="xxx"), + and_filter([ + NumberFilter(property="Number", greater_than=10), + CheckboxFilter(property="Checkbox", equals=True) + ]) + ]) + ) + ... + ``` + +You can read more on filters [here](https://developers.notion.com/reference/post-database-query-filter){:target="_blank"} + +### Sorts + +You can use `python_notion_api.models.sorts.Sort` class to create sorts and pass them to the query. + +=== "Async" + + ```python + from python_notion_api.models.sorts import Sort + + async def main(): + async_api = AsyncNotionAPI(access_token='') + data_source = await async_api.get_data_source(data_source_id='') + + await for page in data_source.query( + sorts=[ + Sort(property="Title"), + Sort(property="Date", descending=True) + ] + ): + ``` + +=== "Sync" + + ```python + from python_notion_api.models.sorts import Sort + + api = NotionAPI(access_token='') + data_source = api.get_data_source(data_source_id='') + + for page in data_source.query( + sorts=[ + Sort(property="Title"), + Sort(property="Date", descending=True) + ] + ) + ``` diff --git a/docs/get_started/databases.md b/docs/get_started/databases.md index 1e8f6eb..4c8b38a 100644 --- a/docs/get_started/databases.md +++ b/docs/get_started/databases.md @@ -14,141 +14,3 @@ api = NotionAPI(access_token='') database = api.get_database(database_id='') ``` - -## Query - -=== "Async" - - ```python - async def main(): - async_api = AsyncNotionAPI(access_token='') - database = await async_api.get_database(database_id='') - - async for page in database.query(): - ... - ``` - -=== "Sync" - - ```python - api = NotionAPI(access_token='') - database = api.get_database(database_id='') - - for page in database.query(): - ... - ``` - -### Filters - -You can use filter classes in `python_notion_api.models.filters` to create property filters and pass them to the query. - -=== "Async" - - ```python - from python_notion_api.models.filters import SelectFilter - - async def main(): - async_api = AsyncNotionAPI(access_token='') - database = await async_api.get_database(database_id='') - - await for page in database.query( - filters=SelectFilter(property='', equals='') - ): - ... - ``` - -=== "Sync" - - ```python - from python_notion_api.models.filters import SelectFilter - - api = NotionAPI(access_token='') - database = api.get_database(database_id='') - - for page in database.query( - filters=SelectFilter(property='', equals='') - ): - ... - ``` - -'and' and 'or' filters are supported: - -=== "Async" - - ```python - from python_notion_api.models.filters import SelectFilter, or_filter, and_filter - - async def main(): - async_api = AsyncNotionAPI(access_token='') - database = await async_api.get_database(database_id='') - - await for page in database.query( - filters=or_filter([ - SelectFilter(property="Select", equals="xxx"), - and_filter([ - NumberFilter(property="Number", greater_than=10), - CheckboxFilter(property="Checkbox", equals=True) - ]) - ]) - ): - ... - ``` - -=== "Sync" - - ```python - from python_notion_api.models.filters import SelectFilter, or_filter, and_filter - - api = NotionAPI(access_token='') - database = api.get_database(database_id='') - - for page in database.query( - filters=or_filter([ - SelectFilter(property="Select", equals="xxx"), - and_filter([ - NumberFilter(property="Number", greater_than=10), - CheckboxFilter(property="Checkbox", equals=True) - ]) - ]) - ) - ... - ``` - -You can read more on filters [here](https://developers.notion.com/reference/post-database-query-filter){:target="_blank"} - -### Sorts - -You can use `python_notion_api.models.sorts.Sort` class to create sorts and pass them to the query. - -=== "Async" - - ```python - from python_notion_api.models.sorts import Sort - - async def main(): - async_api = AsyncNotionAPI(access_token='') - database = await async_api.get_database(database_id='') - - await for page in database.query( - sorts=[ - Sort(property="Title"), - Sort(property="Date", descending=True) - ] - ): - ``` - -=== "Sync" - - ```python - from python_notion_api.models.sorts import Sort - - api = NotionAPI(access_token='') - database = api.get_database(database_id='') - - for page in database.query( - sorts=[ - Sort(property="Title"), - Sort(property="Date", descending=True) - ] - ) - ``` diff --git a/docs/get_started/pages.md b/docs/get_started/pages.md index 25dde12..87773eb 100644 --- a/docs/get_started/pages.md +++ b/docs/get_started/pages.md @@ -22,9 +22,9 @@ ```python async def main(): async_api = AsyncNotionAPI(access_token='') - database = await async_api.get_database(database_id='') + data_source = await async_api.get_data_source(data_source_id='') - await database.create_page(properties={ + await data_source.create_page(properties={ 'Number_property': 234, 'Select_property': 'select1', 'Checkbox_property': True, @@ -36,9 +36,9 @@ ```python api = NotionAPI(access_token='') - database = api.get_database(database_id='') + data_source = api.get_data_source(data_source_id='') - database.create_page(properties={ + data_source.create_page(properties={ 'Number_property': 234, 'Select_property': 'select1', 'Checkbox_property': True, @@ -130,8 +130,8 @@ In particular, the values of rollups and formulas may be incorrect when retrieve To use custom page properties, create a subclass of NotionPage. Define a function to get each custom property (these must return a `PropertyValue`) and define the mapping from Notion property names to the function names. ```python -from python_notion_api.api import NotionPage -from python_notion_api.models import RichTextObject +from python_notion_api.sync_api.api import NotionPage +from python_notion_api.models import RichTextObject, RichTextPropertyItem from python_notion_api.models.values import RichTextPropertyValue class MyPage(NotionPage): @@ -159,14 +159,14 @@ class MyPage(NotionPage): ``` -This page class can be passed when querying a database or getting a page. +This page class can be passed when querying a data source or getting a page. ```python page = api.get_page(page_id='', cast_cls=MyPage) -for page in database.query(cast_cls=MyPage, filters=NumberFilter(property='Value', equals=1)): +for page in data_source.query(cast_cls=MyPage, filters=NumberFilter(property='Value', equals=1)): print('Custom processing:', page.get('Value').value) print('Raw value:', page._direct_get('Value').value) ``` From dfc4a1884e759f163482aefd34e04634fb9a839c Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Thu, 4 Dec 2025 16:49:22 +0000 Subject: [PATCH 3/5] Updating type hints --- python_notion_api/async_api/notion_data_source.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python_notion_api/async_api/notion_data_source.py b/python_notion_api/async_api/notion_data_source.py index 650ff92..54b24ee 100644 --- a/python_notion_api/async_api/notion_data_source.py +++ b/python_notion_api/async_api/notion_data_source.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING +from typing import Any, Generator, Optional, TYPE_CHECKING from pydantic.v1 import BaseModel @@ -28,7 +28,7 @@ class NotionDataSource: class CreatePageRequest(BaseModel): parent: ParentObject - properties: Dict[str, PropertyValue] + properties: dict[str, PropertyValue] cover: Optional[FileObject] def __init__(self, api: "AsyncNotionAPI", data_source_id: str): @@ -66,7 +66,7 @@ async def reload(self): async def query( self, filters: Optional[FilterItem] = None, - sorts: Optional[List[Sort]] = None, + sorts: Optional[list[Sort]] = None, page_limit: Optional[int] = None, cast_cls=NotionPage, ) -> Generator[NotionPage, None, None]: @@ -108,13 +108,13 @@ def title(self) -> str: @property @ensure_loaded - def properties(self) -> Dict[str, NotionPropertyConfiguration]: + def properties(self) -> dict[str, NotionPropertyConfiguration]: """Get all property configurations of the data source.""" return self._properties @property @ensure_loaded - def relations(self) -> Dict[str, RelationPropertyConfiguration]: + def relations(self) -> dict[str, RelationPropertyConfiguration]: """Get all property configurations of the data source that are relations. """ @@ -126,7 +126,7 @@ def relations(self) -> Dict[str, RelationPropertyConfiguration]: async def create_page( self, - properties: Dict[str, Any] = {}, + properties: dict[str, Any] = {}, cover_url: Optional[str] = None, ) -> NotionPage: """Creates a new page in the Data Source and updates the new page with From c4777956158f7a6d13d33ed6bf8316470f19d95c Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Fri, 19 Dec 2025 13:48:11 +0000 Subject: [PATCH 4/5] Using dedicated Notion instance for testing --- conftest.py | 21 ++++++- .../async_api/tests/test_async_api.py | 20 +++--- .../async_api/tests/test_async_block.py | 6 +- .../async_api/tests/test_async_data_source.py | 6 +- .../async_api/tests/test_async_database.py | 29 ++++----- .../async_api/tests/test_async_page.py | 4 +- .../async_api/tests/test_properties.py | 47 +++++++------- .../async_api/tests/test_query.py | 3 +- python_notion_api/sync_api/test_sync.py | 62 +++++++++---------- 9 files changed, 98 insertions(+), 100 deletions(-) diff --git a/conftest.py b/conftest.py index 53a78c6..3aa509e 100644 --- a/conftest.py +++ b/conftest.py @@ -22,9 +22,24 @@ def cover_url() -> str: @fixture def example_page_id() -> str: - return "e439b7a7296d45b98805f24c9cfc2115" + return "2cef2075b1dc803a8ec3d142ac105817" + + +@fixture(scope="session") +def database_id() -> str: + return "2cef2075b1dc8023b0ece0dbf9b278f7" + + +@fixture(scope="session") +def data_source_id1() -> str: + return "2cef2075b1dc80bab834000b42b068d7" + + +@fixture +def data_source_id2() -> str: + return "2cef2075b1dc80048d8d000b1b75df8f" @fixture -def example_page_id_2() -> str: - return "d5bce0a0fe6248d0a120c6c693d9b597" +def block_id() -> str: + return "2cef2075b1dc80a59d6ad9ed97846ff8" \ No newline at end of file diff --git a/python_notion_api/async_api/tests/test_async_api.py b/python_notion_api/async_api/tests/test_async_api.py index 4f91c4e..f71da05 100644 --- a/python_notion_api/async_api/tests/test_async_api.py +++ b/python_notion_api/async_api/tests/test_async_api.py @@ -6,29 +6,25 @@ from python_notion_api.async_api.notion_database import NotionDatabase from python_notion_api.async_api.notion_page import NotionPage -TEST_DATABASE_ID = "401076f6c7c04ae796bf3e4c847361e1" -TEST_DATA_SOURCE_ID = "924fbc0cb38f4a09ac2f967266f5c743" -TEST_BLOCK_ID = "f572e889cd374edbbd15d8bf13174bbc" - @async_fixture -async def page(async_api, example_page_id_2): - return await async_api.get_page(page_id=example_page_id_2) +async def page(async_api, example_page_id): + return await async_api.get_page(page_id=example_page_id) @async_fixture -async def database(async_api): - return await async_api.get_database(database_id=TEST_DATABASE_ID) +async def database(async_api, database_id): + return await async_api.get_database(database_id=database_id) @async_fixture -async def data_source(async_api): - return await async_api.get_data_source(data_source_id=TEST_DATA_SOURCE_ID) +async def data_source(async_api, data_source_id1): + return await async_api.get_data_source(data_source_id=data_source_id1) @async_fixture -async def block(async_api): - return await async_api.get_block(block_id=TEST_BLOCK_ID) +async def block(async_api, block_id): + return await async_api.get_block(block_id=block_id) @mark.asyncio diff --git a/python_notion_api/async_api/tests/test_async_block.py b/python_notion_api/async_api/tests/test_async_block.py index 220c1b4..c2ab0fe 100644 --- a/python_notion_api/async_api/tests/test_async_block.py +++ b/python_notion_api/async_api/tests/test_async_block.py @@ -6,14 +6,12 @@ from python_notion_api.async_api.notion_block import NotionBlock from python_notion_api.models import ParagraphBlock, RichTextObject -TEST_BLOCK_ID = "f572e889cd374edbbd15d8bf13174bbc" - @mark.asyncio class TestAsyncBlock: @async_fixture - async def block(self, async_api): - block = NotionBlock(block_id=TEST_BLOCK_ID, api=async_api) + async def block(self, async_api, block_id): + block = NotionBlock(block_id=block_id, api=async_api) await block.reload() return block diff --git a/python_notion_api/async_api/tests/test_async_data_source.py b/python_notion_api/async_api/tests/test_async_data_source.py index d67d033..8deefd1 100644 --- a/python_notion_api/async_api/tests/test_async_data_source.py +++ b/python_notion_api/async_api/tests/test_async_data_source.py @@ -6,15 +6,13 @@ from python_notion_api.async_api.notion_data_source import NotionDataSource from python_notion_api.async_api.notion_page import NotionPage -TEST_DATA_SOURCE_ID = "924fbc0cb38f4a09ac2f967266f5c743" - @mark.asyncio class TestAsyncDataSource: @async_fixture - async def data_source(self, async_api): + async def data_source(self, async_api, data_source_id1): data_source = NotionDataSource( - data_source_id=TEST_DATA_SOURCE_ID, api=async_api + data_source_id=data_source_id1, api=async_api ) await data_source.reload() return data_source diff --git a/python_notion_api/async_api/tests/test_async_database.py b/python_notion_api/async_api/tests/test_async_database.py index d1038b5..0f15eab 100644 --- a/python_notion_api/async_api/tests/test_async_database.py +++ b/python_notion_api/async_api/tests/test_async_database.py @@ -4,18 +4,12 @@ from python_notion_api.async_api.notion_database import NotionDatabase from python_notion_api.models.common import DataSourceObject -TEST_DATABASE_ID = "401076f6c7c04ae796bf3e4c847361e1" -TEST_DATA_SOURCE_IDS = [ - "28d751c8a89180ed80c8000b05bd4bb1", - "924fbc0cb38f4a09ac2f967266f5c743", -] - @mark.asyncio class TestAsyncDatabase: @async_fixture - async def database(self, async_api): - database = NotionDatabase(database_id=TEST_DATABASE_ID, api=async_api) + async def database(self, async_api, database_id): + database = NotionDatabase(database_id=database_id, api=async_api) await database.reload() return database @@ -33,16 +27,19 @@ async def test_get_title(self, database): title = database.title assert title is not None - async def test_get_data_sources(self, database): + async def test_get_data_sources(self, database, data_source_id1, + data_source_id2): data_sources = database.data_sources assert isinstance(data_sources, list) data_sources.sort(key=lambda x: x.data_source_id) - for i, data_source in enumerate(data_sources): - assert isinstance(data_source, DataSourceObject) - assert ( - data_source.data_source_id.replace("-", "") - == TEST_DATA_SOURCE_IDS[i] - ) - assert data_source.data_source_name is not None + assert len(data_sources) == 2 + assert isinstance(data_sources[0], DataSourceObject) + assert data_sources[0].data_source_id.replace("-", "") == data_source_id2 + assert data_sources[0].data_source_name is not None + assert isinstance(data_sources[1], DataSourceObject) + assert data_sources[1].data_source_id.replace( + "-", "" + ) == data_source_id1 + assert data_sources[1].data_source_name is not None diff --git a/python_notion_api/async_api/tests/test_async_page.py b/python_notion_api/async_api/tests/test_async_page.py index f2178e0..e3c4ce0 100644 --- a/python_notion_api/async_api/tests/test_async_page.py +++ b/python_notion_api/async_api/tests/test_async_page.py @@ -12,8 +12,8 @@ @mark.asyncio class TestAsyncPage: @async_fixture - async def page(self, async_api, example_page_id_2): - async_page = NotionPage(page_id=example_page_id_2, api=async_api) + async def page(self, async_api, example_page_id): + async_page = NotionPage(page_id=example_page_id, api=async_api) await async_page.reload() return async_page diff --git a/python_notion_api/async_api/tests/test_properties.py b/python_notion_api/async_api/tests/test_properties.py index a80c2ad..3495279 100644 --- a/python_notion_api/async_api/tests/test_properties.py +++ b/python_notion_api/async_api/tests/test_properties.py @@ -15,8 +15,7 @@ ) from python_notion_api.models.sorts import Sort -TEST_DB = "401076f6c7c04ae796bf3e4c847361e1" -TEST_DS = "924fbc0cb38f4a09ac2f967266f5c743" + TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 @@ -24,7 +23,7 @@ TEST_STATUS = "In progress" TEST_MULTI_SELECT = ["foo", "bar", "baz"] TEST_DATE = datetime.now() -TEST_PEOPLE = ["fa9e1df9-7c24-427c-9c20-eac629565fe4"] +TEST_PEOPLE = ["2ced872b-594c-8176-a765-0002860b6fdc"] TEST_FILES = [File(name="foo.pdf", url="http://example.com/file")] TEST_CHECKBOX = True TEST_URL = "http://example.com" @@ -33,22 +32,22 @@ @async_fixture -async def database(async_api): - return await async_api.get_database(database_id=TEST_DB) +async def database(async_api, database_id): + return await async_api.get_database(database_id=database_id) @async_fixture -async def data_source(async_api): - return await async_api.get_data_source(data_source_id=TEST_DS) +async def data_source(async_api, data_source_id1): + return await async_api.get_data_source(data_source_id=data_source_id1) @mark.asyncio class TestCore: - async def test_get_database(self, database): - assert database.database_id == TEST_DB + async def test_get_database(self, database, database_id): + assert database.database_id == database_id - async def test_get_data_source(self, data_source): - assert data_source.data_source_id == TEST_DS + async def test_get_data_source(self, data_source, data_source_id1): + assert data_source.data_source_id == data_source_id1 async def test_create_empty_page(self, data_source): new_page = await data_source.create_page() @@ -81,18 +80,18 @@ def api(cls): return cls.async_api @async_fixture(scope="class") - async def data_source(cls, api): + async def data_source(cls, api, data_source_id1): if not hasattr(cls, "async_data_source"): cls.async_data_source = await cls.async_api.get_data_source( - data_source_id=TEST_DS + data_source_id=data_source_id1 ) return cls.async_data_source @async_fixture(scope="class") - async def database(cls, api): + async def database(cls, api, database_id): if not hasattr(cls, "async_database"): cls.async_database = await cls.async_api.get_database( - database_id=TEST_DB + database_id=database_id ) return cls.async_database @@ -198,18 +197,18 @@ async def test_get_unique_id(self, page): async def test_get_by_id(self, page): await page.set("Email", TEST_EMAIL) - email = await page.get("%3E%5Ehh", cache=False) + email = await page.get("FEe%40", cache=False) assert email == TEST_EMAIL async def test_set_by_id(self, page): - await page.set("%3E%5Ehh", TEST_EMAIL) + await page.set("FEe%40", TEST_EMAIL) email = await page.get("Email", cache=False) assert email == TEST_EMAIL async def test_update(self, page): await page.update( properties={ - "%3E%5Ehh": TEST_EMAIL, + "FEe%40": TEST_EMAIL, "Phone": TEST_PHONE, "Multi-select": None, } @@ -233,17 +232,15 @@ async def test_reload(self, page): @mark.asyncio class TestRollups: - NUMBER_PAGE_ID = "25e800a118414575ab30a8dc42689b74" - DATE_PAGE_ID = "e38bb90faf8a436895f089fed2446cc6" - EMPTY_ROLLUP_PAGE_ID = "2b5efae5bad24df884b4f953e3788b64" + EMPTY_ROLLUP_PAGE_ID = "2cef2075b1dc80b5b67edc58426e92f2" - async def test_number_rollup(self, async_api): - number_page = await async_api.get_page(self.NUMBER_PAGE_ID) + async def test_number_rollup(self, async_api, example_page_id): + number_page = await async_api.get_page(example_page_id) num = await number_page.get("Number rollup") assert num == 10 - async def test_date_rollup(self, async_api): - date_page = await async_api.get_page(self.DATE_PAGE_ID) + async def test_date_rollup(self, async_api, example_page_id): + date_page = await async_api.get_page(example_page_id) date = await date_page.get("Date rollup") assert isinstance(date.start, datetime) diff --git a/python_notion_api/async_api/tests/test_query.py b/python_notion_api/async_api/tests/test_query.py index e05d55b..a86aaa1 100644 --- a/python_notion_api/async_api/tests/test_query.py +++ b/python_notion_api/async_api/tests/test_query.py @@ -7,7 +7,8 @@ async def main(): api = AsyncNotionAPI(access_token=os.environ["NOTION_TOKEN"]) - db = await api.get_database(database_id="c0802577c79645e5af855f0ca46148b2") + db = await api.get_data_source( + data_source_id="2cef2075b1dc80bab834000b42b068d7") async for page in db.query(): print(await page.get("title")) diff --git a/python_notion_api/sync_api/test_sync.py b/python_notion_api/sync_api/test_sync.py index c9c4596..367defe 100644 --- a/python_notion_api/sync_api/test_sync.py +++ b/python_notion_api/sync_api/test_sync.py @@ -13,9 +13,7 @@ ) from python_notion_api.models.sorts import Sort -TEST_DB = "401076f6c7c04ae796bf3e4c847361e1" -TEST_DS = "924fbc0cb38f4a09ac2f967266f5c743" -TEST_DS2 = "28d751c8a89180ed80c8000b05bd4bb1" + TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 @@ -23,7 +21,7 @@ TEST_STATUS = "In progress" TEST_MULTI_SELECT = ["foo", "bar", "baz"] TEST_DATE = datetime.now() -TEST_PEOPLE = ["fa9e1df9-7c24-427c-9c20-eac629565fe4"] +TEST_PEOPLE = ["2ced872b-594c-8176-a765-0002860b6fdc"] TEST_FILES = [File(name="foo.pdf", url="http://example.com/file")] TEST_CHECKBOX = True TEST_URL = "http://colorifix.com" @@ -37,21 +35,21 @@ def api(): @fixture -def database(api): - return api.get_database(database_id=TEST_DB) +def database(api, database_id): + return api.get_database(database_id=database_id) @fixture -def data_source(api): - return api.get_data_source(data_source_id=TEST_DS) +def data_source(api, data_source_id1): + return api.get_data_source(data_source_id=data_source_id1) class TestCore: - def test_database_id(self, database): - assert database.database_id == TEST_DB + def test_database_id(self, database, database_id): + assert database.database_id == database_id - def test_get_data_source(self, data_source): - assert data_source.data_source_id == TEST_DS + def test_get_data_source(self, data_source, data_source_id1): + assert data_source.data_source_id == data_source_id1 def test_create_empty_page(self, data_source): new_page = data_source.create_page() @@ -72,12 +70,12 @@ def api(cls): return NotionAPI(access_token=os.environ.get("NOTION_TOKEN")) @fixture(scope="class") - def database(cls, api): - return api.get_database(database_id=TEST_DB) + def database(cls, api, database_id): + return api.get_database(database_id=database_id) @fixture(scope="class") - def data_source(cls, api): - return api.get_data_source(data_source_id=TEST_DS) + def data_source(cls, api, data_source_id1): + return api.get_data_source(data_source_id=data_source_id1) @fixture(scope="class") def new_page(cls, data_source): @@ -189,18 +187,18 @@ def test_get_unique_id(self, new_page): def test_get_by_id(self, new_page): new_page.set("Email", TEST_EMAIL) - email = new_page.get("%3E%5Ehh", cache=False).value + email = new_page.get("FEe%40", cache=False).value assert email == TEST_EMAIL def test_set_by_id(self, new_page): - new_page.set("%3E%5Ehh", TEST_EMAIL) + new_page.set("FEe%40", TEST_EMAIL) email = new_page.get("Email", cache=False).value assert email == TEST_EMAIL def test_update(self, new_page): new_page.update( properties={ - "%3E%5Ehh": TEST_EMAIL, + "FEe%40": TEST_EMAIL, "Phone": TEST_PHONE, "Multi-select": None, } @@ -222,17 +220,15 @@ def test_reload(self, new_page): class TestRollups: - NUMBER_PAGE_ID = "25e800a118414575ab30a8dc42689b74" - DATE_PAGE_ID = "e38bb90faf8a436895f089fed2446cc6" - EMPTY_ROLLUP_PAGE_ID = "2b5efae5bad24df884b4f953e3788b64" + EMPTY_ROLLUP_PAGE_ID = "2cef2075b1dc80b5b67edc58426e92f2" - def test_number_rollup(self, api): - number_page = api.get_page(self.NUMBER_PAGE_ID) + def test_number_rollup(self, api, example_page_id): + number_page = api.get_page(example_page_id) num = number_page.get("Number rollup") assert num.value == 10 - def test_date_rollup(self, api): - date_page = api.get_page(self.DATE_PAGE_ID) + def test_date_rollup(self, api, example_page_id): + date_page = api.get_page(example_page_id) date = date_page.get("Date rollup") assert isinstance(date.value.start, datetime) @@ -248,17 +244,17 @@ def api(cls): return NotionAPI(access_token=os.environ.get("NOTION_TOKEN")) @fixture(scope="class") - def database(cls, api): - return api.get_database(database_id=TEST_DB) + def database(cls, api, database_id): + return api.get_database(database_id=database_id) - def test_get_datasources(self, database): + def test_get_datasources(self, database, data_source_id1, data_source_id2): database.data_sources.sort(key=lambda x: x.data_source_id) assert ( database.data_sources[0].data_source_id.replace("-", "") - == TEST_DS2 + == data_source_id2 ) assert ( - database.data_sources[1].data_source_id.replace("-", "") == TEST_DS + database.data_sources[1].data_source_id.replace("-", "") == data_source_id1 ) @@ -268,8 +264,8 @@ def api(cls): return NotionAPI(access_token=os.environ.get("NOTION_TOKEN")) @fixture(scope="class") - def data_source(cls, api): - return api.get_data_source(data_source_id=TEST_DS) + def data_source(cls, api, data_source_id1): + return api.get_data_source(data_source_id=data_source_id1) def test_query_database(self, data_source): data_source.query() From dce71ec8561be58d19d675e2285d67765bbe07c0 Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Fri, 19 Dec 2025 13:54:13 +0000 Subject: [PATCH 5/5] Ruff formatting --- conftest.py | 4 ++-- python_notion_api/async_api/api.py | 2 +- python_notion_api/async_api/notion_data_source.py | 2 +- python_notion_api/async_api/notion_database.py | 4 +--- python_notion_api/async_api/notion_page.py | 7 ++++++- .../async_api/tests/test_async_database.py | 15 +++++++++------ .../async_api/tests/test_properties.py | 1 - python_notion_api/async_api/tests/test_query.py | 3 ++- python_notion_api/sync_api/test_sync.py | 4 ++-- 9 files changed, 24 insertions(+), 18 deletions(-) diff --git a/conftest.py b/conftest.py index 3aa509e..0ebedac 100644 --- a/conftest.py +++ b/conftest.py @@ -31,7 +31,7 @@ def database_id() -> str: @fixture(scope="session") -def data_source_id1() -> str: +def data_source_id1() -> str: return "2cef2075b1dc80bab834000b42b068d7" @@ -42,4 +42,4 @@ def data_source_id2() -> str: @fixture def block_id() -> str: - return "2cef2075b1dc80a59d6ad9ed97846ff8" \ No newline at end of file + return "2cef2075b1dc80a59d6ad9ed97846ff8" diff --git a/python_notion_api/async_api/api.py b/python_notion_api/async_api/api.py index 154cdb4..e3dca12 100644 --- a/python_notion_api/async_api/api.py +++ b/python_notion_api/async_api/api.py @@ -80,7 +80,7 @@ async def get_data_source(self, data_source_id: str) -> NotionDataSource: Args: data_source_id: Id of the data source to fetch. - + Returns: A Notion DataSource with the given id. """ diff --git a/python_notion_api/async_api/notion_data_source.py b/python_notion_api/async_api/notion_data_source.py index 54b24ee..e652ab1 100644 --- a/python_notion_api/async_api/notion_data_source.py +++ b/python_notion_api/async_api/notion_data_source.py @@ -1,4 +1,4 @@ -from typing import Any, Generator, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Generator, Optional from pydantic.v1 import BaseModel diff --git a/python_notion_api/async_api/notion_database.py b/python_notion_api/async_api/notion_database.py index 1ecc808..5c4a47e 100644 --- a/python_notion_api/async_api/notion_database.py +++ b/python_notion_api/async_api/notion_database.py @@ -1,6 +1,4 @@ -from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional - -from pydantic.v1 import BaseModel +from typing import TYPE_CHECKING, List from python_notion_api.async_api.utils import ensure_loaded from python_notion_api.models.common import DataSourceObject diff --git a/python_notion_api/async_api/notion_page.py b/python_notion_api/async_api/notion_page.py index ea789bc..bffb346 100644 --- a/python_notion_api/async_api/notion_page.py +++ b/python_notion_api/async_api/notion_page.py @@ -9,7 +9,12 @@ create_property_iterator, ) from python_notion_api.async_api.utils import ensure_loaded -from python_notion_api.models.objects import Block, DataSource, Page, Pagination +from python_notion_api.models.objects import ( + Block, + DataSource, + Page, + Pagination, +) from python_notion_api.models.properties import PropertyItem from python_notion_api.models.values import PropertyValue, generate_value diff --git a/python_notion_api/async_api/tests/test_async_database.py b/python_notion_api/async_api/tests/test_async_database.py index 0f15eab..fef4fe3 100644 --- a/python_notion_api/async_api/tests/test_async_database.py +++ b/python_notion_api/async_api/tests/test_async_database.py @@ -27,8 +27,9 @@ async def test_get_title(self, database): title = database.title assert title is not None - async def test_get_data_sources(self, database, data_source_id1, - data_source_id2): + async def test_get_data_sources( + self, database, data_source_id1, data_source_id2 + ): data_sources = database.data_sources assert isinstance(data_sources, list) @@ -36,10 +37,12 @@ async def test_get_data_sources(self, database, data_source_id1, assert len(data_sources) == 2 assert isinstance(data_sources[0], DataSourceObject) - assert data_sources[0].data_source_id.replace("-", "") == data_source_id2 + assert ( + data_sources[0].data_source_id.replace("-", "") == data_source_id2 + ) assert data_sources[0].data_source_name is not None assert isinstance(data_sources[1], DataSourceObject) - assert data_sources[1].data_source_id.replace( - "-", "" - ) == data_source_id1 + assert ( + data_sources[1].data_source_id.replace("-", "") == data_source_id1 + ) assert data_sources[1].data_source_name is not None diff --git a/python_notion_api/async_api/tests/test_properties.py b/python_notion_api/async_api/tests/test_properties.py index 3495279..961bdd8 100644 --- a/python_notion_api/async_api/tests/test_properties.py +++ b/python_notion_api/async_api/tests/test_properties.py @@ -15,7 +15,6 @@ ) from python_notion_api.models.sorts import Sort - TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 diff --git a/python_notion_api/async_api/tests/test_query.py b/python_notion_api/async_api/tests/test_query.py index a86aaa1..d145b72 100644 --- a/python_notion_api/async_api/tests/test_query.py +++ b/python_notion_api/async_api/tests/test_query.py @@ -8,7 +8,8 @@ async def main(): api = AsyncNotionAPI(access_token=os.environ["NOTION_TOKEN"]) db = await api.get_data_source( - data_source_id="2cef2075b1dc80bab834000b42b068d7") + data_source_id="2cef2075b1dc80bab834000b42b068d7" + ) async for page in db.query(): print(await page.get("title")) diff --git a/python_notion_api/sync_api/test_sync.py b/python_notion_api/sync_api/test_sync.py index 367defe..21d5290 100644 --- a/python_notion_api/sync_api/test_sync.py +++ b/python_notion_api/sync_api/test_sync.py @@ -13,7 +13,6 @@ ) from python_notion_api.models.sorts import Sort - TEST_TITLE = f"API Test {datetime.now(UTC).isoformat()}" TEST_TEXT = "Test text is boring" TEST_NUMBER = 12.5 @@ -254,7 +253,8 @@ def test_get_datasources(self, database, data_source_id1, data_source_id2): == data_source_id2 ) assert ( - database.data_sources[1].data_source_id.replace("-", "") == data_source_id1 + database.data_sources[1].data_source_id.replace("-", "") + == data_source_id1 )