notionary 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +9 -0
- notionary/core/__init__.py +0 -0
- notionary/core/converters/__init__.py +50 -0
- notionary/core/converters/elements/__init__.py +0 -0
- notionary/core/converters/elements/bookmark_element.py +224 -0
- notionary/core/converters/elements/callout_element.py +179 -0
- notionary/core/converters/elements/code_block_element.py +153 -0
- notionary/core/converters/elements/column_element.py +294 -0
- notionary/core/converters/elements/divider_element.py +73 -0
- notionary/core/converters/elements/heading_element.py +84 -0
- notionary/core/converters/elements/image_element.py +130 -0
- notionary/core/converters/elements/list_element.py +130 -0
- notionary/core/converters/elements/notion_block_element.py +51 -0
- notionary/core/converters/elements/paragraph_element.py +73 -0
- notionary/core/converters/elements/qoute_element.py +242 -0
- notionary/core/converters/elements/table_element.py +306 -0
- notionary/core/converters/elements/text_inline_formatter.py +294 -0
- notionary/core/converters/elements/todo_lists.py +114 -0
- notionary/core/converters/elements/toggle_element.py +205 -0
- notionary/core/converters/elements/video_element.py +159 -0
- notionary/core/converters/markdown_to_notion_converter.py +482 -0
- notionary/core/converters/notion_to_markdown_converter.py +45 -0
- notionary/core/converters/registry/__init__.py +0 -0
- notionary/core/converters/registry/block_element_registry.py +234 -0
- notionary/core/converters/registry/block_element_registry_builder.py +280 -0
- notionary/core/database/database_info_service.py +43 -0
- notionary/core/database/database_query_service.py +73 -0
- notionary/core/database/database_schema_service.py +57 -0
- notionary/core/database/models/page_result.py +10 -0
- notionary/core/database/notion_database_manager.py +332 -0
- notionary/core/database/notion_database_manager_factory.py +233 -0
- notionary/core/database/notion_database_schema.py +415 -0
- notionary/core/database/notion_database_writer.py +390 -0
- notionary/core/database/page_service.py +161 -0
- notionary/core/notion_client.py +134 -0
- notionary/core/page/meta_data/metadata_editor.py +37 -0
- notionary/core/page/notion_page_manager.py +110 -0
- notionary/core/page/page_content_manager.py +85 -0
- notionary/core/page/property_formatter.py +97 -0
- notionary/exceptions/database_exceptions.py +76 -0
- notionary/exceptions/page_creation_exception.py +9 -0
- notionary/util/logging_mixin.py +47 -0
- notionary/util/singleton_decorator.py +20 -0
- notionary/util/uuid_utils.py +24 -0
- {notionary-0.1.1.dist-info → notionary-0.1.3.dist-info}/METADATA +1 -1
- notionary-0.1.3.dist-info/RECORD +49 -0
- notionary-0.1.3.dist-info/top_level.txt +1 -0
- notionary-0.1.1.dist-info/RECORD +0 -5
- notionary-0.1.1.dist-info/top_level.txt +0 -1
- {notionary-0.1.1.dist-info → notionary-0.1.3.dist-info}/WHEEL +0 -0
- {notionary-0.1.1.dist-info → notionary-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
from typing import Any, Dict, Optional
|
2
|
+
from notionary.core.notion_client import NotionClient
|
3
|
+
from notionary.util.logging_mixin import LoggingMixin
|
4
|
+
|
5
|
+
|
6
|
+
class MetadataEditor(LoggingMixin):
|
7
|
+
def __init__(self, page_id: str, client: NotionClient):
|
8
|
+
self.page_id = page_id
|
9
|
+
self._client = client
|
10
|
+
|
11
|
+
async def set_title(self, title: str) -> Optional[Dict[str, Any]]:
|
12
|
+
return await self._client.patch(
|
13
|
+
f"pages/{self.page_id}",
|
14
|
+
{
|
15
|
+
"properties": {
|
16
|
+
"title": {"title": [{"type": "text", "text": {"content": title}}]}
|
17
|
+
}
|
18
|
+
},
|
19
|
+
)
|
20
|
+
|
21
|
+
async def set_icon(
|
22
|
+
self, emoji: Optional[str] = None, external_url: Optional[str] = None
|
23
|
+
) -> Optional[Dict[str, Any]]:
|
24
|
+
if emoji:
|
25
|
+
icon = {"type": "emoji", "emoji": emoji}
|
26
|
+
elif external_url:
|
27
|
+
icon = {"type": "external", "external": {"url": external_url}}
|
28
|
+
else:
|
29
|
+
return None
|
30
|
+
|
31
|
+
return await self._client.patch(f"pages/{self.page_id}", {"icon": icon})
|
32
|
+
|
33
|
+
async def set_cover(self, external_url: str) -> Optional[Dict[str, Any]]:
|
34
|
+
return await self._client.patch(
|
35
|
+
f"pages/{self.page_id}",
|
36
|
+
{"cover": {"type": "external", "external": {"url": external_url}}},
|
37
|
+
)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
|
+
from notionary.core.converters.registry.block_element_registry import (
|
3
|
+
BlockElementRegistry,
|
4
|
+
)
|
5
|
+
from notionary.core.converters.registry.block_element_registry_builder import (
|
6
|
+
BlockElementRegistryBuilder,
|
7
|
+
)
|
8
|
+
from notionary.core.notion_client import NotionClient
|
9
|
+
from notionary.core.page.page_content_manager import PageContentManager
|
10
|
+
from notionary.util.logging_mixin import LoggingMixin
|
11
|
+
from notionary.core.page.meta_data.metadata_editor import MetadataEditor
|
12
|
+
from notionary.util.uuid_utils import extract_uuid, format_uuid, is_valid_uuid
|
13
|
+
|
14
|
+
|
15
|
+
class NotionPageManager(LoggingMixin):
|
16
|
+
"""
|
17
|
+
High-Level Fassade zur Verwaltung von Inhalten und Metadaten einer Notion-Seite.
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
page_id: Optional[str] = None,
|
23
|
+
title: Optional[str] = None,
|
24
|
+
url: Optional[str] = None,
|
25
|
+
token: Optional[str] = None,
|
26
|
+
):
|
27
|
+
if not page_id and not url:
|
28
|
+
raise ValueError("Either page_id or url must be provided")
|
29
|
+
|
30
|
+
if not page_id and url:
|
31
|
+
page_id = extract_uuid(url)
|
32
|
+
if not page_id:
|
33
|
+
raise ValueError(f"Could not extract a valid UUID from the URL: {url}")
|
34
|
+
|
35
|
+
page_id = format_uuid(page_id)
|
36
|
+
if not page_id or not is_valid_uuid(page_id):
|
37
|
+
raise ValueError(f"Invalid UUID format: {page_id}")
|
38
|
+
|
39
|
+
self._page_id = page_id
|
40
|
+
self.url = url
|
41
|
+
self._title = title
|
42
|
+
|
43
|
+
self._client = NotionClient(token=token)
|
44
|
+
|
45
|
+
self._block_element_registry = (
|
46
|
+
BlockElementRegistryBuilder.create_standard_registry()
|
47
|
+
)
|
48
|
+
|
49
|
+
self._page_content_manager = PageContentManager(
|
50
|
+
page_id=page_id,
|
51
|
+
client=self._client,
|
52
|
+
block_registry=self._block_element_registry,
|
53
|
+
)
|
54
|
+
self._metadata = MetadataEditor(page_id, self._client)
|
55
|
+
|
56
|
+
@property
|
57
|
+
def page_id(self) -> Optional[str]:
|
58
|
+
"""Get the title of the page."""
|
59
|
+
return self._page_id
|
60
|
+
|
61
|
+
@property
|
62
|
+
def title(self) -> Optional[str]:
|
63
|
+
return self._title
|
64
|
+
|
65
|
+
@property
|
66
|
+
def block_registry(self) -> BlockElementRegistry:
|
67
|
+
return self._block_element_registry
|
68
|
+
|
69
|
+
@block_registry.setter
|
70
|
+
def block_registry(self, block_registry: BlockElementRegistry) -> None:
|
71
|
+
"""Set the block element registry for the page content manager."""
|
72
|
+
|
73
|
+
self._block_element_registry = block_registry
|
74
|
+
|
75
|
+
self._page_content_manager = PageContentManager(
|
76
|
+
page_id=self._page_id, client=self._client, block_registry=block_registry
|
77
|
+
)
|
78
|
+
|
79
|
+
async def append_markdown(self, markdown: str) -> str:
|
80
|
+
return await self._page_content_manager.append_markdown(markdown)
|
81
|
+
|
82
|
+
async def clear(self) -> str:
|
83
|
+
return await self._page_content_manager.clear()
|
84
|
+
|
85
|
+
async def replace_content(self, markdown: str) -> str:
|
86
|
+
await self._page_content_manager.clear()
|
87
|
+
return await self._page_content_manager.append_markdown(markdown)
|
88
|
+
|
89
|
+
async def get_blocks(self) -> List[Dict[str, Any]]:
|
90
|
+
return await self._page_content_manager.get_blocks()
|
91
|
+
|
92
|
+
async def get_block_children(self, block_id: str) -> List[Dict[str, Any]]:
|
93
|
+
return await self._page_content_manager.get_block_children(block_id)
|
94
|
+
|
95
|
+
async def get_page_blocks_with_children(self) -> List[Dict[str, Any]]:
|
96
|
+
return await self._page_content_manager.get_page_blocks_with_children()
|
97
|
+
|
98
|
+
async def get_text(self) -> str:
|
99
|
+
return await self._page_content_manager.get_text()
|
100
|
+
|
101
|
+
async def set_title(self, title: str) -> Optional[Dict[str, Any]]:
|
102
|
+
return await self._metadata.set_title(title)
|
103
|
+
|
104
|
+
async def set_page_icon(
|
105
|
+
self, emoji: Optional[str] = None, external_url: Optional[str] = None
|
106
|
+
) -> Optional[Dict[str, Any]]:
|
107
|
+
return await self._metadata.set_icon(emoji, external_url)
|
108
|
+
|
109
|
+
async def set_page_cover(self, external_url: str) -> Optional[Dict[str, Any]]:
|
110
|
+
return await self._metadata.set_cover(external_url)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
|
+
|
3
|
+
from notionary.core.converters.markdown_to_notion_converter import (
|
4
|
+
MarkdownToNotionConverter,
|
5
|
+
)
|
6
|
+
from notionary.core.converters.notion_to_markdown_converter import (
|
7
|
+
NotionToMarkdownConverter,
|
8
|
+
)
|
9
|
+
from notionary.core.converters.registry.block_element_registry import (
|
10
|
+
BlockElementRegistry,
|
11
|
+
)
|
12
|
+
from notionary.core.notion_client import NotionClient
|
13
|
+
from notionary.util.logging_mixin import LoggingMixin
|
14
|
+
|
15
|
+
|
16
|
+
class PageContentManager(LoggingMixin):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
page_id: str,
|
20
|
+
client: NotionClient,
|
21
|
+
block_registry: Optional[BlockElementRegistry] = None,
|
22
|
+
):
|
23
|
+
self.page_id = page_id
|
24
|
+
self._client = client
|
25
|
+
self._markdown_to_notion_converter = MarkdownToNotionConverter(
|
26
|
+
block_registry=block_registry
|
27
|
+
)
|
28
|
+
self._notion_to_markdown_converter = NotionToMarkdownConverter(
|
29
|
+
block_registry=block_registry
|
30
|
+
)
|
31
|
+
|
32
|
+
async def append_markdown(self, markdown_text: str) -> str:
|
33
|
+
blocks = self._markdown_to_notion_converter.convert(markdown_text)
|
34
|
+
result = await self._client.patch(
|
35
|
+
f"blocks/{self.page_id}/children", {"children": blocks}
|
36
|
+
)
|
37
|
+
return (
|
38
|
+
"Successfully added text to the page." if result else "Failed to add text."
|
39
|
+
)
|
40
|
+
|
41
|
+
async def clear(self) -> str:
|
42
|
+
blocks = await self._client.get(f"blocks/{self.page_id}/children")
|
43
|
+
if not blocks:
|
44
|
+
return "No content to delete."
|
45
|
+
|
46
|
+
results = blocks.get("results", [])
|
47
|
+
if not results:
|
48
|
+
return "No content to delete."
|
49
|
+
|
50
|
+
deleted = 0
|
51
|
+
for b in results:
|
52
|
+
if await self._client.delete(f"blocks/{b['id']}"):
|
53
|
+
deleted += 1
|
54
|
+
|
55
|
+
return f"Deleted {deleted}/{len(results)} blocks."
|
56
|
+
|
57
|
+
# Methods from PageContentReader
|
58
|
+
async def get_blocks(self) -> List[Dict[str, Any]]:
|
59
|
+
result = await self._client.get(f"blocks/{self.page_id}/children")
|
60
|
+
if not result:
|
61
|
+
self.logger.error("Error retrieving page content: %s", result.error)
|
62
|
+
return []
|
63
|
+
return result.data.get("results", [])
|
64
|
+
|
65
|
+
async def get_block_children(self, block_id: str) -> List[Dict[str, Any]]:
|
66
|
+
result = await self._client.get(f"blocks/{block_id}/children")
|
67
|
+
if not result:
|
68
|
+
self.logger.error("Error retrieving block children: %s", result.error)
|
69
|
+
return []
|
70
|
+
return result.data.get("results", [])
|
71
|
+
|
72
|
+
async def get_page_blocks_with_children(self) -> List[Dict[str, Any]]:
|
73
|
+
blocks = await self.get_blocks()
|
74
|
+
for block in blocks:
|
75
|
+
if block.get("has_children"):
|
76
|
+
block_id = block.get("id")
|
77
|
+
if block_id:
|
78
|
+
children = await self.get_block_children(block_id)
|
79
|
+
if children:
|
80
|
+
block["children"] = children
|
81
|
+
return blocks
|
82
|
+
|
83
|
+
async def get_text(self) -> str:
|
84
|
+
blocks = await self.get_page_blocks_with_children()
|
85
|
+
return self._notion_to_markdown_converter.convert(blocks)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
from typing import Any, Dict, Optional
|
2
|
+
|
3
|
+
from notionary.util.logging_mixin import LoggingMixin
|
4
|
+
|
5
|
+
|
6
|
+
class NotionPropertyFormatter(LoggingMixin):
|
7
|
+
"""Klasse zur Formatierung von Notion-Eigenschaften nach Typ."""
|
8
|
+
|
9
|
+
def __init__(self):
|
10
|
+
# Mapping von Typen zu Formatierungsmethoden
|
11
|
+
self._formatters = {
|
12
|
+
"title": self.format_title,
|
13
|
+
"rich_text": self.format_rich_text,
|
14
|
+
"url": self.format_url,
|
15
|
+
"email": self.format_email,
|
16
|
+
"phone_number": self.format_phone_number,
|
17
|
+
"number": self.format_number,
|
18
|
+
"checkbox": self.format_checkbox,
|
19
|
+
"select": self.format_select,
|
20
|
+
"multi_select": self.format_multi_select,
|
21
|
+
"date": self.format_date,
|
22
|
+
"status": self.format_status,
|
23
|
+
"relation": self.format_relation,
|
24
|
+
}
|
25
|
+
|
26
|
+
def format_title(self, value: Any) -> Dict[str, Any]:
|
27
|
+
"""Formatiert einen Titel-Wert."""
|
28
|
+
return {"title": [{"type": "text", "text": {"content": str(value)}}]}
|
29
|
+
|
30
|
+
def format_rich_text(self, value: Any) -> Dict[str, Any]:
|
31
|
+
"""Formatiert einen Rich-Text-Wert."""
|
32
|
+
return {"rich_text": [{"type": "text", "text": {"content": str(value)}}]}
|
33
|
+
|
34
|
+
def format_url(self, value: str) -> Dict[str, Any]:
|
35
|
+
"""Formatiert eine URL."""
|
36
|
+
return {"url": value}
|
37
|
+
|
38
|
+
def format_email(self, value: str) -> Dict[str, Any]:
|
39
|
+
"""Formatiert eine E-Mail-Adresse."""
|
40
|
+
return {"email": value}
|
41
|
+
|
42
|
+
def format_phone_number(self, value: str) -> Dict[str, Any]:
|
43
|
+
"""Formatiert eine Telefonnummer."""
|
44
|
+
return {"phone_number": value}
|
45
|
+
|
46
|
+
def format_number(self, value: Any) -> Dict[str, Any]:
|
47
|
+
"""Formatiert eine Zahl."""
|
48
|
+
return {"number": float(value)}
|
49
|
+
|
50
|
+
def format_checkbox(self, value: Any) -> Dict[str, Any]:
|
51
|
+
"""Formatiert einen Checkbox-Wert."""
|
52
|
+
return {"checkbox": bool(value)}
|
53
|
+
|
54
|
+
def format_select(self, value: str) -> Dict[str, Any]:
|
55
|
+
"""Formatiert einen Select-Wert."""
|
56
|
+
return {"select": {"name": str(value)}}
|
57
|
+
|
58
|
+
def format_multi_select(self, value: Any) -> Dict[str, Any]:
|
59
|
+
"""Formatiert einen Multi-Select-Wert."""
|
60
|
+
if isinstance(value, list):
|
61
|
+
return {"multi_select": [{"name": item} for item in value]}
|
62
|
+
return {"multi_select": [{"name": str(value)}]}
|
63
|
+
|
64
|
+
def format_date(self, value: Any) -> Dict[str, Any]:
|
65
|
+
"""Formatiert ein Datum."""
|
66
|
+
if isinstance(value, dict) and "start" in value:
|
67
|
+
return {"date": value}
|
68
|
+
return {"date": {"start": str(value)}}
|
69
|
+
|
70
|
+
def format_status(self, value: str) -> Dict[str, Any]:
|
71
|
+
"""Formatiert einen Status-Wert."""
|
72
|
+
return {"status": {"name": str(value)}}
|
73
|
+
|
74
|
+
def format_relation(self, value: Any) -> Dict[str, Any]:
|
75
|
+
"""Formatiert einen Relations-Wert."""
|
76
|
+
if isinstance(value, list):
|
77
|
+
return {"relation": [{"id": item} for item in value]}
|
78
|
+
return {"relation": [{"id": str(value)}]}
|
79
|
+
|
80
|
+
def format_value(self, property_type: str, value: Any) -> Optional[Dict[str, Any]]:
|
81
|
+
"""
|
82
|
+
Formatiert einen Wert entsprechend des angegebenen Eigenschaftstyps.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
property_type: Notion-Eigenschaftstyp (z.B. "title", "rich_text", "status")
|
86
|
+
value: Der zu formatierende Wert
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
Formatierter Wert als Dictionary oder None bei unbekanntem Typ
|
90
|
+
"""
|
91
|
+
formatter = self._formatters.get(property_type)
|
92
|
+
if not formatter:
|
93
|
+
if self.logger:
|
94
|
+
self.logger.warning("Unbekannter Eigenschaftstyp: %s", property_type)
|
95
|
+
return None
|
96
|
+
|
97
|
+
return formatter(value)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
|
4
|
+
class NotionDatabaseException(Exception):
|
5
|
+
"""Base exception for all Notion database operations."""
|
6
|
+
|
7
|
+
pass
|
8
|
+
|
9
|
+
|
10
|
+
class DatabaseNotFoundException(NotionDatabaseException):
|
11
|
+
"""Exception raised when a database is not found."""
|
12
|
+
|
13
|
+
def __init__(self, identifier: str, message: str = None):
|
14
|
+
self.identifier = identifier
|
15
|
+
self.message = message or f"Database not found: {identifier}"
|
16
|
+
super().__init__(self.message)
|
17
|
+
|
18
|
+
|
19
|
+
class DatabaseInitializationError(NotionDatabaseException):
|
20
|
+
"""Exception raised when a database manager fails to initialize."""
|
21
|
+
|
22
|
+
def __init__(self, database_id: str, message: str = None):
|
23
|
+
self.database_id = database_id
|
24
|
+
self.message = (
|
25
|
+
message or f"Failed to initialize database manager for ID: {database_id}"
|
26
|
+
)
|
27
|
+
super().__init__(self.message)
|
28
|
+
|
29
|
+
|
30
|
+
class DatabaseConnectionError(NotionDatabaseException):
|
31
|
+
"""Exception raised when there's an error connecting to Notion API."""
|
32
|
+
|
33
|
+
def __init__(self, message: str = None):
|
34
|
+
self.message = message or "Error connecting to Notion API"
|
35
|
+
super().__init__(self.message)
|
36
|
+
|
37
|
+
|
38
|
+
class DatabaseParsingError(NotionDatabaseException):
|
39
|
+
"""Exception raised when there's an error parsing database data."""
|
40
|
+
|
41
|
+
def __init__(self, message: str = None):
|
42
|
+
self.message = message or "Error parsing database data"
|
43
|
+
super().__init__(self.message)
|
44
|
+
|
45
|
+
|
46
|
+
class PageNotFoundException(NotionDatabaseException):
|
47
|
+
"""Raised when a page is not found or cannot be accessed."""
|
48
|
+
|
49
|
+
def __init__(self, page_id: str, message: Optional[str] = None):
|
50
|
+
self.page_id = page_id
|
51
|
+
self.message = message or f"Page not found: {page_id}"
|
52
|
+
super().__init__(self.message)
|
53
|
+
|
54
|
+
|
55
|
+
class PageOperationError(NotionDatabaseException):
|
56
|
+
"""Raised when an operation on a page fails."""
|
57
|
+
|
58
|
+
def __init__(self, page_id: str, operation: str, message: Optional[str] = None):
|
59
|
+
self.page_id = page_id
|
60
|
+
self.operation = operation
|
61
|
+
self.message = message or f"Failed to {operation} page {page_id}"
|
62
|
+
super().__init__(self.message)
|
63
|
+
|
64
|
+
|
65
|
+
class PropertyError(NotionDatabaseException):
|
66
|
+
"""Raised when there's an error with database properties."""
|
67
|
+
|
68
|
+
def __init__(
|
69
|
+
self, property_name: Optional[str] = None, message: Optional[str] = None
|
70
|
+
):
|
71
|
+
self.property_name = property_name
|
72
|
+
self.message = (
|
73
|
+
message
|
74
|
+
or f"Error with property{' ' + property_name if property_name else ''}"
|
75
|
+
)
|
76
|
+
super().__init__(self.message)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import logging
|
2
|
+
import inspect
|
3
|
+
|
4
|
+
|
5
|
+
def setup_logging():
|
6
|
+
logging.basicConfig(
|
7
|
+
level=logging.INFO,
|
8
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
class LoggingMixin:
|
13
|
+
@property
|
14
|
+
def logger(self):
|
15
|
+
if not hasattr(self, "_logger"):
|
16
|
+
self._logger = logging.getLogger(self.__class__.__name__)
|
17
|
+
return self._logger
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
def class_logger(cls):
|
21
|
+
"""Class logger - für Klassenmethoden"""
|
22
|
+
return logging.getLogger(cls.__name__)
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def static_logger():
|
26
|
+
stack = inspect.stack()
|
27
|
+
for frame_info in stack[1:]:
|
28
|
+
class_name = LoggingMixin._get_class_name_from_frame(frame_info.frame)
|
29
|
+
if class_name:
|
30
|
+
return logging.getLogger(class_name)
|
31
|
+
return logging.getLogger("UnknownStaticContext")
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def _get_class_name_from_frame(frame):
|
35
|
+
local_vars = frame.f_locals
|
36
|
+
if "self" in local_vars:
|
37
|
+
return local_vars["self"].__class__.__name__
|
38
|
+
|
39
|
+
if "cls" in local_vars:
|
40
|
+
return local_vars["cls"].__name__
|
41
|
+
|
42
|
+
if "__qualname__" in frame.f_code.co_names:
|
43
|
+
qualname = frame.f_code.co_qualname
|
44
|
+
if "." in qualname:
|
45
|
+
return qualname.split(".")[0]
|
46
|
+
|
47
|
+
return None
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from typing import TypeVar
|
2
|
+
from functools import wraps
|
3
|
+
|
4
|
+
T = TypeVar("T")
|
5
|
+
|
6
|
+
|
7
|
+
def singleton(cls: T) -> T:
|
8
|
+
"""
|
9
|
+
Decorator zur Implementierung des Singleton-Musters.
|
10
|
+
Stellt sicher, dass nur eine Instanz der Klasse existiert.
|
11
|
+
"""
|
12
|
+
instances = {}
|
13
|
+
|
14
|
+
@wraps(cls)
|
15
|
+
def get_instance(*args, **kwargs):
|
16
|
+
if cls not in instances:
|
17
|
+
instances[cls] = cls(*args, **kwargs)
|
18
|
+
return instances[cls]
|
19
|
+
|
20
|
+
return get_instance
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
UUID_PATTERN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
5
|
+
UUID_RAW_PATTERN = r"([a-f0-9]{32})"
|
6
|
+
|
7
|
+
|
8
|
+
def extract_uuid(source: str) -> Optional[str]:
|
9
|
+
match = re.search(UUID_RAW_PATTERN, source.lower())
|
10
|
+
if not match:
|
11
|
+
return None
|
12
|
+
|
13
|
+
uuid_raw = match.group(1)
|
14
|
+
return f"{uuid_raw[0:8]}-{uuid_raw[8:12]}-{uuid_raw[12:16]}-{uuid_raw[16:20]}-{uuid_raw[20:32]}"
|
15
|
+
|
16
|
+
|
17
|
+
def is_valid_uuid(uuid: str) -> bool:
|
18
|
+
return bool(re.match(UUID_PATTERN, uuid.lower()))
|
19
|
+
|
20
|
+
|
21
|
+
def format_uuid(value: str) -> Optional[str]:
|
22
|
+
if is_valid_uuid(value):
|
23
|
+
return value
|
24
|
+
return extract_uuid(value)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
notionary/__init__.py,sha256=5T16clJlSHFsopcPUnkGrEo8spNDUQ0i-O00UEF7nR8,320
|
2
|
+
notionary/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
notionary/core/notion_client.py,sha256=AKxQcXkQ9OsvFBBJEvMYfXipos8ttOMf_LtT09aR41M,4646
|
4
|
+
notionary/core/converters/__init__.py,sha256=GOUehJbe4BKHtec1MqL1YGu3AX8zFtkwSZfhYkY5-P0,1798
|
5
|
+
notionary/core/converters/markdown_to_notion_converter.py,sha256=PGtg4v5lUvkXXl1Y8E6a3Mf8hEfxfhBrslPs_H_Lq_E,16564
|
6
|
+
notionary/core/converters/notion_to_markdown_converter.py,sha256=c8GyWX8-UrNfRDk7OOBKbSEb5qOwljUCwI6g5risO2c,1287
|
7
|
+
notionary/core/converters/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
notionary/core/converters/elements/bookmark_element.py,sha256=bpHobkGnyBGDAJK5vY9R3Ntl4GiRSF-EyyA31aq2O3E,8593
|
9
|
+
notionary/core/converters/elements/callout_element.py,sha256=rkDoXikjIl-zU3GLawSXgRunBJGLnEvin9zIlCgW4TY,5964
|
10
|
+
notionary/core/converters/elements/code_block_element.py,sha256=G1iGMsGSK5KPSk-tA8TsPs9XNU9ydjYfOVnjIvdZG74,5189
|
11
|
+
notionary/core/converters/elements/column_element.py,sha256=ZwQsLBEownVJnzyv-GfNjvzJhAfKz9ncggqZUmZHF5A,10722
|
12
|
+
notionary/core/converters/elements/divider_element.py,sha256=Ul0wXHY96qWL72iAvttRQMOoAGuASgwFwPraGnpUkX0,2792
|
13
|
+
notionary/core/converters/elements/heading_element.py,sha256=BCBcpEO_UX92nzCclVHAjlOLFJ5zu9wDlAGbphesaOQ,2788
|
14
|
+
notionary/core/converters/elements/image_element.py,sha256=uU3bY26LvJwD_CAXN11tqYt5Ed84gjUeHWnJmxvH07Y,4861
|
15
|
+
notionary/core/converters/elements/list_element.py,sha256=lM9nVGVG3VtmjMkqxrBj71wiJITuRypwxORu4zghqAM,4878
|
16
|
+
notionary/core/converters/elements/notion_block_element.py,sha256=lLRBDXhBeRaRzkbvdpYpr-U9nbkd62oVtqdSe-svT4c,1746
|
17
|
+
notionary/core/converters/elements/paragraph_element.py,sha256=5YGZjcgs0QCvQZ2kF8WCtImbyq5BeMywmME1m3-NDdo,2766
|
18
|
+
notionary/core/converters/elements/qoute_element.py,sha256=CZD1Fe_Lhxsv4mdAM_dr4tlWDjmZCU_5aSpLOFrxDcc,9057
|
19
|
+
notionary/core/converters/elements/table_element.py,sha256=3V9bnNBdsslXZivQ0vpF4_rzrdyI3SWt3aYR814mOzA,11254
|
20
|
+
notionary/core/converters/elements/text_inline_formatter.py,sha256=FE_Sq2cozpu5RVtMbnPq21gD06UjH3LMRYr3s16JKYo,10606
|
21
|
+
notionary/core/converters/elements/todo_lists.py,sha256=wgY6YejURBQ5ESdVLZVIy9QKchS-x8odrmS8X4cC5Kc,4265
|
22
|
+
notionary/core/converters/elements/toggle_element.py,sha256=Xv4MuuOyoamvT3IEJX4mynvLEycgtZ9LWt6Nm764KXE,6980
|
23
|
+
notionary/core/converters/elements/video_element.py,sha256=xrBLY3e_SgKNamItZkfPNMbNEh37Ftp4jWIV6nwV-ds,6047
|
24
|
+
notionary/core/converters/registry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
notionary/core/converters/registry/block_element_registry.py,sha256=0dpRFMM67UVmXRMP4-Ubc_uCZCVVmRKgxPpueCeknjw,8619
|
26
|
+
notionary/core/converters/registry/block_element_registry_builder.py,sha256=yTV1GPKAN7o65r9jHIqPeBCw0ICdaUZnRC5JKNwpRq4,9227
|
27
|
+
notionary/core/database/database_info_service.py,sha256=58k7om0UXP8w0jCJHewccG5UbOvELMBAbQvXOm7F1OM,1341
|
28
|
+
notionary/core/database/database_query_service.py,sha256=ggD-Sx9GdRTeKn9ytGBxijQw6euYgV__iTpObT24ebo,2442
|
29
|
+
notionary/core/database/database_schema_service.py,sha256=aVyguzw8YjgQ632TDyRCDfWWCsIok3vJ4Sx3dnG9pss,1869
|
30
|
+
notionary/core/database/notion_database_manager.py,sha256=E3qUxPYEJ1drurmtZaqepskH84Iw-4RN3MyXv8cb8zQ,11443
|
31
|
+
notionary/core/database/notion_database_manager_factory.py,sha256=jYQeoWV4VKyfCkbxCMVf0aeShfXDvE2EVTs3Jr--Ro8,8285
|
32
|
+
notionary/core/database/notion_database_schema.py,sha256=DpZPkrR15u24PGd8jhTrqngu__5LviEsU-VBZgRzrHM,12486
|
33
|
+
notionary/core/database/notion_database_writer.py,sha256=qpKOSDLI89GWL1kDnzLKSY5GVIzQHVYAWUl12_n-nwU,13651
|
34
|
+
notionary/core/database/page_service.py,sha256=NzKCU2G-mnmqOitNWCJ6jOr0HSv1vPi1-ScSruvFdqg,5190
|
35
|
+
notionary/core/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
|
36
|
+
notionary/core/page/notion_page_manager.py,sha256=iatr-ERpjdpdRRYH0w097YGVKbtor7GWWFqa421wOO8,4109
|
37
|
+
notionary/core/page/page_content_manager.py,sha256=wx-2bW4znIaBVZeFwtoVrmlGUwHMLBPOLkQCZDIV3sA,3180
|
38
|
+
notionary/core/page/property_formatter.py,sha256=X70Yfg0Y8HYLrFH7Y_BZVhhc_6b369jjn02bc5IZBBI,3780
|
39
|
+
notionary/core/page/meta_data/metadata_editor.py,sha256=FpB7BA5cicWmEictwdn7EuRBMX7bGvSxbdM5Grs-jLI,1347
|
40
|
+
notionary/exceptions/database_exceptions.py,sha256=I-Tx6bYRLpi5pjGPtbT-Mqxvz3BFgYTiuZxknJeLxtI,2638
|
41
|
+
notionary/exceptions/page_creation_exception.py,sha256=4v7IuZD6GsQLrqhDLriGjuG3ML638gAO53zDCrLePuU,281
|
42
|
+
notionary/util/logging_mixin.py,sha256=fKsx9t90bwvL74ZX3dU-sXdC4TZCQyO6qU9I8txkw_U,1369
|
43
|
+
notionary/util/singleton_decorator.py,sha256=GTNMfIlVNRUVMw_c88xqd12-DcqZJjmyidN54yqiNVw,472
|
44
|
+
notionary/util/uuid_utils.py,sha256=qS2tdJSqw_gLyQxVIqlIdmkzGa7b9bJ-vw88RiQ-oGc,680
|
45
|
+
notionary-0.1.3.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
|
46
|
+
notionary-0.1.3.dist-info/METADATA,sha256=CCgL4-_K2WKIjzW5CFeOpDvcHxqe92xjTc1YeWJ_hbM,6153
|
47
|
+
notionary-0.1.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
48
|
+
notionary-0.1.3.dist-info/top_level.txt,sha256=fhONa6BMHQXqthx5PanWGbPL0b8rdFqhrJKVLf_adSs,10
|
49
|
+
notionary-0.1.3.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
notionary
|
notionary-0.1.1.dist-info/RECORD
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
notionary-0.1.1.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
|
2
|
-
notionary-0.1.1.dist-info/METADATA,sha256=dfNzwMTI_-JHQAIOgKb-NOM2nbC_u2zzBukjiTzQTAg,6153
|
3
|
-
notionary-0.1.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
4
|
-
notionary-0.1.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
5
|
-
notionary-0.1.1.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
|
File without changes
|
File without changes
|