Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small refactor #42

Merged
merged 5 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions n2y/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import json
johndgiese marked this conversation as resolved.
Show resolved Hide resolved
import logging

from n2y.utils import strip_dashes


logger = logging.getLogger(__name__)


def database_config_json_to_dict(config_json):
try:
config = json.loads(config_json)
except json.JSONDecodeError:
logger.error("Error parsing the data config JSON")
return None
if not validate_database_config(config):
return None
return config


def validate_database_config(config):
try:
for database_id, config_values in config.items():
if not _valid_id(database_id):
logger.error("Invalid database id in database config: %s", database_id)
return False
for key, values in config_values.items():
if key not in ["sorts", "filter"]:
logger.error("Invalid key in database config: %s", key)
return False
if not isinstance(values, dict) and not isinstance(values, list):
logger.error(
"Invalid value of type '%s' for key '%s' in database config, "
"expected dict or list", type(values), key,
)
return False
except AttributeError:
return False
return True


def _valid_id(notion_id):
return len(strip_dashes(notion_id)) == 32
42 changes: 10 additions & 32 deletions n2y/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import os
import sys
import json
import logging
import argparse

from n2y import notion
from n2y.database import Database
from n2y.page import Page
from n2y.errors import APIErrorCode, APIResponseError
from n2y.utils import id_from_share_link
from n2y.config import database_config_json_to_dict

logger = None

Expand Down Expand Up @@ -44,8 +45,7 @@ def main(raw_args, access_token):
)
# TODO: Consider making id-property and url-property available when dumping
# to markdown files, and not just when dumping to YAML; if we do this, we
# should probably move some code out of Database.to_yaml into the Page; also
# be sure to update the end-to-end tests
# should probably move some code out of Database.to_yaml into the Page
parser.add_argument(
"--id-property", default='id',
help=(
Expand Down Expand Up @@ -81,8 +81,12 @@ def main(raw_args, access_token):
)
parser.add_argument(
"--database-config", default='{}',
help="A JSON string in the format {database_id: {sorts: {...}, filter: {...}}}. "
"Will get passed to the Notion API directly"
help=(
"A JSON string in the format {database_id: {sorts: {...}, filter: {...}}}. "
"These can be used to filter and sort databases. See "
"https://developers.notion.com/reference/post-database-query-filter and "
"https://developers.notion.com/reference/post-database-query-sort"
)
)

# TODO: Add the ability to dump out a "schema" file that contains the schema
Expand All @@ -101,7 +105,7 @@ def main(raw_args, access_token):
logger.critical('No NOTION_ACCESS_TOKEN environment variable is set')
return 1

object_id = notion.id_from_share_link(args.object_id)
object_id = id_from_share_link(args.object_id)
media_root = args.media_root or args.output

database_config = database_config_json_to_dict(args.database_config)
Expand Down Expand Up @@ -149,32 +153,6 @@ def main(raw_args, access_token):
return 0


def database_config_json_to_dict(config_json):
try:
config = json.loads(config_json)
except json.JSONDecodeError:
return None
if not validate_database_config(config):
return None
return config


def validate_database_config(config):
try:
for database_id, config_values in config.items():
database_id_correct_length = len(database_id) == 32
if not database_id_correct_length:
return False
for key, values in config_values.items():
if key not in ["sorts", "filter"]:
return False
if not isinstance(values, dict) and not isinstance(values, list):
return False
except AttributeError:
return False
return True


def export_database_as_markdown_files(database, options):
os.makedirs(options.output, exist_ok=True)
seen_file_names = set()
Expand Down
14 changes: 3 additions & 11 deletions n2y/notion.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,9 @@ def get_database_notion_pages(self, database_id):
else:
request_data["start_cursor"] = data["next_cursor"]

def _create_database_request_data(self, dashed_database_id):
database_id = strip_dashes(dashed_database_id)
return self.database_config[database_id] if database_id in self.database_config else {}
def _create_database_request_data(self, database_id):
stripped_database_id = strip_dashes(database_id)
return self.database_config.get(stripped_database_id, {})

def get_page(self, page_id):
"""
Expand Down Expand Up @@ -389,11 +389,3 @@ def save_file(self, content_iterator, page, extension):
makedirs(path.dirname(full_filepath), exist_ok=True)
shutil.move(temp_filepath, full_filepath)
return urljoin(self.media_url, relative_filepath)


def id_from_share_link(share_link):
hyphens_removed = share_link.replace("-", "")
if not hyphens_removed.startswith("https://www.notion.so/"):
return hyphens_removed
else:
return hyphens_removed.split("/")[-1].split("?")[0]
8 changes: 8 additions & 0 deletions n2y/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,13 @@ def sanitize_filename(filename):
return s


def id_from_share_link(share_link):
hyphens_removed = share_link.replace("-", "")
johndgiese marked this conversation as resolved.
Show resolved Hide resolved
if not hyphens_removed.startswith("https://www.notion.so/"):
return hyphens_removed
else:
return hyphens_removed.split("/")[-1].split("?")[0]


def strip_dashes(string):
return string.replace("-", "")
18 changes: 13 additions & 5 deletions tests/notion_mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
from n2y.rich_text import mock_notion_annotations as mock_annotations # noqa: F401


def mock_id():
return str(uuid.uuid4())


def mock_id_stripped():
return mock_id()


def mock_user(**kwargs):
return {'object': 'user', 'id': str(uuid.uuid4()), **kwargs}
return {'object': 'user', 'id': mock_id(), **kwargs}


def mock_person_user(name, email):
Expand All @@ -21,7 +29,7 @@ def mock_block(block_type, content, has_children=False, **kwargs):
created_by = mock_user()
created_time = datetime.now().isoformat()
return {
'id': str(uuid.uuid4()),
'id': mock_id(),
'created_time': created_time,
'created_by': created_by,
'last_edited_time': created_time,
Expand Down Expand Up @@ -51,7 +59,7 @@ def mock_file(url):

def mock_property_value(property_value_type, content):
return {
'id': str(uuid.uuid4()),
'id': mock_id(),
'type': property_value_type,
property_value_type: content,
}
Expand All @@ -74,12 +82,12 @@ def mock_rollup_property_value(rollup_type, content):

def mock_select_option(name, **kwargs):
return {
'id': str(uuid.uuid4()),
'id': mock_id(),
'name': name,
'color': 'green',
**kwargs,
}


def mock_relation_value():
return {"id": str(uuid.uuid4)}
return {"id": mock_id()}
43 changes: 43 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from n2y.config import validate_database_config
from tests.notion_mocks import mock_id


def test_validate_database_config_empty():
assert validate_database_config({})


def test_validate_database_config_no_props():
assert validate_database_config({
mock_id(): {},
})


def test_validate_database_config_invalid_id():
invalid_id = mock_id() + 'a'
assert not validate_database_config({
invalid_id: {},
})


def test_validate_database_config_invalid_props():
assert not validate_database_config({
mock_id(): {'invalid': 'thing'},
})


def test_validate_database_config_invalid_value():
assert not validate_database_config({
mock_id(): {'filter': 'invalid'},
})


def test_validate_database_config_valid_dict():
assert validate_database_config({
mock_id(): {'filter': {}},
})


def test_validate_database_config_valid_list():
assert validate_database_config({
mock_id(): {'filter': []},
})
27 changes: 0 additions & 27 deletions tests/test_notion.py

This file was deleted.

28 changes: 27 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime, timezone

from n2y.utils import fromisoformat
from n2y.utils import fromisoformat, id_from_share_link


def test_fromisoformat_datetime():
Expand All @@ -11,3 +11,29 @@ def test_fromisoformat_datetime():
def test_fromisoformat_date():
expected = datetime(2022, 5, 11)
assert fromisoformat('2022-05-11') == expected


def test_database_id_from_share_link_id():
database_id = "90a3f77db3af4ab6a47b6162dacd76681231"
assert id_from_share_link(database_id) == database_id


def test_database_id_from_share_link_id_hyphens():
database_id = "90a3f77db3af4ab6a47b6162dacd76681231"
database_id_hyphens = "90a3f77-db3af4ab6a47b6162-dacd76681231"
assert id_from_share_link(database_id_hyphens) == database_id


def test_database_id_from_share_link_no_hyphens():
database_id = "90a3f77db3af4ab6a47b6162dacd76681231"
view_id = "463a296148dc38f791e7037dda9a8c3f"
share_link = f"https://www.notion.so/ws/{database_id}?v={view_id}"
assert id_from_share_link(share_link) == database_id


def test_database_id_from_share_link_hyphens():
database_id = "90a3f77db3af4ab6a47b6162dacd76681231"
database_id_hyphens = "90a3f77-db3af4ab6a47b6162-dacd76681231"
view_id = "463a296148dc38f791e7037dda9a8c3f"
share_link = f"https://www.notion.so/ws/{database_id_hyphens}?v={view_id}"
assert id_from_share_link(share_link) == database_id