notionhelper 0.2.1__tar.gz → 0.2.2__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.
Files changed (25) hide show
  1. {notionhelper-0.2.1 → notionhelper-0.2.2}/PKG-INFO +1 -1
  2. {notionhelper-0.2.1 → notionhelper-0.2.2}/pyproject.toml +1 -1
  3. {notionhelper-0.2.1 → notionhelper-0.2.2}/src/notionhelper/helper.py +33 -12
  4. notionhelper-0.2.2/src/notionhelper/helper1.8.py +546 -0
  5. {notionhelper-0.2.1 → notionhelper-0.2.2}/uv.lock +1 -1
  6. {notionhelper-0.2.1 → notionhelper-0.2.2}/.claude/settings.local.json +0 -0
  7. {notionhelper-0.2.1 → notionhelper-0.2.2}/.coverage +0 -0
  8. {notionhelper-0.2.1 → notionhelper-0.2.2}/.github/workflows/claude-code-review.yml +0 -0
  9. {notionhelper-0.2.1 → notionhelper-0.2.2}/.github/workflows/claude.yml +0 -0
  10. {notionhelper-0.2.1 → notionhelper-0.2.2}/.gitignore +0 -0
  11. {notionhelper-0.2.1 → notionhelper-0.2.2}/.python-version +0 -0
  12. {notionhelper-0.2.1 → notionhelper-0.2.2}/README.md +0 -0
  13. {notionhelper-0.2.1 → notionhelper-0.2.2}/images/helper_logo.png +0 -0
  14. {notionhelper-0.2.1 → notionhelper-0.2.2}/images/json_builder.png.png +0 -0
  15. {notionhelper-0.2.1 → notionhelper-0.2.2}/images/logo.png +0 -0
  16. {notionhelper-0.2.1 → notionhelper-0.2.2}/images/pillio.png +0 -0
  17. {notionhelper-0.2.1 → notionhelper-0.2.2}/images/pillio2.png +0 -0
  18. {notionhelper-0.2.1 → notionhelper-0.2.2}/notion_api_examples.md +0 -0
  19. {notionhelper-0.2.1 → notionhelper-0.2.2}/notionapi_md_info.md +0 -0
  20. {notionhelper-0.2.1 → notionhelper-0.2.2}/pytest.ini +0 -0
  21. {notionhelper-0.2.1 → notionhelper-0.2.2}/src/notionhelper/__init__.py +0 -0
  22. {notionhelper-0.2.1 → notionhelper-0.2.2}/tests/README.md +0 -0
  23. {notionhelper-0.2.1 → notionhelper-0.2.2}/tests/__init__.py +0 -0
  24. {notionhelper-0.2.1 → notionhelper-0.2.2}/tests/conftest.py +0 -0
  25. {notionhelper-0.2.1 → notionhelper-0.2.2}/tests/test_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: notionhelper
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: NotionHelper is a Python library that simplifies interactions with the Notion API, enabling easy management of databases, pages, and files within Notion workspaces.
5
5
  Author-email: Jan du Plessis <drjanduplessis@icloud.com>
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "notionhelper"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "NotionHelper is a Python library that simplifies interactions with the Notion API, enabling easy management of databases, pages, and files within Notion workspaces."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -487,19 +487,22 @@ class NotionHelper:
487
487
  try:
488
488
  # Step 1: Create a File Upload object
489
489
  create_upload_url = "https://api.notion.com/v1/file_uploads"
490
- # File uploads still use the older API version
491
- upload_headers = {
490
+ headers = {
492
491
  "Authorization": f"Bearer {self.notion_token}",
493
492
  "Content-Type": "application/json",
494
493
  "Notion-Version": "2022-06-28",
495
494
  }
496
- response = requests.post(create_upload_url, headers=upload_headers, json={})
495
+ response = requests.post(create_upload_url, headers=headers, json={})
497
496
  response.raise_for_status()
498
497
  upload_data = response.json()
499
498
  upload_url = upload_data["upload_url"]
500
499
 
501
500
  # Step 2: Upload file contents
502
501
  with open(file_path, "rb") as f:
502
+ upload_headers = {
503
+ "Authorization": f"Bearer {self.notion_token}",
504
+ "Notion-Version": "2022-06-28",
505
+ }
503
506
  files = {'file': (os.path.basename(file_path), f, mimetypes.guess_type(file_path)[0] or 'application/octet-stream')}
504
507
  upload_response = requests.post(upload_url, headers=upload_headers, files=files)
505
508
  upload_response.raise_for_status()
@@ -512,7 +515,13 @@ class NotionHelper:
512
515
 
513
516
  def attach_file_to_page(self, page_id: str, file_upload_id: str) -> Dict[str, Any]:
514
517
  """Attaches an uploaded file to a specific page."""
515
- payload = {
518
+ attach_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
519
+ headers = {
520
+ "Authorization": f"Bearer {self.notion_token}",
521
+ "Content-Type": "application/json",
522
+ "Notion-Version": "2022-06-28",
523
+ }
524
+ data = {
516
525
  "children": [
517
526
  {
518
527
  "type": "file",
@@ -525,12 +534,18 @@ class NotionHelper:
525
534
  }
526
535
  ]
527
536
  }
528
- url = f"https://api.notion.com/v1/blocks/{page_id}/children"
529
- return self._make_request("PATCH", url, payload)
537
+ response = requests.patch(attach_url, headers=headers, json=data)
538
+ return response.json()
530
539
 
531
540
  def embed_image_to_page(self, page_id: str, file_upload_id: str) -> Dict[str, Any]:
532
541
  """Embeds an uploaded image to a specific page."""
533
- payload = {
542
+ attach_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
543
+ headers = {
544
+ "Authorization": f"Bearer {self.notion_token}",
545
+ "Content-Type": "application/json",
546
+ "Notion-Version": "2022-06-28",
547
+ }
548
+ data = {
534
549
  "children": [
535
550
  {
536
551
  "type": "image",
@@ -543,14 +558,20 @@ class NotionHelper:
543
558
  }
544
559
  ]
545
560
  }
546
- url = f"https://api.notion.com/v1/blocks/{page_id}/children"
547
- return self._make_request("PATCH", url, payload)
561
+ response = requests.patch(attach_url, headers=headers, json=data)
562
+ return response.json()
548
563
 
549
564
  def attach_file_to_page_property(
550
565
  self, page_id: str, property_name: str, file_upload_id: str, file_name: str
551
566
  ) -> Dict[str, Any]:
552
567
  """Attaches a file to a Files & Media property on a specific page."""
553
- payload = {
568
+ update_url = f"https://api.notion.com/v1/pages/{page_id}"
569
+ headers = {
570
+ "Authorization": f"Bearer {self.notion_token}",
571
+ "Content-Type": "application/json",
572
+ "Notion-Version": "2022-06-28",
573
+ }
574
+ data = {
554
575
  "properties": {
555
576
  property_name: {
556
577
  "files": [
@@ -563,8 +584,8 @@ class NotionHelper:
563
584
  }
564
585
  }
565
586
  }
566
- url = f"https://api.notion.com/v1/pages/{page_id}"
567
- return self._make_request("PATCH", url, payload)
587
+ response = requests.patch(update_url, headers=headers, json=data)
588
+ return response.json()
568
589
 
569
590
  def one_step_image_embed(self, page_id: str, file_path: str) -> Dict[str, Any]:
570
591
  """Uploads an image and embeds it in a Notion page in one step."""
@@ -0,0 +1,546 @@
1
+ from typing import Optional, Dict, List, Any
2
+ from notion_client import Client
3
+ import pandas as pd
4
+ import os
5
+ import requests
6
+ import mimetypes
7
+
8
+ # NotionHelper can be used in conjunction with the Streamlit APP: (Notion API JSON)[https://notioinapiassistant.streamlit.app]
9
+
10
+
11
+ class NotionHelper:
12
+ """
13
+ A helper class to interact with the Notion API.
14
+
15
+ Methods
16
+ -------
17
+ __init__():
18
+ Initializes the NotionHelper instance and authenticates with the Notion API.
19
+
20
+ authenticate():
21
+ Authenticates with the Notion API using a token from environment variables.
22
+
23
+ get_database(database_id):
24
+ Fetches the schema of a Notion database given its database_id.
25
+
26
+ notion_search_db(database_id, query=""):
27
+ Searches for pages in a Notion database that contain the specified query in their title.
28
+
29
+ notion_get_page(page_id):
30
+ Returns the JSON of the page properties and an array of blocks on a Notion page given its page_id.
31
+
32
+ create_database(parent_page_id, database_title, properties):
33
+ Creates a new database in Notion under the specified parent page with the given title and properties.
34
+
35
+ new_page_to_db(database_id, page_properties):
36
+ Adds a new page to a Notion database with the specified properties.
37
+
38
+ append_page_body(page_id, blocks):
39
+ Appends blocks of text to the body of a Notion page.
40
+
41
+ get_all_page_ids(database_id):
42
+ Returns the IDs of all pages in a given Notion database.
43
+
44
+ get_all_pages_as_json(database_id, limit=None):
45
+ Returns a list of JSON objects representing all pages in the given database, with all properties.
46
+
47
+ get_all_pages_as_dataframe(database_id, limit=None):
48
+ Returns a Pandas DataFrame representing all pages in the given database, with selected properties.
49
+
50
+ upload_file(file_path):
51
+ Uploads a file to Notion and returns the file upload object.
52
+
53
+ attach_file_to_page(page_id, file_upload_id):
54
+ Attaches an uploaded file to a specific page.
55
+
56
+ embed_image_to_page(page_id, file_upload_id):
57
+ Embeds an uploaded image to a specific page.
58
+
59
+ attach_file_to_page_property(page_id, property_name, file_upload_id, file_name):
60
+ Attaches a file to a Files & Media property on a specific page.
61
+ """
62
+
63
+ def __init__(self, notion_token: str):
64
+ """Initializes the NotionHelper instance and authenticates with the Notion API
65
+ using the provided token."""
66
+ self.notion_token = notion_token
67
+ self.notion = Client(auth=self.notion_token)
68
+
69
+ def get_database(self, database_id: str) -> Dict[str, Any]:
70
+ """Retrieves the schema of a Notion database given its database_id.
71
+
72
+ Parameters
73
+ ----------
74
+ database_id : str
75
+ The unique identifier of the Notion database.
76
+
77
+ Returns
78
+ -------
79
+ dict
80
+ A dictionary representing the database schema.
81
+ """
82
+ try:
83
+ response = self.notion.databases.retrieve(database_id=database_id)
84
+ return response
85
+ except Exception as e:
86
+ raise Exception(f"Failed to retrieve database {database_id}: {str(e)}")
87
+
88
+ def notion_search_db(
89
+ self, database_id: str, query: str = ""
90
+ ) -> None:
91
+ """Searches for pages in a Notion database that contain the specified query in their title."""
92
+ my_pages = self.notion.databases.query(
93
+ database_id=database_id,
94
+ filter={
95
+ "property": "title",
96
+ "rich_text": {"contains": query},
97
+ },
98
+ )
99
+
100
+ page_title = my_pages["results"][0]["properties"]["Code / Notebook Description"]["title"][0]["plain_text"]
101
+ page_url = my_pages["results"][0]["url"]
102
+
103
+ page_list = my_pages["results"]
104
+ count = 1
105
+ for page in page_list:
106
+ try:
107
+ print(
108
+ count,
109
+ page["properties"]["Code / Notebook Description"]["title"][0]["plain_text"],
110
+ )
111
+ except IndexError:
112
+ print("No results found.")
113
+
114
+ print(page["url"])
115
+ print()
116
+ count += 1
117
+
118
+ # pprint.pprint(page)
119
+
120
+ def notion_get_page(self, page_id: str) -> Dict[str, Any]:
121
+ """Retrieves the JSON of the page properties and an array of blocks on a Notion page given its page_id."""
122
+
123
+ # Retrieve the page and block data
124
+ page = self.notion.pages.retrieve(page_id)
125
+ blocks = self.notion.blocks.children.list(page_id)
126
+
127
+ # Extract all properties as a JSON object
128
+ properties = page.get("properties", {})
129
+ content = [block for block in blocks["results"]]
130
+
131
+ # Print the full JSON of the properties
132
+ print(properties)
133
+
134
+ # Return the properties JSON and blocks content
135
+ return {"properties": properties, "content": content}
136
+
137
+ def create_database(self, parent_page_id: str, database_title: str, properties: Dict[str, Any]) -> Dict[str, Any]:
138
+ """Creates a new database in Notion.
139
+
140
+ This method creates a new database under a specified parent page with the provided title and property definitions.
141
+
142
+ Parameters:
143
+ parent_page_id (str): The unique identifier of the parent page.
144
+ database_title (str): The title for the new database.
145
+ properties (dict): A dictionary defining the property schema for the database.
146
+
147
+ Returns:
148
+ dict: The JSON response from the Notion API containing details about the created database.
149
+ """
150
+
151
+ # Define the properties for the database
152
+ new_database = {
153
+ "parent": {"type": "page_id", "page_id": parent_page_id},
154
+ "title": [{"type": "text", "text": {"content": database_title}}],
155
+ "properties": properties,
156
+ }
157
+
158
+ response = self.notion.databases.create(**new_database)
159
+ return response
160
+
161
+ def new_page_to_db(self, database_id: str, page_properties: Dict[str, Any]) -> Dict[str, Any]:
162
+ """Adds a new page to a Notion database."""
163
+
164
+ new_page = {
165
+ "parent": {"database_id": database_id},
166
+ "properties": page_properties,
167
+ }
168
+
169
+ response = self.notion.pages.create(**new_page)
170
+ return response
171
+
172
+ def append_page_body(self, page_id: str, blocks: List[Dict[str, Any]]) -> Dict[str, Any]:
173
+ """Appends blocks of text to the body of a Notion page."""
174
+
175
+ new_blocks = {"children": blocks}
176
+
177
+ response = self.notion.blocks.children.append(block_id=page_id, **new_blocks)
178
+ return response
179
+
180
+ def get_all_page_ids(self, database_id: str) -> List[str]:
181
+ """Returns the IDs of all pages in a given database."""
182
+
183
+ my_pages = self.notion.databases.query(database_id=database_id)
184
+ page_ids = [page["id"] for page in my_pages["results"]]
185
+ return page_ids
186
+
187
+ def get_all_pages_as_json(self, database_id: str, limit: Optional[int] = None) -> List[Dict[str, Any]]:
188
+ """Returns a list of JSON objects representing all pages in the given database, with all properties.
189
+ You can specify the number of entries to be loaded using the `limit` parameter.
190
+ """
191
+
192
+ # Use pagination to remove any limits on number of entries, optionally limited by `limit` argument
193
+ pages_json = []
194
+ has_more = True
195
+ start_cursor = None
196
+ count = 0
197
+
198
+ while has_more:
199
+ my_pages = self.notion.databases.query(
200
+ **{
201
+ "database_id": database_id,
202
+ "start_cursor": start_cursor,
203
+ }
204
+ )
205
+ pages_json.extend([page["properties"] for page in my_pages["results"]])
206
+ has_more = my_pages.get("has_more", False)
207
+ start_cursor = my_pages.get("next_cursor", None)
208
+ count += len(my_pages["results"])
209
+
210
+ if limit is not None and count >= limit:
211
+ pages_json = pages_json[:limit]
212
+ break
213
+
214
+ return pages_json
215
+
216
+ def get_all_pages_as_dataframe(self, database_id: str, limit: Optional[int] = None, include_page_ids: bool = True) -> pd.DataFrame:
217
+ """Retrieves all pages from a Notion database and returns them as a Pandas DataFrame.
218
+
219
+ This method collects pages from the specified Notion database, optionally including the page IDs,
220
+ and extracts a predefined set of allowed properties from each page to form a structured DataFrame.
221
+ Numeric values are formatted to avoid scientific notation.
222
+
223
+ Parameters:
224
+ database_id (str): The identifier of the Notion database.
225
+ limit (int, optional): Maximum number of page entries to include. If None, all pages are retrieved.
226
+ include_page_ids (bool, optional): If True, includes an additional column 'notion_page_id' in the DataFrame.
227
+ Defaults to True.
228
+
229
+ Returns:
230
+ pandas.DataFrame: A DataFrame where each row represents a page with columns corresponding to page properties.
231
+ If include_page_ids is True, an additional column 'notion_page_id' is included.
232
+ """
233
+ # Retrieve pages with or without page IDs based on the flag
234
+ if include_page_ids:
235
+ pages_json = []
236
+ has_more = True
237
+ start_cursor = None
238
+ count = 0
239
+ # Retrieve pages with pagination including the page ID in properties
240
+ while has_more:
241
+ my_pages = self.notion.databases.query(
242
+ database_id=database_id,
243
+ start_cursor=start_cursor,
244
+ )
245
+ for page in my_pages["results"]:
246
+ props = page["properties"]
247
+ props["notion_page_id"] = page.get("id", "")
248
+ pages_json.append(props)
249
+ has_more = my_pages.get("has_more", False)
250
+ start_cursor = my_pages.get("next_cursor", None)
251
+ count += len(my_pages["results"])
252
+ if limit is not None and count >= limit:
253
+ pages_json = pages_json[:limit]
254
+ break
255
+ else:
256
+ pages_json = self.get_all_pages_as_json(database_id, limit=limit)
257
+
258
+ data = []
259
+ # Define the list of allowed property types that we want to extract
260
+ allowed_properties = [
261
+ "title",
262
+ "status",
263
+ "number",
264
+ "date",
265
+ "url",
266
+ "checkbox",
267
+ "rich_text",
268
+ "email",
269
+ "select",
270
+ "people",
271
+ "phone_number",
272
+ "multi_select",
273
+ "created_time",
274
+ "created_by",
275
+ "rollup",
276
+ "relation",
277
+ "last_edited_by",
278
+ "last_edited_time",
279
+ "formula",
280
+ "file",
281
+ ]
282
+ if include_page_ids:
283
+ allowed_properties.append("notion_page_id")
284
+
285
+ for page in pages_json:
286
+ row = {}
287
+ for key, value in page.items():
288
+ if key == "notion_page_id":
289
+ row[key] = value
290
+ continue
291
+ property_type = value.get("type", "")
292
+ if property_type in allowed_properties:
293
+ if property_type == "title":
294
+ row[key] = value.get("title", [{}])[0].get("plain_text", "")
295
+ elif property_type == "status":
296
+ row[key] = value.get("status", {}).get("name", "")
297
+ elif property_type == "number":
298
+ number_value = value.get("number", None)
299
+ row[key] = float(number_value) if isinstance(number_value, (int, float)) else None
300
+ elif property_type == "date":
301
+ date_field = value.get("date", {})
302
+ row[key] = date_field.get("start", "") if date_field else ""
303
+ elif property_type == "url":
304
+ row[key] = value.get("url", "")
305
+ elif property_type == "checkbox":
306
+ row[key] = value.get("checkbox", False)
307
+ elif property_type == "rich_text":
308
+ rich_text_field = value.get("rich_text", [])
309
+ row[key] = rich_text_field[0].get("plain_text", "") if rich_text_field else ""
310
+ elif property_type == "email":
311
+ row[key] = value.get("email", "")
312
+ elif property_type == "select":
313
+ select_field = value.get("select", {})
314
+ row[key] = select_field.get("name", "") if select_field else ""
315
+ elif property_type == "people":
316
+ people_list = value.get("people", [])
317
+ if people_list:
318
+ person = people_list[0]
319
+ row[key] = {"name": person.get("name", ""), "email": person.get("person", {}).get("email", "")}
320
+ elif property_type == "phone_number":
321
+ row[key] = value.get("phone_number", "")
322
+ elif property_type == "multi_select":
323
+ multi_select_field = value.get("multi_select", [])
324
+ row[key] = [item.get("name", "") for item in multi_select_field]
325
+ elif property_type == "created_time":
326
+ row[key] = value.get("created_time", "")
327
+ elif property_type == "created_by":
328
+ created_by = value.get("created_by", {})
329
+ row[key] = created_by.get("name", "")
330
+ elif property_type == "rollup":
331
+ rollup_field = value.get("rollup", {}).get("array", [])
332
+ row[key] = [item.get("date", {}).get("start", "") for item in rollup_field]
333
+ elif property_type == "relation":
334
+ relation_list = value.get("relation", [])
335
+ row[key] = [relation.get("id", "") for relation in relation_list]
336
+ elif property_type == "last_edited_by":
337
+ last_edited_by = value.get("last_edited_by", {})
338
+ row[key] = last_edited_by.get("name", "")
339
+ elif property_type == "last_edited_time":
340
+ row[key] = value.get("last_edited_time", "")
341
+ elif property_type == "formula":
342
+ formula_value = value.get("formula", {})
343
+ row[key] = formula_value.get(formula_value.get("type", ""), "")
344
+ elif property_type == "file":
345
+ files = value.get("files", [])
346
+ row[key] = [file.get("name", "") for file in files]
347
+ data.append(row)
348
+
349
+ df = pd.DataFrame(data)
350
+ pd.options.display.float_format = "{:.3f}".format
351
+ return df
352
+
353
+ def upload_file(self, file_path: str) -> Dict[str, Any]:
354
+ """Uploads a file to Notion and returns the file upload object."""
355
+ if not os.path.exists(file_path):
356
+ raise FileNotFoundError(f"File not found: {file_path}")
357
+
358
+ try:
359
+ # Step 1: Create a File Upload object
360
+ create_upload_url = "https://api.notion.com/v1/file_uploads"
361
+ headers = {
362
+ "Authorization": f"Bearer {self.notion_token}",
363
+ "Content-Type": "application/json",
364
+ "Notion-Version": "2022-06-28",
365
+ }
366
+ response = requests.post(create_upload_url, headers=headers, json={})
367
+ response.raise_for_status()
368
+ upload_data = response.json()
369
+ upload_url = upload_data["upload_url"]
370
+
371
+ # Step 2: Upload file contents
372
+ with open(file_path, "rb") as f:
373
+ upload_headers = {
374
+ "Authorization": f"Bearer {self.notion_token}",
375
+ "Notion-Version": "2022-06-28",
376
+ }
377
+ files = {'file': (os.path.basename(file_path), f, mimetypes.guess_type(file_path)[0] or 'application/octet-stream')}
378
+ upload_response = requests.post(upload_url, headers=upload_headers, files=files)
379
+ upload_response.raise_for_status()
380
+
381
+ return upload_response.json()
382
+ except requests.RequestException as e:
383
+ raise Exception(f"Failed to upload file {file_path}: {str(e)}")
384
+ except Exception as e:
385
+ raise Exception(f"Error uploading file {file_path}: {str(e)}")
386
+
387
+ def attach_file_to_page(self, page_id: str, file_upload_id: str) -> Dict[str, Any]:
388
+ """Attaches an uploaded file to a specific page."""
389
+ attach_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
390
+ headers = {
391
+ "Authorization": f"Bearer {self.notion_token}",
392
+ "Content-Type": "application/json",
393
+ "Notion-Version": "2022-06-28",
394
+ }
395
+ data = {
396
+ "children": [
397
+ {
398
+ "type": "file",
399
+ "file": {
400
+ "type": "file_upload",
401
+ "file_upload": {
402
+ "id": file_upload_id
403
+ }
404
+ }
405
+ }
406
+ ]
407
+ }
408
+ response = requests.patch(attach_url, headers=headers, json=data)
409
+ return response.json()
410
+
411
+ def embed_image_to_page(self, page_id: str, file_upload_id: str) -> Dict[str, Any]:
412
+ """Embeds an uploaded image to a specific page."""
413
+ attach_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
414
+ headers = {
415
+ "Authorization": f"Bearer {self.notion_token}",
416
+ "Content-Type": "application/json",
417
+ "Notion-Version": "2022-06-28",
418
+ }
419
+ data = {
420
+ "children": [
421
+ {
422
+ "type": "image",
423
+ "image": {
424
+ "type": "file_upload",
425
+ "file_upload": {
426
+ "id": file_upload_id
427
+ }
428
+ }
429
+ }
430
+ ]
431
+ }
432
+ response = requests.patch(attach_url, headers=headers, json=data)
433
+ return response.json()
434
+
435
+ def attach_file_to_page_property(
436
+ self, page_id: str, property_name: str, file_upload_id: str, file_name: str
437
+ ) -> Dict[str, Any]:
438
+ """Attaches a file to a Files & Media property on a specific page."""
439
+ update_url = f"https://api.notion.com/v1/pages/{page_id}"
440
+ headers = {
441
+ "Authorization": f"Bearer {self.notion_token}",
442
+ "Content-Type": "application/json",
443
+ "Notion-Version": "2022-06-28",
444
+ }
445
+ data = {
446
+ "properties": {
447
+ property_name: {
448
+ "files": [
449
+ {
450
+ "type": "file_upload",
451
+ "file_upload": {"id": file_upload_id},
452
+ "name": file_name,
453
+ }
454
+ ]
455
+ }
456
+ }
457
+ }
458
+ response = requests.patch(update_url, headers=headers, json=data)
459
+ return response.json()
460
+
461
+ def one_step_image_embed(self, page_id: str, file_path: str) -> Dict[str, Any]:
462
+ """Uploads an image and embeds it in a Notion page in one step."""
463
+
464
+ # Upload the file
465
+ file_upload = self.upload_file(file_path)
466
+ file_upload_id = file_upload["id"]
467
+
468
+ # Embed the image in the page
469
+ return self.embed_image_to_page(page_id, file_upload_id)
470
+
471
+ def one_step_file_to_page(self, page_id: str, file_path: str) -> Dict[str, Any]:
472
+ """Uploads a file and attaches it to a Notion page in one step."""
473
+
474
+ # Upload the file
475
+ file_upload = self.upload_file(file_path)
476
+ file_upload_id = file_upload["id"]
477
+
478
+ # Attach the file to the page
479
+ return self.attach_file_to_page(page_id, file_upload_id)
480
+
481
+ def one_step_file_to_page_property(self, page_id: str, property_name: str, file_path: str, file_name: str) -> Dict[str, Any]:
482
+ """Uploads a file and attaches it to a Notion page property in one step."""
483
+
484
+ # Upload the file
485
+ file_upload = self.upload_file(file_path)
486
+ file_upload_id = file_upload["id"]
487
+
488
+ # Attach the file to the page property
489
+ return self.attach_file_to_page_property(page_id, property_name, file_upload_id, file_name)
490
+
491
+ def info(self) -> Optional[Any]:
492
+ """Displays comprehensive library information in a Jupyter notebook.
493
+
494
+ Shows:
495
+ - Library name and description
496
+ - Complete list of all available methods with descriptions
497
+ - Version information
498
+ - Optional logo display (if available)
499
+
500
+ Returns:
501
+ IPython.display.HTML: An HTML display object or None if IPython is not available.
502
+ """
503
+ try:
504
+ from IPython.display import HTML
505
+ import base64
506
+ import inspect
507
+
508
+ # Get logo image data
509
+ logo_path = os.path.join(os.path.dirname(__file__), '../images/helper_logo.png')
510
+ if os.path.exists(logo_path):
511
+ with open(logo_path, "rb") as image_file:
512
+ encoded_logo = base64.b64encode(image_file.read()).decode('utf-8')
513
+ else:
514
+ encoded_logo = ""
515
+
516
+ # Get all methods and their docstrings
517
+ methods = []
518
+ for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
519
+ if not name.startswith('_'):
520
+ doc = inspect.getdoc(method) or "No description available"
521
+ methods.append(f"<li><code>{name}()</code>: {doc.splitlines()[0]}</li>")
522
+
523
+ # Create HTML content
524
+ html_content = f"""
525
+ <div style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 0 auto; color: #1e293b;">
526
+ <h1 style="color: #1e293b;">NotionHelper Library</h1>
527
+ {f'<img src="data:image/png;base64,{encoded_logo}" style="max-width: 200px; margin: 20px 0; display: block;">' if encoded_logo else ''}
528
+ <p style="font-size: 1.1rem;">A Python helper class for interacting with the Notion API.</p>
529
+ <h3 style="color: #1e293b;">All Available Methods:</h3>
530
+ <ul style="list-style-type: none; padding-left: 0;">
531
+ {''.join(methods)}
532
+ </ul>
533
+ <h3 style="color: #1e293b;">Features:</h3>
534
+ <ul style="list-style-type: none; padding-left: 0;">
535
+ <li style="margin-bottom: 8px;">Database querying and manipulation</li>
536
+ <li style="margin-bottom: 8px;">Page creation and editing</li>
537
+ <li style="margin-bottom: 8px;">File uploads and attachments</li>
538
+ <li style="margin-bottom: 8px;">Data conversion to Pandas DataFrames</li>
539
+ </ul>
540
+ <p style="font-size: 0.9rem; color: #64748b;">Version: {getattr(self, '__version__', '1.0.0')}</p>
541
+ </div>
542
+ """
543
+ return HTML(html_content)
544
+ except ImportError:
545
+ print("IPython is required for this functionality. Please install it with: pip install ipython")
546
+ return None
@@ -1279,7 +1279,7 @@ wheels = [
1279
1279
 
1280
1280
  [[package]]
1281
1281
  name = "notionhelper"
1282
- version = "0.1.8"
1282
+ version = "0.2.1"
1283
1283
  source = { editable = "." }
1284
1284
  dependencies = [
1285
1285
  { name = "mimetype" },
File without changes
File without changes
File without changes
File without changes