Finaliza ajustes para iniciar Repositorio Git do SDK de Integração do Notion separado do meu projeto original
--- - Cria singleton de `client` com func `configure` para inicializar e `get_instance` para buscar instância do client; - Ajusta clients para buscar headers vindo do pai `client` e fixa versão legacy no client de databases; - Adiciona inicialização de `client` no init do projeto com api_token e api_version informados pelo usuário; - Altera `NotionConfig` para inserir `database_id` no lugar de `database_name`; - Altera sistema para receber `database_id` no lugar de `database_name`; - Altera tipo de `properties` em `schemas.responses.pages.Page` de `Union[Dict[str, Any]], TDB` para `Union[Any, TDB]` para resolver reclamações de type hint; - Adiciona param `generic_response` no init de `client` e nos clients e databases e pages para pular uso de mapping ao usar `.generic`; - Adiciona param `raw_response` para pular parser e mappings e retornar resposta original da api; - Finaliza `types` com subpastas para importações mas com init mãe vazio para evitar dependência circular e permitir uso de `notion.types.` pelo usuário; - Remove importações do projeto original não relacionadas com o SDK; - Adiciona param `timezone` na func `start_date` em `orm.common.SetProperty` que antes vinha do env, para posteriormente puxar da init da integração; - Monta `LICENSE`, `README.md` e `pyproject.toml` base simples para commit inicial do projeto permitindo build de pacote; ---
This commit is contained in:
Executable
+12
@@ -0,0 +1,12 @@
|
||||
from .database import NotionDatabase as _NotionDatabase
|
||||
from .registry import DatabaseRegistry as _DatabaseRegistry
|
||||
|
||||
class _Mapping:
|
||||
|
||||
NotionDatabase = _NotionDatabase
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.registry = _DatabaseRegistry
|
||||
|
||||
Mapping = _Mapping()
|
||||
__all__ = ["Mapping"]
|
||||
Executable
+198
@@ -0,0 +1,198 @@
|
||||
from typing import Dict, Any, Optional, Callable, ClassVar, TypeVar, Type
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
T = TypeVar('T', bound='NotionDatabase')
|
||||
|
||||
class NotionConfigMeta:
|
||||
|
||||
"Metadados de configuração da database do Notion"
|
||||
|
||||
def __init__(self, config_class):
|
||||
|
||||
self.database_id : Optional[str] = getattr(config_class, 'database_id', None)
|
||||
self.mappings : Dict[str, Any] = getattr(config_class, 'mappings', {})
|
||||
self.validators : Dict[str, Callable] = getattr(config_class, 'validators', {})
|
||||
self.computed : Dict[str, Callable] = getattr(config_class, 'computed', {})
|
||||
|
||||
transformers_config: Dict[str, Callable] = getattr(config_class, 'transformers', {})
|
||||
|
||||
# Processa mappings para separar nome e transformer
|
||||
self.field_mappings : Dict[str, str] = {}
|
||||
self.transformers : Dict[str, Callable] = {}
|
||||
|
||||
for field_name, mapping in self.mappings.items():
|
||||
|
||||
if isinstance(mapping, str):
|
||||
|
||||
# Mapping simples: "field": "Notion Name"
|
||||
self.field_mappings[field_name] = mapping
|
||||
|
||||
elif isinstance(mapping, tuple) and len(mapping) == 2:
|
||||
|
||||
# Mapping com transformer: "field": ("Notion Name", lambda x: ...)
|
||||
self.field_mappings[field_name] = mapping[0]
|
||||
self.transformers[field_name] = mapping[1]
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError(
|
||||
f"Mapping inválido para '{field_name}'. "
|
||||
f"Use: 'Notion Name' ou ('Notion Name', transformer_func)"
|
||||
)
|
||||
|
||||
self.transformers.update(transformers_config)
|
||||
|
||||
class NotionDatabaseMeta(type(BaseModel)):
|
||||
|
||||
"Metaclass que processa a classe e injeta funcionalidades do Notion"
|
||||
|
||||
_notion_config: NotionConfigMeta
|
||||
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
|
||||
# Cria a classe Pydantic
|
||||
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
||||
|
||||
# Se tem NotionConfig, processa
|
||||
if hasattr(cls, 'NotionConfig'):
|
||||
|
||||
config = NotionConfigMeta(cls.NotionConfig)
|
||||
|
||||
# Injeta o config processado
|
||||
cls._notion_config = config # type: ignore
|
||||
|
||||
if not config.database_id:
|
||||
raise AttributeError("Database ID is missing")
|
||||
|
||||
# Injeta validators customizados do NotionConfig
|
||||
if config.validators:
|
||||
|
||||
for field_name, validator_func in config.validators.items():
|
||||
|
||||
# Cria um validator Pydantic dinâmico
|
||||
validator_name = f'validate_{field_name}_notion'
|
||||
|
||||
def make_validator(func):
|
||||
def validator(cls, v):
|
||||
return func(v)
|
||||
return field_validator(field_name)(validator)
|
||||
|
||||
setattr(cls, validator_name, make_validator(validator_func))
|
||||
|
||||
return cls
|
||||
|
||||
class NotionDatabase(BaseModel, metaclass = NotionDatabaseMeta):
|
||||
|
||||
"""
|
||||
Classe base para schemas de databases do Notion.
|
||||
|
||||
Uso:
|
||||
----
|
||||
class AccountsDB(NotionDatabase):
|
||||
name: str
|
||||
credit: float
|
||||
type: List[str]
|
||||
|
||||
class NotionConfig:
|
||||
mappings = {
|
||||
"name": "Name",
|
||||
"credit": "Credit",
|
||||
"type": ("Type", lambda x: [s["name"] for s in x] if x else [])
|
||||
}
|
||||
"""
|
||||
|
||||
_notion_config: ClassVar[NotionConfigMeta]
|
||||
|
||||
@classmethod
|
||||
def from_notion_page(
|
||||
cls : Type[T],
|
||||
page_properties : Dict[str, Any],
|
||||
page_parser : Optional[Callable] = None
|
||||
) -> Optional[T]:
|
||||
|
||||
"""
|
||||
Cria uma instância do schema a partir de propriedades parseadas do Notion.
|
||||
|
||||
Args:
|
||||
page_properties: Dict com propriedades já parseadas pelo PropertyExtractor
|
||||
page_parser: Função de parsing (se as propriedades ainda não foram parseadas)
|
||||
|
||||
Returns:
|
||||
Instância do schema ou None se parsing falhar
|
||||
"""
|
||||
|
||||
if not hasattr(cls, '_notion_config'):
|
||||
raise AttributeError(
|
||||
f"{cls.__name__} não tem NotionConfig definido. "
|
||||
f"Adicione uma subclasse NotionConfig com os mappings."
|
||||
)
|
||||
|
||||
config: NotionConfigMeta = cls._notion_config
|
||||
|
||||
# Se recebeu a página raw, faz o parsing
|
||||
if page_parser and 'properties' in page_properties:
|
||||
page_properties = page_parser(page_properties)
|
||||
|
||||
if not page_properties:
|
||||
return None
|
||||
|
||||
result = {}
|
||||
|
||||
# Processa campos mapeados
|
||||
for field_name, notion_name in config.field_mappings.items():
|
||||
|
||||
raw_value = page_properties.get(notion_name)
|
||||
|
||||
# Aplica transformer se existir
|
||||
if field_name in config.transformers:
|
||||
value = config.transformers[field_name](raw_value)
|
||||
else:
|
||||
value = raw_value
|
||||
|
||||
result[field_name] = value
|
||||
|
||||
# Cria instância temporária para computed fields
|
||||
instance = cls(**result)
|
||||
|
||||
# Processa computed fields
|
||||
for field_name, compute_func in config.computed.items():
|
||||
|
||||
computed_value = compute_func(instance)
|
||||
result[field_name] = computed_value
|
||||
|
||||
# Retorna instância final com computed fields
|
||||
return cls(**result)
|
||||
|
||||
@classmethod
|
||||
def get_database_id(cls) -> str:
|
||||
|
||||
"Retorna o nome da database no Notion"
|
||||
|
||||
if hasattr(cls, '_notion_config'):
|
||||
return cls._notion_config.database_id or cls.__name__
|
||||
|
||||
return cls.__name__
|
||||
|
||||
@classmethod
|
||||
def get_notion_field_name(cls,
|
||||
python_field : str
|
||||
) -> Optional[str]:
|
||||
|
||||
"Retorna o nome do campo no Notion a partir do nome Python"
|
||||
|
||||
if hasattr(cls, '_notion_config'):
|
||||
return cls._notion_config.field_mappings.get(python_field)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_all_mappings(cls) -> Dict[str, str]:
|
||||
|
||||
"Retorna todos os mappings field_python -> field_notion"
|
||||
|
||||
if hasattr(cls, '_notion_config'):
|
||||
return cls._notion_config.field_mappings.copy()
|
||||
|
||||
return {}
|
||||
|
||||
__all__ = ["NotionDatabase"]
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
from typing import Dict, Any, Optional, Callable, Type
|
||||
from .database import NotionDatabase as _NotionDatabase
|
||||
|
||||
class DatabaseRegistry:
|
||||
|
||||
"""
|
||||
Registry global de databases do Notion.
|
||||
Permite acessar schemas e criar parsers dinamicamente.
|
||||
"""
|
||||
|
||||
_databases: Dict[str, Type[_NotionDatabase]] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls,
|
||||
database_class : Type[_NotionDatabase]
|
||||
) -> None:
|
||||
|
||||
"Registra uma database no registry"
|
||||
|
||||
db_name = database_class.get_database_id()
|
||||
cls._databases[db_name] = database_class
|
||||
|
||||
@classmethod
|
||||
def get(cls,
|
||||
database_id : str
|
||||
) -> Optional[Type[_NotionDatabase]]:
|
||||
|
||||
"Retorna a classe de schema de uma database"
|
||||
|
||||
return cls._databases.get(database_id)
|
||||
|
||||
@classmethod
|
||||
def get_parser(cls,
|
||||
database_id : str,
|
||||
page_parser : Callable
|
||||
) -> Callable:
|
||||
|
||||
"""
|
||||
Retorna uma função parser para a database.
|
||||
|
||||
Args:
|
||||
database_id: ID da Database
|
||||
page_parser: Função que parseia páginas (ex: PageProperties.parse)
|
||||
|
||||
Returns:
|
||||
Função parser ou None se database não encontrada
|
||||
"""
|
||||
|
||||
db_class = cls.get(database_id)
|
||||
if not db_class:
|
||||
raise ValueError(f"Database de ID '{database_id}' não está registrada")
|
||||
|
||||
def parser(page : Dict[str, Any]) -> Optional[_NotionDatabase]:
|
||||
return db_class.from_notion_page(page, page_parser)
|
||||
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
def list_databases(cls) -> list[str]:
|
||||
|
||||
"Lista todas as databases registradas"
|
||||
|
||||
return list(cls._databases.keys())
|
||||
|
||||
@classmethod
|
||||
def auto_register(cls,
|
||||
*database_classes : Type[_NotionDatabase]
|
||||
) -> None:
|
||||
|
||||
"Registra múltiplas databases de uma vez"
|
||||
|
||||
for db_class in database_classes:
|
||||
cls.register(db_class)
|
||||
|
||||
@classmethod
|
||||
def generate_literal_type(cls) -> str:
|
||||
|
||||
"Gera o código do Literal com todas as databases registradas"
|
||||
|
||||
db_names = list(cls._databases.keys())
|
||||
literal_str = ", ".join([f'"{name}"' for name in db_names])
|
||||
|
||||
return f"Literal[{literal_str}]"
|
||||
|
||||
@classmethod
|
||||
def get_database_class_by_id(cls,
|
||||
db_id : str
|
||||
) -> Optional[Type[_NotionDatabase]]:
|
||||
|
||||
"Retorna a classe schema baseado no nome da database"
|
||||
|
||||
return cls._databases.get(db_id)
|
||||
Reference in New Issue
Block a user