notion2pg-bulk 0.1.0__tar.gz

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.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.3
2
+ Name: notion2pg-bulk
3
+ Version: 0.1.0
4
+ Summary: A Python package that automatically finds and migrates to PostgreSQL all databases in a Notion workspace.
5
+ Keywords: notion,postgresql,migration,database,export,workspace,notion export,notion migrate
6
+ Author: benvigano
7
+ License: GPL-3.0-or-later
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Dist: notion-client>=2.2.1
20
+ Requires-Dist: psycopg2-binary>=2.9.5
21
+ Requires-Dist: sqlalchemy>=2.0.0
22
+ Requires-Dist: tabulate>=0.9.0
23
+ Requires-Dist: tqdm>=4.64.0
24
+ Requires-Python: >=3.8
25
+ Project-URL: Homepage, https://github.com/benvigano/notion2pg-bulk
26
+ Project-URL: Issues, https://github.com/benvigano/notion2pg-bulk/issues
27
+ Project-URL: Repository, https://github.com/benvigano/notion2pg-bulk
28
+ Description-Content-Type: text/markdown
29
+
30
+ # notion2pg_bulk
31
+
32
+ - Automatically discovers and migrates **all databases in a workspace** regardless of their views or position in the page structure
33
+ - Preserves **database descriptions and property descriptions**
34
+ - Supports **relation properties** using PostgreSQL arrays
35
+ - **Select field support**: Single and multi-select options stored in separate lookup tables with foreign key constraints
36
+ - Complies with Notion API limits (3 requests/second average)
37
+
38
+ ## Installation
39
+ ```bash
40
+ pip install notion2pg-bulk
41
+ ```
42
+
43
+ ## Setup and Usage
44
+
45
+ ### Requirements
46
+ - a **Notion API key** connected to all the databased that you wish to migrate (note: Notion now allows to connect databases ditectly from the integration configuration page, under the 'Access' tab. For quickly connecting **all databases** in a workspace, just tick all top level pages.
47
+ - PostgreSQL database connection URL
48
+
49
+ ### Interactive Mode
50
+ **On (Default):**
51
+ - Dispays detected databases and fields and asks for confirmation before running the migration
52
+ - Progress bar
53
+ - Shows post-migration notes
54
+
55
+ **Off:**
56
+ (`--quiet` flag in CLI or set `interactive_mode=False` in Python API)
57
+ - `.run()` runs migration directly and silently
58
+
59
+ ### CLI
60
+ **Options:**
61
+ - `--notion-token`: Notion integration token (or set `NOTION_TOKEN` env var)
62
+ - `--database-url`: PostgreSQL connection string (or set `DATABASE_URL` env var)
63
+ - `--quiet`: Run in non-interactive mode (skip validation steps and progress bars)
64
+ - `--extract-page-content`: Extract free-form content from page bodies (slower migration)
65
+
66
+ **Examples:**
67
+ ```bash
68
+ # Interactive mode (default)
69
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..."
70
+
71
+ # Non-interactive mode (skip validation steps)
72
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet
73
+
74
+ # Extract page content (slower but includes free-form content)
75
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --extract-page-content
76
+
77
+ # Non-interactive with page content extraction
78
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet --extract-page-content
79
+ ```
80
+
81
+ ### Python API:
82
+ ```python
83
+ from notion2pg_bulk import NotionMigrator
84
+ import sqlalchemy as sa
85
+
86
+ engine = sa.create_engine('postgresql://user:password@localhost/dbname')
87
+ migrator = NotionMigrator(
88
+ notion_token="your_notion_integration_token",
89
+ db_connection=engine,
90
+ interactive_mode=True,
91
+ extract_page_content=True,
92
+ )
93
+ migrator.run()
94
+ ```
95
+
96
+ ## Property Type Mapping
97
+
98
+ | Notion Property | PostgreSQL Type | Notes |
99
+ |----------------|-----------------|-------|
100
+ | Title | `TEXT` | Primary identifier |
101
+ | Rich Text | `TEXT` | Preserved as markdown with formatting |
102
+ | Number | `NUMERIC` | |
103
+ | Select | `TEXT` | Single option value + lookup table with foreign key |
104
+ | Multi-select | `TEXT[]` | Array of option values + lookup table with check constraints |
105
+ | Date | `TIMESTAMP` | |
106
+ | Checkbox | `BOOLEAN` | |
107
+ | URL | `TEXT` | |
108
+ | Email | `TEXT` | |
109
+ | Phone | `TEXT` | |
110
+ | Relation | `TEXT[]` | Array of related page IDs |
111
+ | People | `TEXT[]` | Array of user IDs |
112
+ | Files | `TEXT[]` | Array of file URLs |
113
+ | Created time | `TIMESTAMP` | |
114
+ | Created by | `TEXT` | User ID |
115
+ | Last edited time | `TIMESTAMP` | |
116
+ | Last edited by | `TEXT` | User ID |
117
+ | Formula | - | ⚠️ Skipped (not supported by Notion API) |
118
+ | Rollup | - | ⚠️ Skipped (not supported by Notion API) |
119
+
120
+ ## Additional Page Content Mode
121
+
122
+ The migrator can optionally extract free-form content from database pages. Extracted content is stored in the `additional_page_content` column as plain text.
123
+
124
+ ```python
125
+ migrator = NotionMigrator(
126
+ notion_token=token,
127
+ db_connection=engine,
128
+ extract_page_content=True # Default is false
129
+ )
130
+ ```
131
+
132
+ **Supported blocks:**
133
+ - Paragraph text
134
+ - Headings (all levels)
135
+ - Bulleted and numbered lists
136
+ - Code blocks
137
+ - To-do items
138
+ - Callouts
139
+ - Toggle blocks
140
+ - Dividers
141
+ - Embedded databases (ID reference only)
142
+
143
+ **Unsupported blocks:**
144
+ - Images and media files
145
+ - Complex block types (equations, embeds, etc.)
146
+ - Block formatting and styling
147
+ - Page hierarchies and relationships
148
+
149
+ **Post-migration analysis:**
150
+ When page content extraction is enabled, the migrator provides a comprehensive summary of:
151
+ - Unsupported block types found and their locations (grouped by record)
152
+ - Embedded databases that were referenced in page content but not connected to the integration (with full database IDs)
@@ -0,0 +1,123 @@
1
+ # notion2pg_bulk
2
+
3
+ - Automatically discovers and migrates **all databases in a workspace** regardless of their views or position in the page structure
4
+ - Preserves **database descriptions and property descriptions**
5
+ - Supports **relation properties** using PostgreSQL arrays
6
+ - **Select field support**: Single and multi-select options stored in separate lookup tables with foreign key constraints
7
+ - Complies with Notion API limits (3 requests/second average)
8
+
9
+ ## Installation
10
+ ```bash
11
+ pip install notion2pg-bulk
12
+ ```
13
+
14
+ ## Setup and Usage
15
+
16
+ ### Requirements
17
+ - a **Notion API key** connected to all the databased that you wish to migrate (note: Notion now allows to connect databases ditectly from the integration configuration page, under the 'Access' tab. For quickly connecting **all databases** in a workspace, just tick all top level pages.
18
+ - PostgreSQL database connection URL
19
+
20
+ ### Interactive Mode
21
+ **On (Default):**
22
+ - Dispays detected databases and fields and asks for confirmation before running the migration
23
+ - Progress bar
24
+ - Shows post-migration notes
25
+
26
+ **Off:**
27
+ (`--quiet` flag in CLI or set `interactive_mode=False` in Python API)
28
+ - `.run()` runs migration directly and silently
29
+
30
+ ### CLI
31
+ **Options:**
32
+ - `--notion-token`: Notion integration token (or set `NOTION_TOKEN` env var)
33
+ - `--database-url`: PostgreSQL connection string (or set `DATABASE_URL` env var)
34
+ - `--quiet`: Run in non-interactive mode (skip validation steps and progress bars)
35
+ - `--extract-page-content`: Extract free-form content from page bodies (slower migration)
36
+
37
+ **Examples:**
38
+ ```bash
39
+ # Interactive mode (default)
40
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..."
41
+
42
+ # Non-interactive mode (skip validation steps)
43
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet
44
+
45
+ # Extract page content (slower but includes free-form content)
46
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --extract-page-content
47
+
48
+ # Non-interactive with page content extraction
49
+ notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet --extract-page-content
50
+ ```
51
+
52
+ ### Python API:
53
+ ```python
54
+ from notion2pg_bulk import NotionMigrator
55
+ import sqlalchemy as sa
56
+
57
+ engine = sa.create_engine('postgresql://user:password@localhost/dbname')
58
+ migrator = NotionMigrator(
59
+ notion_token="your_notion_integration_token",
60
+ db_connection=engine,
61
+ interactive_mode=True,
62
+ extract_page_content=True,
63
+ )
64
+ migrator.run()
65
+ ```
66
+
67
+ ## Property Type Mapping
68
+
69
+ | Notion Property | PostgreSQL Type | Notes |
70
+ |----------------|-----------------|-------|
71
+ | Title | `TEXT` | Primary identifier |
72
+ | Rich Text | `TEXT` | Preserved as markdown with formatting |
73
+ | Number | `NUMERIC` | |
74
+ | Select | `TEXT` | Single option value + lookup table with foreign key |
75
+ | Multi-select | `TEXT[]` | Array of option values + lookup table with check constraints |
76
+ | Date | `TIMESTAMP` | |
77
+ | Checkbox | `BOOLEAN` | |
78
+ | URL | `TEXT` | |
79
+ | Email | `TEXT` | |
80
+ | Phone | `TEXT` | |
81
+ | Relation | `TEXT[]` | Array of related page IDs |
82
+ | People | `TEXT[]` | Array of user IDs |
83
+ | Files | `TEXT[]` | Array of file URLs |
84
+ | Created time | `TIMESTAMP` | |
85
+ | Created by | `TEXT` | User ID |
86
+ | Last edited time | `TIMESTAMP` | |
87
+ | Last edited by | `TEXT` | User ID |
88
+ | Formula | - | ⚠️ Skipped (not supported by Notion API) |
89
+ | Rollup | - | ⚠️ Skipped (not supported by Notion API) |
90
+
91
+ ## Additional Page Content Mode
92
+
93
+ The migrator can optionally extract free-form content from database pages. Extracted content is stored in the `additional_page_content` column as plain text.
94
+
95
+ ```python
96
+ migrator = NotionMigrator(
97
+ notion_token=token,
98
+ db_connection=engine,
99
+ extract_page_content=True # Default is false
100
+ )
101
+ ```
102
+
103
+ **Supported blocks:**
104
+ - Paragraph text
105
+ - Headings (all levels)
106
+ - Bulleted and numbered lists
107
+ - Code blocks
108
+ - To-do items
109
+ - Callouts
110
+ - Toggle blocks
111
+ - Dividers
112
+ - Embedded databases (ID reference only)
113
+
114
+ **Unsupported blocks:**
115
+ - Images and media files
116
+ - Complex block types (equations, embeds, etc.)
117
+ - Block formatting and styling
118
+ - Page hierarchies and relationships
119
+
120
+ **Post-migration analysis:**
121
+ When page content extraction is enabled, the migrator provides a comprehensive summary of:
122
+ - Unsupported block types found and their locations (grouped by record)
123
+ - Embedded databases that were referenced in page content but not connected to the integration (with full database IDs)
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "notion2pg-bulk"
3
+ version = "0.1.0"
4
+ description = "A Python package that automatically finds and migrates to PostgreSQL all databases in a Notion workspace."
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ dependencies = [
8
+ "notion-client>=2.2.1",
9
+ "psycopg2-binary>=2.9.5",
10
+ "sqlalchemy>=2.0.0",
11
+ "tabulate>=0.9.0",
12
+ "tqdm>=4.64.0",
13
+ ]
14
+ authors = [
15
+ { name = "benvigano" }
16
+ ]
17
+ license = { text = "GPL-3.0-or-later" }
18
+ keywords = ["notion", "postgresql", "migration", "database", "export", "workspace", "notion export", "notion migrate"]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.8",
25
+ "Programming Language :: Python :: 3.9",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Topic :: Database",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/benvigano/notion2pg-bulk"
35
+ Repository = "https://github.com/benvigano/notion2pg-bulk"
36
+ Issues = "https://github.com/benvigano/notion2pg-bulk/issues"
37
+
38
+ [project.scripts]
39
+ notion2pg-bulk = "notion2pg_bulk.cli:main"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.8.13,<0.9.0"]
43
+ build-backend = "uv_build"
@@ -0,0 +1,6 @@
1
+ """
2
+ Notion Workspace to PostgreSQL Migration Tool
3
+ """
4
+ from .migrator import NotionMigrator
5
+
6
+ __all__ = ["NotionMigrator"]
@@ -0,0 +1,69 @@
1
+ """
2
+ Command line interface for the Notion to PostgreSQL migration tool.
3
+ """
4
+
5
+ import argparse
6
+ import os
7
+ import sys
8
+ import sqlalchemy as sa
9
+ from .migrator import NotionMigrator
10
+
11
+
12
+ def main():
13
+ """Main CLI entry point."""
14
+ parser = argparse.ArgumentParser(
15
+ description="Migrate Notion workspace to PostgreSQL database"
16
+ )
17
+
18
+ parser.add_argument(
19
+ "--notion-token",
20
+ help="Notion integration token (or set NOTION_TOKEN env var)"
21
+ )
22
+
23
+ parser.add_argument(
24
+ "--database-url",
25
+ help="PostgreSQL connection string (or set DATABASE_URL env var)"
26
+ )
27
+
28
+ parser.add_argument(
29
+ "--extract-page-content",
30
+ action="store_true",
31
+ help="Extract free-form content from page bodies (slower migration)"
32
+ )
33
+
34
+ parser.add_argument(
35
+ "--quiet",
36
+ action="store_true",
37
+ help="Run in non-interactive mode (skip validation steps and progress bars)"
38
+ )
39
+
40
+ args = parser.parse_args()
41
+
42
+ # Get credentials
43
+ notion_token = args.notion_token or os.getenv("NOTION_TOKEN")
44
+ if not notion_token:
45
+ print("❌ Error: Notion token required. Set --notion-token or NOTION_TOKEN env var")
46
+ return 1
47
+
48
+ database_url = args.database_url or os.getenv("DATABASE_URL")
49
+ if not database_url:
50
+ print("❌ Error: Database URL required. Set --database-url or DATABASE_URL env var")
51
+ return 1
52
+
53
+ # Create database connection
54
+ engine = sa.create_engine(database_url)
55
+
56
+ # Initialize and run migrator
57
+ migrator = NotionMigrator(
58
+ notion_token=notion_token,
59
+ db_connection=engine,
60
+ interactive_mode=not args.quiet,
61
+ extract_page_content=args.extract_page_content
62
+ )
63
+
64
+ migrator.run()
65
+ return 0
66
+
67
+
68
+ if __name__ == "__main__":
69
+ sys.exit(main())