1 Commits

Author SHA1 Message Date
Kralot fb85e82601 Adiciona README.md e CHANGELOG.md completo para lançamento beta 2026-01-23 23:30:23 -03:00
22 changed files with 474 additions and 99 deletions
Executable
+35
View File
@@ -0,0 +1,35 @@
# Changelog
Todas as mudanças notáveis neste projeto serão documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
## [Unreleased]
## [0.1.0-beta] - 2026-01-23
### Added
- Sistema de autenticação com suporte a múltiplas versões da API (`legacy` e `data_sources`)
- Client HTTP para endpoints `pages`, `databases` e `blocks`
- Sistema ORM declarativo com `NotionDatabase` para mapeamento de schemas
- Registry global de databases com `DatabaseRegistry`
- Container pattern para organização de databases (`DatabasesContainer`)
- Repositories para `pages` e `databases` com fluent interface
- Query builders (`QueryFilter` e `QuerySort`)
- Property extractors e parsers automáticos
- Schemas Pydantic para todas as respostas da API
- Suporte a Generic Types para autocomplete perfeito
- Computed fields e validators customizados
- Configuração global de timezone via `ORMConfig`
- Singleton pattern para Client configurável
- Transformers customizáveis para processamento de propriedades
### Notes
- Primeira release beta do projeto
- Documentação completa em desenvolvimento
- Alguns tipos de propriedades ainda não suportados
[0.1.0-beta]: [https://github.com/riguettodev/sdk-notion/releases/tag/v0.1.0-beta](https://github.com/riguettodev/sdk-notion/releases/tag/v0.1.0-beta)
+371 -2
View File
@@ -1,5 +1,374 @@
# Notion SDK # Notion SDK
**SDK para Integração com Notion API** com base forte em *pydantic*, *asyncio*, e programação orientada a objetos. > **Beta Release** 🎉\
> Desenvolvido por [Eduardo Riguetto](https://riguetto.dev)
Pensado para o uso como *ORMs*. Permitindo personalização total do usuário ao configurar suas Databases do Notion, com recursos como mapeamentos, transformadores, validadores, calculos e mais. ## 🎯 Visão Geral
SDK Python moderno e type-safe para a API Notion, com foco em Developer Experience (DX) e autocomplete perfeito.
---
## ✨ Principais Features
### 🔐 Autenticação Simplificada
- Configuração centralizada via `Notion()` com suporte a múltiplas versões da API
- Suporte para versões `legacy` (2022-06-28), `data_sources` (2025-09-03), ou personalizada
- Headers gerenciados automaticamente via singleton pattern
### 🗄️ ORM Declarativo
- **Sistema de Mapeamento Declarativo**: Defina schemas uma única vez com `NotionDatabase`
- **Type Hints Perfeitos**: Autocomplete completo em IDEs (VSCode, PyCharm)
- **Transformers Customizáveis**: Processe propriedades do Notion conforme necessário
- **Validators Integrados**: Validação automática com Pydantic
- **Computed Fields**: Campos calculados automaticamente a partir de outros campos
- **Registry Global**: Acesso centralizado a todas as databases via `DatabaseRegistry`
- **Container Pattern**: Organize suas databases com `DatabasesContainer`
### 📦 Repositories
**Pages:**
- `CreatePage` - Criação de páginas com fluent interface
- `GetPage` - Busca de páginas com mapeamento automático de schemas
- `GetPageProperty` - Acesso direto a propriedades específicas
**Databases:**
- `CreateDatabasePage` - Criação de páginas em databases com type safety
- `SearchPage` - Queries avançadas com filtros e ordenação
- `SearchPageProperty` - Busca de propriedades específicas em databases
### 🎨 Fluent Interface
```python
await notion.orm.repo.pages.CreatePage()\
.set_parent("database_id", "db_id")\
.set_title("Name", "My Page")\
.set_property.start_date("Date", datetime.now())\
.set_property.number("Count", 42)\
.call()
```
### 🔧 Features Avançadas
- **Generic Types**: Tipagem forte com Generic/TypeVars para autocomplete perfeito
- **Property System**: Sistema completo de setters para todos os tipos de propriedades
- **Query Builders**: Construtores fluentes para filtros (`QueryFilter`) e ordenação (`QuerySort`)
- **Property Extractors**: Extração automática de propriedades da API Notion
- **Property Parsers**: Parse inteligente de respostas com suporte a tipos complexos
- **Property Accessors**: Acesso tipado a propriedades de páginas
- **Configuração Global**: Timezone e outras configs definidas via `ORMConfig`
- **Singleton Pattern**: Client configurado globalmente, sem duplicação
---
## 🚀 Quick Start
```python
from notion import Notion
from my_databases import MyContainer
# Configuração
notion: Notion[MyContainer] = Notion(
api_token="secret_...",
api_version="data_sources",
orm_container=MyContainer,
timezone="America/Sao_Paulo"
)
# Uso com autocomplete perfeito
con = notion.orm.repo.databases.container.tasks.SearchPage
result = await con\
.set_filter(
con.filter.checkbox("Done", "equals", False)
)\
.call()
# Acesso type-safe
for page in result.results:
print(page.properties.myproperty) # ← Autocomplete funciona!
```
---
### 📦 Configurando seu ORM
#### 1. Defina seus Schemas
Crie schemas para suas databases do Notion usando `NotionDatabase`:
```python
# my_databases/tasks.py
from notion.types.orm.databases import NotionDatabase
from pydantic import ConfigDict
from typing import Optional, List
from datetime import date, datetime
from decimal import Decimal
class TasksDb(NotionDatabase):
model_config = ConfigDict(title="Notion_Databases_TasksDb")
# Defina os campos do schema
title: Optional[str]
status: Optional[str]
priority: Optional[str]
tags: Optional[List[str]]
due_date: Optional[date]
assignee: Optional[str]
completed: bool = False
progress: Optional[int]
estimated_hours: Optional[Decimal]
actual_hours: Optional[Decimal]
# Campos computed (não vêm do Notion, são calculados)
is_overdue: Optional[bool] = None
total_cost: Optional[Decimal] = None
class NotionConfig:
# ID da sua database no Notion
database_id = "abc123def456"
# Mapeie campos Python → Campos Notion
mappings = {
"title": "Title",
"status": "Status",
"priority": "Priority",
"tags": "Tags",
"due_date": "Due Date",
"assignee": "Assignee",
"completed": "Completed",
"progress": "Progress",
"estimated_hours": "Estimated Hours",
"actual_hours": "Actual Hours"
}
# Transformers para processar valores
transformers = {
"status": lambda x: x["name"] if x else None,
"priority": lambda x: x["name"] if x else None,
"tags": lambda x: [t["name"] for t in x] if x else [],
"assignee": lambda x: x[0] if x else None,
"due_date": lambda x: x["start"] if x else None,
"estimated_hours": lambda x: Decimal(str(x)) if x else None,
"actual_hours": lambda x: Decimal(str(x)) if x else None
}
# Validators para garantir integridade dos dados
validators = {
"progress": lambda v: max(0, min(100, v)) if v else 0, # Entre 0-100
"estimated_hours": lambda v: v if v and v >= 0 else Decimal(0) # Não negativo
}
# Computed fields calculados automaticamente
computed = {
"is_overdue": lambda obj: (
obj.due_date < date.today() and not obj.completed
if obj.due_date else False
),
"total_cost": lambda obj: (
obj.actual_hours * Decimal("50.00") # $50/hora
if obj.actual_hours else None
)
}
```
#### 2. Crie seu Container
Organize todas as suas databases em um container:
```python
# my_databases/__init__.py
from notion.types.orm.databases import Container, Registry, Client
from .tasks import TasksDb
from .projects import ProjectsDb # Outras databases...
class MyDatabasesContainer(Container):
"""Container customizado com suas databases"""
def __init__(self):
# Registre todas as databases
Registry.register(TasksDb)
Registry.register(ProjectsDb)
# Configure acesso tipado
self.tasks: Client[TasksDb] = Client(TasksDb.id())
self.projects: Client[ProjectsDb] = Client(ProjectsDb.id())
# Singleton para uso direto
databases = MyDatabasesContainer()
__all__ = ["MyDatabasesContainer", "databases"]
```
#### 3. Configure a Integração
No seu `main.py` ou arquivo de inicialização:
```python
from notion import Notion
from my_databases import MyDatabasesContainer
# Configure com seu container
notion = Notion(
api_token="secret_...",
api_version="data_sources",
orm_container=MyDatabasesContainer,
timezone="America/Sao_Paulo"
)
# Agora você tem autocomplete perfeito!
con = notion.orm.repo.databases.container.tasks.SearchPage
tasks = await con\
.set_filter(
con.checkbox("Completed", "equals", False)
)\
.call()
# Acesso type-safe aos dados
for task in tasks.results:
print(f"📋 {task.properties.title}")
print(f"Status: {task.properties.status}")
print(f"Due: {task.properties.due_date}")
```
---
## 📚 Client API
### Pages
- `get(page_id)` - Buscar página por ID
- `get_property(page_id, property_id)` - Buscar propriedade específica
- `update_properties(page_id, properties)` - Atualizar propriedades de uma página
- `create(parent, properties)` - Criar nova página
### Databases
- `get(database_id)` - Buscar informações de uma database
- `query(database_id, filter, sorts)` - Query com filtros e ordenação
- `query_property(database_id, property_id)` - Query propriedade específica
- `update(database_id, properties)` - Atualizar schema de database
### Blocks
- `get_children(block_id)` - Buscar blocos filhos de um bloco/página
---
## 📖 Schemas Disponíveis
**Responses:**
- `pages.Page`, `pages.Parent` - Estruturas de páginas
- `databases.Query` - Resultados de queries
- `users.User`, `users.Person`, `users.Bot` - Informações de usuários
- `errors.Error` - Erros da API
- `pages.properties.RichText` - Propriedade rich text
**ORM:**
- `NotionDatabase` - Classe base para schemas customizados
- `DatabasesContainer` - Container para organização de databases
- `SearchPageProperty` - Schema para propriedades de busca
- `properties.RichText` - Rich text para ORM
**DTO:**
- Data Transfer Objects para comunicação interna
---
## 🏗️ Arquitetura
```
notion/
├── auth/ # Sistema de autenticação
├── client/ # HTTP clients por endpoint
│ ├── blocks.py # Blocks API
│ ├── databases.py # Databases API
│ └── pages.py # Pages API
├── orm/
│ ├── accessors/ # Acesso tipado a propriedades
│ ├── common/ # QueryFilter, QuerySort, SetProperty
│ ├── config/ # Configurações globais do ORM
│ ├── extractors/ # Extração de propriedades da API
│ ├── mapping/ # Sistema declarativo (NotionDatabase)
│ ├── parsers/ # Parse de respostas da API
│ └── repositories/ # Camada de repositórios
│ ├── databases/ # Repositórios de databases
│ └── pages/ # Repositórios de pages
├── schemas/
│ ├── orm/ # Schemas do ORM
│ │ ├── database/ # DatabasesContainer, SearchPageProperty
│ │ └── properties/ # RichText e outros
│ └── responses/ # Schemas de respostas da API
│ ├── databases/ # Query
│ ├── errors/ # Error
│ ├── pages/ # Page, Parent, properties
│ └── users/ # User, Person, Bot
└── types/ # Type definitions e exports
├── orm/ # Types do ORM
└── responses/ # Types de responses
```
---
## 📋 Dependências
- Python >= 3.9
- httpx - Requests HTTP assíncronos
- pydantic >= 2.5 - Validação e serialização
---
## 🐛 Known Issues
- Suporte parcial a tipos de propriedades (em desenvolvimento contínuo)
- Documentação completa em progresso
- Alguns endpoints da API Notion ainda não implementados
---
## 🔮 Roadmap
- [ ] Support completo para Blocks API (criação e atualização)
- [ ] Suporte completo a todos os tipos de propriedades Notion
- [ ] Documentação completa com exemplos práticos
- [ ] Testes unitários e de integração
- [ ] Support para Comments API
- [ ] Cache system para reduzir chamadas à API
- [ ] Retry logic e rate limiting inteligente
- [ ] CLI para geração automática de schemas a partir de databases
---
## 📄 Licença
MIT License - Veja [LICENSE](LICENSE) para detalhes
---
## 🙏 Agradecimentos
Desenvolvido com ❤️ por [Eduardo Riguetto](https://riguetto.dev)
**Contribuições são bem-vindas!** Sinta-se livre para:
- 🐛 Reportar bugs via Issues
- 💡 Sugerir features
- 🔧 Abrir Pull Requests
- 📖 Melhorar a documentação
---
## 🔗 Links
- **GitHub**: [https://github.com/riguettodev/sdk-notion](https://github.com/riguettodev/sdk-notion)
- **PyPI**: [https://pypi.org/project/sdk-notion/](https://pypi.org/project/sdk-notion/)
- **Documentação**: (em breve)
+3 -16
View File
@@ -8,27 +8,14 @@ class Blocks:
def __init__(self, headers : Dict[str, str]): def __init__(self, headers : Dict[str, str]):
self._headers = headers self._headers = headers
async def get(self, block_id : str): async def get_children(self, page_id : str):
"Busca detalhes de um bloco" "Busca pelos blocos de uma página"
async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client: async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client:
response = await client.get( response = await client.get(
f'https://api.notion.com/v1/blocks/{block_id}', f'https://api.notion.com/v1/blocks/{page_id}/children',
headers = self._headers
)
return response.json()
async def get_children(self, block_id : str):
"Busca pelos blocos de um bloco ou página"
async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client:
response = await client.get(
f'https://api.notion.com/v1/blocks/{block_id}/children',
headers = self._headers headers = self._headers
) )
+8
View File
@@ -0,0 +1,8 @@
# async def main():
# instance = NotionOrm.repo.pages()
# create = await instance.CreatePage()\
# .set_parent("page_id", "2a564c9be67881a185c1c5d9133b9b1c")\
# .set_title("Name", "Teste abc")\
# .set_children("heading_1", "Teste")\
# .call()
# return create
+22
View File
@@ -0,0 +1,22 @@
import sys, os, asyncio
sys.path.append(
os.path.abspath(
os.path.join(
os.path.dirname(__file__), '..', '..', '..', '..', '..', '..', '..'
)
)
)
from src.utils.pprint import pprint
from src.integrations.notion.orm.repositories.pages.GetPage import GetPage
async def main():
instance = GetPage()
search = await instance\
.set_database(name="accounts")\
.set_pageid("0db2806f-b365-4327-919d-afbd1943f2ad")\
.select("Name")
#.call(True)
return search
test = await main()
pprint(test)
+21
View File
@@ -0,0 +1,21 @@
import sys, os, asyncio
sys.path.append(
os.path.abspath(
os.path.join(
os.path.dirname(__file__), '..', '..', '..', '..', '..', '..', '..'
)
)
)
from src.utils.pprint import pprint
from src.integrations.notion.orm.repositories.pages.GetPageProperty import GetPageProperty
async def main():
instance = GetPageProperty()
search = await instance\
.set_pageid("0db2806f-b365-4327-919d-afbd1943f2ad")\
.set_propname("Movements")\
.call()
return search
test = await main()
pprint(test)
View File
+3 -3
View File
@@ -1,9 +1,9 @@
from ....schemas.dto import BaseModelSdk from ....schemas.dto import BaseModelSdk
from pydantic import ConfigDict from pydantic import ConfigDict
from typing import List, Optional from typing import Dict, Any, List, Optional
from ...responses.properties.RichText import RichText as _RichText from ...responses.pages.properties.RichText import RichText as _RichText
class RichText(BaseModelSdk): class RichText(BaseModelSdk):
model_config = ConfigDict(title = "Notion_Orm_Common_RichText") model_config = ConfigDict(title="Notion_Orm_Common_RichText")
text: Optional[str] text: Optional[str]
detailed: List[_RichText] detailed: List[_RichText]
-26
View File
@@ -1,26 +0,0 @@
from ...dto import BaseModelSdk
from pydantic import ConfigDict
from typing import Any, Union, Literal
from datetime import datetime
from ..users.User import User as _User
from ..misc.Parent import Parent as _Parent
from .Toggle import Toggle as _Toggle
class Block(BaseModelSdk):
model_config = ConfigDict(title = "Notion_Responses_Blocks_List")
object: str
id: str
parent: _Parent
created_time: datetime
last_edited_time: datetime
created_by: _User
last_edited_by: _User
has_children: bool
archived: bool
in_trash: bool
type: Literal[
"toggle"
]
block : Union[Any,
_Toggle
]
@@ -1,12 +0,0 @@
from ...dto import BaseModelSdk
from typing import Optional, List, Any, Union
from pydantic import ConfigDict, Field
from .Block import Block as _Block
class BlockList(BaseModelSdk):
model_config = ConfigDict(title = "Notion_Responses_Blocks_BlockList")
results: List[Union[_Block, Any]]
next_cursor: Optional[str] = None
has_more: bool
type: str = "block"
block: dict
@@ -1,8 +0,0 @@
from ...dto import BaseModelSdk
from pydantic import ConfigDict
from ..properties.RichText import RichText as _RichText
class Toggle(BaseModelSdk):
model_config = ConfigDict(title = "Notion_Responses_Blocks_Toggle")
rich_text : _RichText
color : str
@@ -1,11 +0,0 @@
from .Block import Block as _Block
from .BlockList import BlockList as _BlockList
from .Toggle import Toggle as _Toggle
class Schemas:
Block = _Block
BlockList = _BlockList
Toggle = _Toggle
__all__ = ["Schemas"]
@@ -1,7 +0,0 @@
from .Parent import Parent as _Parent
class Schemas:
Parent = _Parent
__all__ = ["Schemas"]
+1 -1
View File
@@ -4,7 +4,7 @@ from typing import Optional, Any, Dict, Generic, TypeVar, Union
from datetime import datetime from datetime import datetime
from ....orm.mapping.database import NotionDatabase as _NotionDatabase from ....orm.mapping.database import NotionDatabase as _NotionDatabase
from ..users.User import User as _User from ..users.User import User as _User
from ..misc.Parent import Parent as _Parent from .Parent import Parent as _Parent
TDB = TypeVar('TDB', bound = _NotionDatabase) TDB = TypeVar('TDB', bound = _NotionDatabase)
@@ -1,10 +1,9 @@
from ...dto import BaseModelSdk from ....schemas.dto import BaseModelSdk
from pydantic import ConfigDict from pydantic import ConfigDict
from typing import Optional, Literal from typing import Optional, Literal
class Parent(BaseModelSdk): class Parent(BaseModelSdk):
model_config = ConfigDict(title="Notion_Responses_Misc_Parent") model_config = ConfigDict(title="Notion_Responses_Pages_Parent")
type: Literal ["page_id", "data_source_id", "database_id"] type: Literal ["page_id", "data_source_id", "database_id"]
data_source_id: Optional[str] = None data_source_id: Optional[str] = None
database_id: Optional[str] = None database_id: Optional[str] = None
page_id: Optional[str] = None
@@ -1,7 +1,9 @@
from .Page import Page as _Page from .Page import Page as _Page
from .Parent import Parent as _Parent
class Schemas: class Schemas:
Page = _Page Page = _Page
Parent = _Parent
__all__ = ["Schemas"] __all__ = ["Schemas"]
@@ -1,9 +1,9 @@
from ....schemas.dto import BaseModelSdk from .....schemas.dto import BaseModelSdk
from pydantic import ConfigDict from pydantic import ConfigDict
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
class RichText(BaseModelSdk): class RichText(BaseModelSdk):
model_config = ConfigDict(title="Notion_Responses_Properties_RichText") model_config = ConfigDict(title="Notion_Responses_Pages_Properties_RichText")
type: str type: str
text: 'Text' text: 'Text'
annotations: 'Annotations' annotations: 'Annotations'
@@ -1,3 +0,0 @@
from ....schemas.responses.blocks.Block import Block
from ....schemas.responses.blocks.BlockList import BlockList
from ....schemas.responses.blocks.Toggle import Toggle
-1
View File
@@ -1 +0,0 @@
from ....schemas.responses.misc.Parent import Parent
+1
View File
@@ -1 +1,2 @@
from ....schemas.responses.pages.Page import Page from ....schemas.responses.pages.Page import Page
from ....schemas.responses.pages.Parent import Parent
@@ -1 +0,0 @@
from ....schemas.responses.properties.RichText import RichText