Skip to content

Commit

Permalink
add pagination for database queries & workspace comments | add `after…
Browse files Browse the repository at this point in the history
…` parameter for appending blocks | add `public_url` property to page/database
  • Loading branch information
ayvi-0001 committed Jul 31, 2023
1 parent cc31536 commit 8961314
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 172 deletions.
47 changes: 29 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ parent_db = notion.Database(homepage.parent_id)
# will also look for env var `TZ` to set the default timezone. If not found, will default to local timezone.
```

`__getitem__` searchs for page property values when indexing a Page, and for property objects when indexing a Database.
Indexing a page will search for page property values, and indexing a Database will search for property objects.
A full list can be retrieved for both using;
- `retrieve()` method for a Page, with the optional `filter_properties` parameter.
- `retrieve` attribute for a Database.

```py
homepage['dependencies']
Expand Down Expand Up @@ -128,8 +131,8 @@ homepage.icon = "https://www.notion.so/icons/alien-pixel_purple.svg"

## Creating Pages/Databases/Blocks

The current version of the Notion api does not allow pages to be created to the parent `workspace`.
Create objects by passing an existing Page/Database instance as an arg to the `create` classmethods.
Create objects by passing an existing Page/Database instance to the `create` classmethods.
The current version of the Notion api does not allow pages/databases to be created to the parent `workspace`.

```py
new_database = notion.Database.create(
Expand All @@ -143,8 +146,11 @@ new_database = notion.Database.create(
new_page = notion.Page.create(new_database, page_title="A new database row")
```

Blocks are also created using the classmethods of `Block`. They all require a parent instance of either `Page` or `Block` to append the new block too.
The newly created block is returned as an instance of `Block`, which can be used as the parent instance to a nested block.
Blocks are also created using classmethods. They require a parent instance of either `Page` or `Block` to append the new block too.
The newly created block is returned as an instance of `Block`, which can be used as the parent instance to another nested block.

By default, blocks are appended to the bottom of the parent block.
To append the block somewhere else other than the bottom of the parent block, use the `after` parameter and set its value to the ID of the block that the new block should be appended after. The block_id used in the `after` paramater must still be a child to the parent instance.
```py
from notion import properties as prop

Expand Down Expand Up @@ -243,16 +249,16 @@ If a property of that name already exists, but it's a different type than the me
The original parameters will be saved if you decide to switch back (i.e. if you change a formula column to a select column, upon changing it back to a formula column, the original formula expression will still be there).

```py
new_database.formula_column("page id", expression="id()")
new_database.formula_column("page_id", expression="id()")

new_database.delete_property("url")

new_database.multiselect_column(
"new options column",
"New Options Column",
options=[
prop.Option("option-a", prop.PropertyColor.red),
prop.Option("option-b", prop.PropertyColor.green),
prop.Option("option-c", prop.PropertyColor.blue),
prop.Option("Option A", prop.PropertyColor.red),
prop.Option("Option B", prop.PropertyColor.green),
prop.Option("Option C", prop.PropertyColor.blue),
],
)

Expand All @@ -267,11 +273,14 @@ new_page.set_multiselect("options", ["option-a", "option-b"])
## Database Queries

A single `notion.query.PropertyFilter` is equivalent to filtering one property type in Notion.
To build filters equivalent to Notion's 'advanced filters', use `notion.query.CompoundFilter`.
To build nested filters, use `notion.query.CompoundFilter` and group property filters chained together by `_and(...)` / `_or(...)`.

The database method `query()` will return the raw response from the API.
The method `query_pages()` will extract the page ID for each object in the array of results, and return a list of `notion.Page` objects.

> note: in v0.6.0, new query methods were added: `query_all()` & `query_all_pages()` - these handle pagination, and instead of providing the `next_cursor` and `has_more` keys,
> they will iterate through all the results, or up to the `max_page_size` paramater. These 2 methods will replace the original ones in a later update.
```py
from datetime import datetime
from datetime import timedelta
Expand Down Expand Up @@ -334,19 +343,21 @@ Error 400: The request body does not match the schema for the expected parameter
```

Possible errors are:
- `NotionConflictError`
- `NotionDatabaseConnectionUnavailable`
- `NotionGatewayTimeout`
- `NotionInvalidGrant`
- `NotionInternalServerError`
- `NotionInvalidJson`
- `NotionInvalidRequestUrl`
- `NotionInvalidRequest`
- `NotionValidationError`
- `NotionInvalidRequestUrl`
- `NotionMissingVersion`
- `NotionUnauthorized`
- `NotionRestrictedResource`
- `NotionObjectNotFound`
- `NotionConflictError`
- `NotionRateLimited`
- `NotionInternalServerError`
- `NotionRestrictedResource`
- `NotionServiceUnavailable`
- `NotionDatabaseConnectionUnavailable`
- `NotionUnauthorized`
- `NotionValidationError`

A common error to look out for is `NotionObjectNotFound`. This error is often raised because your bot has not been added as a connection to the page.

Expand Down
2 changes: 2 additions & 0 deletions notion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
ToDoBlock,
)

check_for_pkg_update()

__all__: Sequence[str] = (
"Page",
"Database",
Expand Down
2 changes: 1 addition & 1 deletion notion/api/_about.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
__notion_version__: Final[str] = "2022-06-28"
__content_type__: Final[str] = "application/json"
__base_url__: Final[str] = "https://api.notion.com/v1/"
__version__: Final[str] = "0.5.5"
__version__: Final[str] = "0.6.0"
__package_url__: Final[str] = "https://pypi.org/pypi/notion-api/"
__package_json__: Final[str] = "https://pypi.org/pypi/notion-api/json"

Expand Down
78 changes: 78 additions & 0 deletions notion/api/_pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# MIT License

# Copyright (c) 2023 ayvi-0001

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# mypy: disable-error-code="return"

from functools import partial, partialmethod
from typing import Any, Callable, MutableMapping, Optional, Sequence, TypeVar

__all__ = ("paginated_response_endpoint", "paginated_response_payload")


T = TypeVar("T", covariant=True)


def paginated_response_endpoint(
call: partialmethod[MutableMapping[str, Sequence[T]]],
endpoint: partial[str],
max_page_size: Optional[int] = None,
) -> list[T]:
all: list[T] = []

def append_results(request: MutableMapping[str, Sequence[T]]) -> list[T] | None:
for result in request["results"]:
if max_page_size is not None and len(all) >= max_page_size:
return all
all.append(result)

if "next_cursor" in request and request["has_more"]:
append_results(
call.func(
endpoint(start_cursor=request["next_cursor"], page_size=max_page_size)
)
)

append_results(call.func(endpoint()))
return all


def paginated_response_payload(
call: Callable[[str], MutableMapping[str, Sequence[T]]],
endpoint: str,
payload: dict[str, Any],
max_page_size: Optional[int] = None,
) -> list[T]:
all: list[T] = []
request = partial(call, endpoint)

def append_results(response: MutableMapping[str, Sequence[T]]) -> list[T] | None:
for result in response["results"]:
if max_page_size is not None and len(all) >= max_page_size:
return all
all.append(result)

if "next_cursor" in response and response["has_more"]:
payload["start_cursor"] = response["next_cursor"]
append_results(request(payload))

append_results(request(payload))
return all
Loading

0 comments on commit 8961314

Please sign in to comment.