├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── dist
├── notion-cli-list-manager-0.1.4.tar.gz
├── notion-cli-list-manager-0.1.5.tar.gz
├── notion-cli-list-manager-0.1.6.tar.gz
├── notion_cli_list_manager-0.1.4-py3-none-any.whl
├── notion_cli_list_manager-0.1.5-py3-none-any.whl
└── notion_cli_list_manager-0.1.6-py3-none-any.whl
├── notion_cli_list_manager
├── __init__.py
├── __init__.pyc
├── main.py
├── page.py
└── utils.py
├── poetry.lock
├── pyproject.toml
├── readme.md
├── requirements.txt
├── showcase.gif
└── test
├── __init__.py
├── config.toml
└── main.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /vir_env
2 | .vscode
3 | /notion_cli_list_manager/token.json
4 | /notion_cli_list_manager/config.json
5 | /notion_cli_list_manager/pages.json
6 | /notion_cli_list_manager/config.toml
7 | /notion_cli_list_manager/pages.toml
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5 |
6 | ## [0.1.6] - 2022-01-05
7 | ### Fixed
8 | - Formula type property
9 | - Properties not unique name runtime bug
10 |
11 | ## [0.1.5] - 2022-01-05
12 | ### Added
13 | - Properties support! 🌈 At the present, properties are fully supported (except Relations and Rolls up that are __NS__ - Not supported) but read-only. Writeable ones will be supported in the next versions.
14 | - Use `list db --prop [LABEL]` to edit the displayed properties of an already saved database labeled [LABEL]
15 | - Error display color (yellow/orange)
16 | - Added CHANGELOG file.
17 |
18 | ### Changed
19 | - After a database submission, a prompt will ask you which properties you want to display.
20 |
21 | ### Fixed
22 | - Some typos
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Giacomo Salici
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dist/notion-cli-list-manager-0.1.4.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion-cli-list-manager-0.1.4.tar.gz
--------------------------------------------------------------------------------
/dist/notion-cli-list-manager-0.1.5.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion-cli-list-manager-0.1.5.tar.gz
--------------------------------------------------------------------------------
/dist/notion-cli-list-manager-0.1.6.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion-cli-list-manager-0.1.6.tar.gz
--------------------------------------------------------------------------------
/dist/notion_cli_list_manager-0.1.4-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion_cli_list_manager-0.1.4-py3-none-any.whl
--------------------------------------------------------------------------------
/dist/notion_cli_list_manager-0.1.5-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion_cli_list_manager-0.1.5-py3-none-any.whl
--------------------------------------------------------------------------------
/dist/notion_cli_list_manager-0.1.6-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/dist/notion_cli_list_manager-0.1.6-py3-none-any.whl
--------------------------------------------------------------------------------
/notion_cli_list_manager/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/notion_cli_list_manager/__init__.py
--------------------------------------------------------------------------------
/notion_cli_list_manager/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/notion_cli_list_manager/__init__.pyc
--------------------------------------------------------------------------------
/notion_cli_list_manager/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import typer
3 | from .page import page
4 |
5 | def string_parser(string: str):
6 |
7 | pars_raw = string.split(',')
8 |
9 | indexes = []
10 |
11 | for pars_temp in pars_raw:
12 | pars = pars_temp.split(':')
13 |
14 | if len(pars) == 1:
15 | indexes.append(int(pars[0]))
16 | else:
17 |
18 | if len(pars) == 2:
19 | pars.append('1')
20 |
21 | pars = [int(i) for i in pars]
22 | for i in range(pars[0], pars[1], pars[2]):
23 | indexes.append(i)
24 |
25 | return indexes
26 |
27 | def set_properties_list(properties, label):
28 | try:
29 | typer.echo(page.show_properties(properties))
30 |
31 | typer.secho("Insert the ordered list of indexes of the properties you want to display.", bold=True )
32 | indexes_string = typer.prompt("Eg: \"2,3,0\". Default:",
33 | default="{}:{}".format(0, len(properties)), type=str)
34 | indexes = string_parser(indexes_string)
35 | new_prop=[]
36 | for index in indexes:
37 | if properties[index] == "Index":
38 | typer.secho("Property cannot be named 'Index', please change it.", err= True, fg='yellow')
39 | elif properties[index] in new_prop:
40 | typer.secho("Property names must be unique.", err= True, fg='yellow')
41 | else:
42 | new_prop.append(properties[index])
43 | page.set_properties(label, new_prop)
44 | except:
45 | typer.secho("Error during property set.", err= True, fg='yellow')
46 |
47 | app = typer.Typer()
48 |
49 | @app.command()
50 | def add(title: str = typer.Argument(..., help="The title of the page."),
51 | db: str = typer.Option("Default", help="To add the page to a custom database (Leave the option blank and the default database will be used.)", show_default=False)):
52 | """
53 | Add an entry to your database.
54 | """
55 | page.add(title, db)
56 |
57 | @app.command()
58 | def rm(string_of_index: str = typer.Argument(..., metavar="INDEX", help="The index of the page to remove.")):
59 | """
60 | Remove a database entry. Use this after the "list" command.
61 | """
62 | page.remove(string_parser(string_of_index))
63 |
64 | @app.command()
65 | def set(
66 | token: str = typer.Option("", help="The API token"),
67 | id: str = typer.Option("", help="The database id")
68 | ):
69 | """
70 | Set the personal Notion API Token and the default Database id.
71 | """
72 |
73 |
74 | if token != "":
75 | page.set_token(token)
76 |
77 | if id != "":
78 | properties = page.set_database("Default", id)
79 | set_properties_list(properties, "Default")
80 |
81 |
82 | @app.command()
83 | def db(
84 | rm: str = typer.Option("", help="Remove a database from the manager.", metavar="LABEL"),
85 | label: str = typer.Option("", help="Add a database from the manager.", metavar="LABEL"),
86 | id: str = typer.Option("", help="The database id."),
87 | prop: str = typer.Option("", help="Set the database properties.", metavar="LABEL")
88 | ):
89 | """
90 | Display the databases saved on the manager. To add or to remove a database here does not cause the actual creation or deletion on Notion.
91 | """
92 | if prop != "":
93 | properties = page.get_properties(prop)
94 | set_properties_list(properties, prop)
95 | elif rm == "" and label == "" and id == "" and prop=="":
96 | page.show_databases()
97 | elif rm != "" and (label != "" or id != "") and prop=="":
98 | typer.secho("You cannot remove and add a databese at the same time.", err= True, fg='yellow')
99 | elif rm != "" and prop=="":
100 | page.rm_database(rm)
101 | elif prop=="":
102 | properties = page.set_database(label, id)
103 | set_properties_list(properties, label)
104 |
105 |
106 |
107 |
108 |
109 |
110 | @app.callback(invoke_without_command=True)
111 | def main(ctx: typer.Context,
112 | db: str = typer.Option("Default", help="Display the list for a specific database"),
113 | all: bool = typer.Option(False, help="Display all the lists.")):
114 | """
115 | The Notion Cli List Manager allows you to manage - as you can imagine - Notion Lists.
116 | Use the set command to initialize the token and the default database id.
117 | """
118 | if ctx.invoked_subcommand is None:
119 | if(all):
120 | page.all_by_all()
121 | else:
122 | page.all(db)
123 |
124 |
125 | if __name__=="__main__":
126 | app()
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/notion_cli_list_manager/page.py:
--------------------------------------------------------------------------------
1 | from os import get_terminal_size
2 | import typer
3 | from .utils import api_helper, toml_helper
4 | from prettytable import PrettyTable
5 |
6 |
7 |
8 | class page():
9 |
10 | def add(title, database):
11 | api_helper.new_page(api_helper, title, database)
12 |
13 | def all_by_all():
14 | index = 0
15 | x = PrettyTable()
16 | x.max_table_width=round(get_terminal_size().columns*0.8)
17 | x.field_names = ["Index", "Name"]
18 | x.align["Name"] = "l"
19 | pages_dict = {}
20 | for key in toml_helper.get_db_keys():
21 | index, x, pages_dict = page.query(key, index, x, pages_dict)
22 | typer.echo(x)
23 |
24 | toml_helper.set_dict(pages_dict, toml_helper.pages_file)
25 |
26 |
27 | def all(database):
28 |
29 | properties_list = ["Index"] + toml_helper.get_db_prop(database)
30 | if len(properties_list) ==1:
31 | properties_list.append("Name")
32 |
33 | x = PrettyTable()
34 | x.max_table_width=round(get_terminal_size().columns*0.95)
35 | x.field_names = properties_list
36 | x.align = "l"
37 |
38 | index, x, pages_dict = page.query(database, x = x, properties_list=properties_list)
39 |
40 | typer.echo(x)
41 |
42 | toml_helper.set_dict(pages_dict, toml_helper.pages_file)
43 |
44 | def query(database, index = 0, x = PrettyTable, pages_dict = {}, show_properties=True, properties_list = ["Index", "Name"]):
45 | r = api_helper.get_pages(api_helper, database)
46 |
47 | if type(r) is dict:
48 | if r["object"] == "list":
49 | for page in r["results"]:
50 | properties = page.get("properties")
51 | prop_types = {
52 | "title": "title",
53 | "rich": "rich_text",
54 | "select": "select",
55 | "multi": "multi_select",
56 | "created_time": "created_time",
57 | "number": "number",
58 | "url": "url",
59 | "last_edited_time": "last_edited_time",
60 | "checkbox":"checkbox",
61 | "phone":"phone_number",
62 | "mail":"email",
63 | "formula":"formula",
64 | "created_by":"created_by",
65 | "last_edit": "last_edited_by",
66 | "date":"date",
67 | "people":"people",
68 | "files":"files"
69 | }
70 | row={p:"" for p in properties_list}
71 | row["Index"]= (index)
72 | for property in properties:
73 | if property in row.keys():
74 | type_of = properties.get(property).get("type")
75 | #title
76 | if type_of == prop_types["title"]:
77 | path=properties.get(property).get(prop_types["title"])
78 | if len(path)>0:
79 | row[property] = path[0].get("text").get("content")
80 | #rich-text
81 | elif type_of == prop_types["rich"]:
82 | path=properties.get(property).get(prop_types["rich"])
83 | full_string = ""
84 | for p in path:
85 | full_string += p.get("plain_text")
86 | row[property] = full_string
87 | #select
88 | elif type_of == prop_types["select"]:
89 | path=properties.get(property).get(prop_types["select"])
90 | if path is not None:
91 | row[property] = path.get("name")
92 | #created_by
93 | elif type_of == prop_types["created_by"]:
94 | path=properties.get(property).get(prop_types["created_by"])
95 | if path is not None:
96 | row[property] = path.get("name")
97 | #date
98 | elif type_of == prop_types["date"]:
99 | path=properties.get(property).get(prop_types["date"])
100 | full_string = ""
101 | if path is not None:
102 | start = path.get("start")
103 | end = path.get("end")
104 | if start is not None:
105 | full_string += str(start)
106 | if end is not None:
107 | full_string += " -> "
108 |
109 | if end is not None:
110 | full_string += str(end)
111 |
112 | row[property] = full_string
113 | #last_edit
114 | elif type_of == prop_types["last_edit"]:
115 | path=properties.get(property).get(prop_types["last_edit"])
116 | if path is not None:
117 | row[property] = path.get("name")
118 | #formula
119 | elif type_of == prop_types["formula"]:
120 | path=properties.get(property).get(prop_types["formula"])
121 | if path is not None:
122 | row[property] = path.get("string")
123 | #multi-select
124 | elif type_of == prop_types["multi"]:
125 | path=properties.get(property).get(prop_types["multi"])
126 | full_string = ""
127 | for p in path:
128 | full_string += p.get("name") + " "
129 | row[property] = full_string
130 | #files
131 | elif type_of == prop_types["files"]:
132 | path=properties.get(property).get(prop_types["files"])
133 | full_string = ""
134 | for p in path:
135 | full_string += p.get("name") + " "
136 | row[property] = full_string
137 | #people
138 | elif type_of == prop_types["people"]:
139 | path=properties.get(property).get(prop_types["people"])
140 | full_string = ""
141 | for p in path:
142 | full_string += p.get("name") + " "
143 | row[property] = full_string
144 | #created_time
145 | elif type_of == prop_types["created_time"]:
146 | path=properties.get(property).get(prop_types["created_time"])
147 | row[property] = path
148 | #last_edited_time
149 | elif type_of == prop_types["last_edited_time"]:
150 | path=properties.get(property).get(prop_types["last_edited_time"])
151 | row[property] = path
152 | #phone
153 | elif type_of == prop_types["phone"]:
154 | path=properties.get(property).get(prop_types["phone"])
155 | row[property] = path
156 | #email
157 | elif type_of == prop_types["mail"]:
158 | path=properties.get(property).get(prop_types["mail"])
159 | row[property] = path
160 | #number
161 | elif type_of == prop_types["number"]:
162 | path=properties.get(property).get(prop_types["number"])
163 | if path is None:
164 | row[property] = ""
165 | else:
166 | row[property] = path
167 | #checkbox
168 | elif type_of == prop_types["checkbox"]:
169 | path=properties.get(property).get(prop_types["checkbox"])
170 | if path is True:
171 | row[property] = "[x]"
172 | else:
173 | row[property] = "[ ]"
174 | #url
175 | elif type_of == prop_types["url"]:
176 | path=properties.get(property).get(prop_types["url"])
177 | if path is None:
178 | row[property] = ""
179 | else:
180 | row[property] = path
181 | else:
182 | row[property] = "NS"
183 |
184 | x.add_row(row.values())
185 | pages_dict[str(index)] = page
186 | index+=1
187 |
188 | elif r["object"] == "error":
189 | typer.echo("Error:\t" + str(r["message"]))
190 |
191 |
192 | return index, x, pages_dict
193 |
194 |
195 | def remove(list_of_indexes):
196 | pages_dict = toml_helper.get_dict(toml_helper.pages_file)
197 | for index in list_of_indexes:
198 | p = pages_dict.get(str(index))
199 | if type(p) == dict:
200 | api_helper.remove_page(api_helper, p.get("id"))
201 |
202 | def set_token(token):
203 | dict = toml_helper.get_dict(toml_helper.config_file)
204 | dict["token"] = token
205 | toml_helper.set_dict(dict, toml_helper.config_file)
206 |
207 | def set_database(key, id):
208 | db_dict = toml_helper.get_dict(toml_helper.config_file)
209 |
210 | if "databases" not in db_dict.keys():
211 | db_dict["databases"] = {}
212 |
213 | db_dict.get("databases")[key] = {"id": id}
214 |
215 | toml_helper.set_dict(db_dict, toml_helper.config_file)
216 |
217 | properties = api_helper.get_db_props(api_helper, id)
218 |
219 | return list(properties)
220 |
221 |
222 |
223 | def show_databases():
224 | list = toml_helper.get_db_keys()
225 | x = PrettyTable()
226 | x.field_names = ["Label"]
227 | for label in list:
228 | x.add_row([label])
229 | typer.echo(x)
230 |
231 | def rm_database(label):
232 | dict = toml_helper.get_dict(toml_helper.config_file)
233 | r = dict.get("databases").pop(label)
234 | toml_helper.set_dict(dict, toml_helper.config_file)
235 |
236 | def get_properties(label):
237 | return list(api_helper.get_db_props(api_helper, toml_helper.get_db_id(label)))
238 |
239 | def show_properties(properties):
240 | x = PrettyTable()
241 | x.field_names = [i for i in range (0, len(properties))]
242 | x.add_row(properties)
243 | return x
244 |
245 | def set_properties(label, properties):
246 | db_dict = toml_helper.get_dict(toml_helper.config_file)
247 |
248 |
249 | db_dict.get("databases")[label]["properties"] = properties
250 |
251 | toml_helper.set_dict(db_dict, toml_helper.config_file)
252 |
--------------------------------------------------------------------------------
/notion_cli_list_manager/utils.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from requests.api import request
3 | import typer
4 | import toml
5 | from pathlib import Path
6 |
7 |
8 | class toml_helper():
9 | script_location = Path(__file__).absolute().parent
10 | config_file = script_location / "config.toml"
11 | pages_file = script_location / "pages.toml"
12 |
13 | def get_dict(f, verbose=False):
14 | try:
15 | with open(f, "r+") as file_text:
16 | r = toml.load(file_text)
17 | return r
18 | except FileNotFoundError:
19 | if verbose:
20 | typer.secho("File not found.", err= True, fg='yellow')
21 | except toml.TomlDecodeError:
22 | typer.secho("File encoding error.", err= True, fg='yellow')
23 |
24 | return {}
25 |
26 | def set_dict(dict, f):
27 | with open(f, "w") as file_text:
28 | return file_text.write(toml.dumps(dict))
29 |
30 |
31 | def get_db_id (database):
32 | try:
33 | dict = toml_helper.get_dict(toml_helper.config_file).get("databases")
34 | return str(dict.get(database).get("id"))
35 | except:
36 | return ""
37 |
38 |
39 | def get_db_keys():
40 | try:
41 | r = toml_helper.get_dict(toml_helper.config_file).get("databases")
42 | return r.keys()
43 | except:
44 | return []
45 |
46 | def get_db_prop(database):
47 | try:
48 | r = toml_helper.get_dict(toml_helper.config_file).get("databases").get(database)["properties"]
49 | return r
50 | except:
51 | return []
52 |
53 | class api_helper():
54 | api_url_pre = "https://api.notion.com/v1/"
55 | NOTION_API_KEY = str(toml_helper.get_dict(toml_helper.config_file).get("token"))
56 | payload = {
57 | "Authorization": NOTION_API_KEY,
58 | "Notion-Version": "2021-08-16",
59 | "Content-Type": "application/json"
60 | }
61 |
62 | def get_db_props(self, database):
63 | r = self.get_database(self, database)
64 |
65 | try:
66 | r = r.get("properties")
67 | if type(r) is dict:
68 | return r.keys()
69 | else:
70 | return []
71 | except:
72 | typer.echo(r)
73 | return []
74 |
75 | def get_database(self, database):
76 | return self.api(self, requests.get, "databases/" + database, "")
77 |
78 |
79 | def new_page(self, title, database):
80 | data = '{"parent":{"database_id":"'+ toml_helper.get_db_id(database) + \
81 | '"},"properties":{"Name":{"title":[{"text":{"content":"'+title+'"}}]}}}'
82 | return self.api_post(self, "pages", data)
83 |
84 |
85 | def get_pages(self, database):
86 | return self.api_post(self, "databases/" + toml_helper.get_db_id(database) + "/query", "")
87 |
88 |
89 |
90 | def remove_page(self, page_id):
91 | return self.api_delete(self, "blocks/" + page_id, "")
92 |
93 |
94 |
95 | def api_post(self, api_url_post, data):
96 | return self.api(self, requests.post, api_url_post, data)
97 |
98 |
99 | def api_delete(self, api_url_post, data):
100 | return self.api(self, requests.delete, api_url_post, data)
101 |
102 | def api(self, function, api_url_post, data):
103 | try:
104 | r = function(self.api_url_pre + api_url_post, headers=self.payload, data=data)
105 | if (r.status_code == 200):
106 | return r.json()
107 | else:
108 | typer.secho("Request post error", err= True, fg='yellow')
109 | return ""
110 | except:
111 | typer.secho("Request post error", err= True, fg='yellow')
112 | return ""
113 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "atomicwrites"
3 | version = "1.4.0"
4 | description = "Atomic file writes."
5 | category = "dev"
6 | optional = false
7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
8 |
9 | [[package]]
10 | name = "attrs"
11 | version = "21.4.0"
12 | description = "Classes Without Boilerplate"
13 | category = "dev"
14 | optional = false
15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
16 |
17 | [package.extras]
18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
19 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
22 |
23 | [[package]]
24 | name = "certifi"
25 | version = "2021.10.8"
26 | description = "Python package for providing Mozilla's CA Bundle."
27 | category = "main"
28 | optional = false
29 | python-versions = "*"
30 |
31 | [[package]]
32 | name = "charset-normalizer"
33 | version = "2.0.10"
34 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
35 | category = "main"
36 | optional = false
37 | python-versions = ">=3.5.0"
38 |
39 | [package.extras]
40 | unicode_backport = ["unicodedata2"]
41 |
42 | [[package]]
43 | name = "click"
44 | version = "8.0.3"
45 | description = "Composable command line interface toolkit"
46 | category = "main"
47 | optional = false
48 | python-versions = ">=3.6"
49 |
50 | [package.dependencies]
51 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
52 |
53 | [[package]]
54 | name = "colorama"
55 | version = "0.4.4"
56 | description = "Cross-platform colored terminal text."
57 | category = "main"
58 | optional = false
59 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
60 |
61 | [[package]]
62 | name = "idna"
63 | version = "3.3"
64 | description = "Internationalized Domain Names in Applications (IDNA)"
65 | category = "main"
66 | optional = false
67 | python-versions = ">=3.5"
68 |
69 | [[package]]
70 | name = "more-itertools"
71 | version = "8.12.0"
72 | description = "More routines for operating on iterables, beyond itertools"
73 | category = "dev"
74 | optional = false
75 | python-versions = ">=3.5"
76 |
77 | [[package]]
78 | name = "packaging"
79 | version = "21.3"
80 | description = "Core utilities for Python packages"
81 | category = "dev"
82 | optional = false
83 | python-versions = ">=3.6"
84 |
85 | [package.dependencies]
86 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
87 |
88 | [[package]]
89 | name = "pluggy"
90 | version = "0.13.1"
91 | description = "plugin and hook calling mechanisms for python"
92 | category = "dev"
93 | optional = false
94 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
95 |
96 | [package.extras]
97 | dev = ["pre-commit", "tox"]
98 |
99 | [[package]]
100 | name = "prettytable"
101 | version = "2.5.0"
102 | description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
103 | category = "main"
104 | optional = false
105 | python-versions = ">=3.6"
106 |
107 | [package.dependencies]
108 | wcwidth = "*"
109 |
110 | [package.extras]
111 | tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
112 |
113 | [[package]]
114 | name = "py"
115 | version = "1.11.0"
116 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
117 | category = "dev"
118 | optional = false
119 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
120 |
121 | [[package]]
122 | name = "pyparsing"
123 | version = "3.0.6"
124 | description = "Python parsing module"
125 | category = "dev"
126 | optional = false
127 | python-versions = ">=3.6"
128 |
129 | [package.extras]
130 | diagrams = ["jinja2", "railroad-diagrams"]
131 |
132 | [[package]]
133 | name = "pytest"
134 | version = "5.4.3"
135 | description = "pytest: simple powerful testing with Python"
136 | category = "dev"
137 | optional = false
138 | python-versions = ">=3.5"
139 |
140 | [package.dependencies]
141 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
142 | attrs = ">=17.4.0"
143 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
144 | more-itertools = ">=4.0.0"
145 | packaging = "*"
146 | pluggy = ">=0.12,<1.0"
147 | py = ">=1.5.0"
148 | wcwidth = "*"
149 |
150 | [package.extras]
151 | checkqa-mypy = ["mypy (==v0.761)"]
152 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
153 |
154 | [[package]]
155 | name = "requests"
156 | version = "2.27.0"
157 | description = "Python HTTP for Humans."
158 | category = "main"
159 | optional = false
160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
161 |
162 | [package.dependencies]
163 | certifi = ">=2017.4.17"
164 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
165 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
166 | urllib3 = ">=1.21.1,<1.27"
167 |
168 | [package.extras]
169 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
170 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
171 |
172 | [[package]]
173 | name = "toml"
174 | version = "0.10.2"
175 | description = "Python Library for Tom's Obvious, Minimal Language"
176 | category = "main"
177 | optional = false
178 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
179 |
180 | [[package]]
181 | name = "typer"
182 | version = "0.4.0"
183 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
184 | category = "main"
185 | optional = false
186 | python-versions = ">=3.6"
187 |
188 | [package.dependencies]
189 | click = ">=7.1.1,<9.0.0"
190 |
191 | [package.extras]
192 | all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"]
193 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"]
194 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"]
195 | test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)"]
196 |
197 | [[package]]
198 | name = "urllib3"
199 | version = "1.26.7"
200 | description = "HTTP library with thread-safe connection pooling, file post, and more."
201 | category = "main"
202 | optional = false
203 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
204 |
205 | [package.extras]
206 | brotli = ["brotlipy (>=0.6.0)"]
207 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
208 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
209 |
210 | [[package]]
211 | name = "wcwidth"
212 | version = "0.2.5"
213 | description = "Measures the displayed width of unicode strings in a terminal"
214 | category = "main"
215 | optional = false
216 | python-versions = "*"
217 |
218 | [metadata]
219 | lock-version = "1.1"
220 | python-versions = "^3.8"
221 | content-hash = "c102a1fd90b0ef4dedd7953bd5f74c885b71e0980c06648be2aec0b72988601a"
222 |
223 | [metadata.files]
224 | atomicwrites = [
225 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
226 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
227 | ]
228 | attrs = [
229 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
230 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
231 | ]
232 | certifi = [
233 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
234 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
235 | ]
236 | charset-normalizer = [
237 | {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"},
238 | {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"},
239 | ]
240 | click = [
241 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
242 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
243 | ]
244 | colorama = [
245 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
246 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
247 | ]
248 | idna = [
249 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
250 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
251 | ]
252 | more-itertools = [
253 | {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
254 | {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"},
255 | ]
256 | packaging = [
257 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
258 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
259 | ]
260 | pluggy = [
261 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
262 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
263 | ]
264 | prettytable = [
265 | {file = "prettytable-2.5.0-py3-none-any.whl", hash = "sha256:1411c65d21dca9eaa505ba1d041bed75a6d629ae22f5109a923f4e719cfecba4"},
266 | {file = "prettytable-2.5.0.tar.gz", hash = "sha256:f7da57ba63d55116d65e5acb147bfdfa60dceccabf0d607d6817ee2888a05f2c"},
267 | ]
268 | py = [
269 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
270 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
271 | ]
272 | pyparsing = [
273 | {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
274 | {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"},
275 | ]
276 | pytest = [
277 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
278 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
279 | ]
280 | requests = [
281 | {file = "requests-2.27.0-py2.py3-none-any.whl", hash = "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"},
282 | {file = "requests-2.27.0.tar.gz", hash = "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5"},
283 | ]
284 | toml = [
285 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
286 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
287 | ]
288 | typer = [
289 | {file = "typer-0.4.0-py3-none-any.whl", hash = "sha256:d81169725140423d072df464cad1ff25ee154ef381aaf5b8225352ea187ca338"},
290 | {file = "typer-0.4.0.tar.gz", hash = "sha256:63c3aeab0549750ffe40da79a1b524f60e08a2cbc3126c520ebf2eeaf507f5dd"},
291 | ]
292 | urllib3 = [
293 | {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
294 | {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
295 | ]
296 | wcwidth = [
297 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
298 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
299 | ]
300 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "notion-cli-list-manager"
3 | version = "0.1.6"
4 | description = "A simple command-line tool for managing Notion databases."
5 | authors = ["Jack Salici"]
6 | repository = "https://github.com/jacksalici/notion-cli-list-manager"
7 | keywords = ["Notion", "CLI", "todo manager"]
8 | license = "MIT"
9 | readme = "README.md"
10 | documentation = "https://github.com/jacksalici/notion-cli-list-manager/wiki"
11 |
12 | [tool.poetry.dependencies]
13 | python = "^3.8"
14 | requests = "^2.26.0"
15 | typer = "^0.4.0"
16 | colorama = "^0.4.4"
17 | toml = "^0.10.2"
18 | prettytable = "^2.5.0"
19 |
20 |
21 | [tool.poetry.scripts]
22 | list = "notion_cli_list_manager.main:app"
23 |
24 | [tool.poetry.dev-dependencies]
25 | pytest = "^5.2"
26 |
27 | [build-system]
28 | requires = ["poetry-core>=1.0.0"]
29 | build-backend = "poetry.core.masonry.api"
30 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 | ##### ⚠️ This project is still in work in progress, please forgive any little flaw here and there.
3 | # Notion CLI List Manager 🗂
4 | A simple command-line tool for managing [Notion](http://notion.so) ___List___ databases. ✨
5 |
6 | ### Increase your productivity with a simple command. 🛋
7 |
8 | 
9 |
10 | ## 📺 Features:
11 | - fast and clear; saving your idea is as simple as type `add "get money"` 💆♂️
12 | - tables are pretty-printed with fab ASCII tables 🌈
13 | - parameters are now supported [^3] 🎻
14 |
15 |
16 | ## 👾 Get Started:
17 | - Create a new internal api integration [here](https://www.notion.so/my-integrations).
18 | - ❗️ Share the default database you want to use with your integration.
19 | You can copy [my free simple template](https://jacksalici.notion.site/d75c9590dc8b4d62a6c65cbf3fdd1dfb?v=0e3782222f014d7bb3e44a87376e3cfb).
20 | - Download the tool: [^1]
21 | ```
22 | pip install notion-cli-list-manager
23 | ```
24 | - Set the token and your default database id:
25 | ```
26 | list set --token [token] --id [database-id]
27 | ```
28 | - You're done!
29 |
30 | ## 🧰 Syntax:
31 | TL;DR: `list` is the keyword for activating this tool from the terminal. Typing just `list`, the list of your default database's items will be shown. Other commands can be used typing `list [command]`
32 |
33 | | Commands:| | Args and options:|
34 | |---|---|---|
35 | | `list` | to display all the ___List___ items. | `--db [id] ` to display a specific database. Otherwise the default database will be shown.
`--all` to display all the lists.
36 | | `list add [title]` | to add a new ___List___ item called `title`. | `[title]` will be the text of the ___List___ item (and the title of the associated Notion database page)
`--db [id] ` to add the entry to a specific database. Otherwise, the default database will be used.|
37 | | `list rm [index]` | to remove the ___List___ item with the index `index`.
_(Command to call after `list`)_| `[index]` has to be formatted either like a range or a list, or a combination of these. E.g.: 3,4,6:10:2 will remove pages 3, 4, 6, 8.
38 | | `list db` | to display all the notion display saved in the manager. | `--label [LABEL] --id [ID]` to add a database to the manager. A prompt will then ask you the ordered indexes list.
`--rm [LABEL]` to remove a database named [LABEL] from the manager. Note that adding or removing a database to the manager does not cause the actual creation or deletion on Notion.
`--prop [LABEL]` to set which and in which order display the properties of an already saved database labeled [LABEL]. A prompt will then ask you the ordered indexes list[^3].
39 | | `list set --token [token] --id [database_id]` | to set the token and the ID of the Notion Database you want as default. _This must be executed as the first command_. | You can get the `[token]` as internal api integration [here](https://www.notion.so/my-integrations).
You can get the database id from the database url: notion.so/[username]/`[database_id]`?v=[view_id].
You can also use separately `--token` and `--id` to set just one parameter. After the `--id` command, a prompt will then ask you the ordered indexes list. |
40 |
41 | ## 🛒 Still to do:
42 | See the [project tab](https://github.com/jacksalici/notion-cli-list-manager/projects/1) for a complete and real-time-updated list.
43 | Issues and PRs are appreciated. 🤝
44 |
45 |
46 | [^3]: At the present, properties are fully supported (except Relations and Rolls up that are __NS__ - Not supported) but read-only. Writeable ones will be supported in the next versions.
47 |
48 | [^1]: You can also clone the repo to have always the very last version.
49 | Having installed Python3 and Pip3 on your machine, write on the terminal:
50 | `git clone https://github.com/jacksalici/notion-cli-list-manager.git notion-cli-list-manager`
51 | `pip3 install notion-cli-list-manager/dist/notion-cli-list-manager-[last-version].tar.gz`
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | typer
3 | colorama
4 | toml
5 | prettytable
6 |
--------------------------------------------------------------------------------
/showcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/showcase.gif
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jacksalici/notion-cli-list-manager/68125834f99939680e9c6de4c04183efc51bc31a/test/__init__.py
--------------------------------------------------------------------------------
/test/config.toml:
--------------------------------------------------------------------------------
1 | token = "secret_XgNTCXRenJtCIx7h4Ii79gOEtWaFXwc1KNtWFfOcHGX"
2 |
3 | [databases]
4 | [databases.Default]
5 | id = "70df07a1b4cd4208a93533ec30c21298"
6 |
7 |
--------------------------------------------------------------------------------
/test/main.py:
--------------------------------------------------------------------------------
1 | import toml
2 |
3 | def toml_parser():
4 | with open ("test/config.toml", "r") as f:
5 | dict = toml.load(f)
6 |
7 | return dict
8 |
9 |
10 |
11 |
12 | toml_parser()
--------------------------------------------------------------------------------