Skip to content

Commit

Permalink
Some type hints and documentation improvements
Browse files Browse the repository at this point in the history
* zim.notebook.index.links
  - comment -> docstring
  - type annotated IndexLink
* zim.notebook.index.pages
  - type annotated PagesView
* zim.notebook.page
  - type annotated Path, HRef, shortest_unique_names
  - improved HRef documentation based on zim-desktop-wiki#1847
  • Loading branch information
introt committed Jan 7, 2022
1 parent 622db3d commit cde5e91
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 82 deletions.
43 changes: 20 additions & 23 deletions zim/notebook/index/links.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@

# Copyright 2009-2017 Jaap Karssenberg <jaap.karssenberg@gmail.com>


'''
Links come in 3 flavors:
1/ HREF_REL_ABSOLUTE - starting from the top level e.g. ":foo"
2/ HREF_REL_FLOATING - relative to the source namespace, or parents, e.g. "foo"
3/ HREF_REL_RELATIVE - below the source page, e.g. "+foo"
If the target page does not exist, a "placeholder" is created for this
page.
Floating links are resolved to existing pages in parent namespaces
therefore they may need to be recalculated when pages of the same name
are created or deleted. This is done by the 'anchorkey' field in the
links table.
To avoid circular dependencies between the existance of placeholder
pages and links, floating links do /not/ resolve to placeholders,
but only to existing links.
(Else we would need to drop all placeholders and
re-calculate all links on every page index to ensure the outcome.)
'''

import logging
import sqlite3
Expand All @@ -22,27 +40,6 @@
LINK_DIR_BACKWARD = 2 #: Constant for backward links
LINK_DIR_BOTH = 3 #: Constant for links in any direction


# Links come in 3 flavors:
# 1/ HREF_REL_ABSOLUTE - starting from the top level e.g. ":foo"
# 2/ HREF_REL_FLOATING - relative to the source namespace, or parents, e.g. "foo"
# 3/ HREF_REL_RELATIVE - below the source page, e.g. "+foo"
#
# If the target page does not exist, a "placeholder" is created for this
# page.
#
# Floating links are resolved to existing pages in parent namespaces
# therefore they may need to be recalculated when pages of the same name
# are created or deleted. This is done by the 'anchorkey' field in the
# links table.
# To avoid circular dependencies between the existance of placeholder
# pages and links, floating links do /not/ resolve to placeholders,
# but only to existing links.
# (Else we would need to drop all placeholders and
# re-calculate all links on every page index to ensure the outcome.)



class IndexLink(object):
'''Class used to represent links between two pages
Expand All @@ -52,7 +49,7 @@ class IndexLink(object):

__slots__ = ('source', 'target')

def __init__(self, source, target):
def __init__(self, source: Path, target: Path):
self.source = source
self.target = target

Expand Down
54 changes: 25 additions & 29 deletions zim/notebook/index/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


from datetime import datetime
from typing import Generator, Optional

import sqlite3
import logging
Expand Down Expand Up @@ -354,7 +355,7 @@ def get_pagename(self, page_id):
raise IndexConsistencyError('No page for page_id "%r"' % page_id)
return PageIndexRecord(row)

def get_page_id(self, pagename):
def get_page_id(self, pagename: Path) -> int:
row = self.db.execute(
'SELECT id FROM pages WHERE name=?', (pagename.name,)
).fetchone()
Expand Down Expand Up @@ -516,11 +517,11 @@ def walk_bottomup(self, parent_id):
class PagesView(IndexView):
'''Index view that exposes the "pages" table in the index'''

def __init__(self, db):
def __init__(self, db: sqlite3.Connection):
IndexView.__init__(self, db)
self._pages = PagesViewInternal(db)

def lookup_by_pagename(self, pagename):
def lookup_by_pagename(self, pagename: Path) -> PageIndexRecord:
r = self.db.execute(
'SELECT * FROM pages WHERE name=?', (pagename.name,)
).fetchone()
Expand All @@ -529,9 +530,8 @@ def lookup_by_pagename(self, pagename):
else:
return PageIndexRecord(r)

def list_pages(self, path=None):
def list_pages(self, path: Optional[Path] = None) -> Generator[PageIndexRecord, None, None]:
'''Generator for child pages of C{path}
@param path: a L{Path} object
@returns: yields L{Path} objects for children of C{path}
@raises IndexNotFoundError: if C{path} is not found in the index
'''
Expand All @@ -541,24 +541,25 @@ def list_pages(self, path=None):
page_id = self._pages.get_page_id(path) # can raise
return self._list_pages(page_id)

def _list_pages(self, page_id):
def _list_pages(self, page_id: int):
for row in self.db.execute(
'SELECT * FROM pages WHERE parent=? ORDER BY sortkey, name',
(page_id,)
):
yield PageIndexRecord(row)

def n_list_pages(self, path=None):
def n_list_pages(self, path: Optional[Path] = None) -> int:
'''@returns: number of child pages of C{path}.
@param path: optional, defaults to root path
'''
page_id = self._pages.get_page_id(path or ROOT_PATH)
c, = self.db.execute(
'SELECT COUNT(*) FROM pages WHERE parent=?', (page_id,)
).fetchone()
return c

def match_pages(self, path, text, limit=10):
def match_pages(self, path: Path, text: str, limit: int = 10) -> Generator[PageIndexRecord, None, None]:
'''Generator for child pages of C{path} that match C{text} in their name
@param path: a L{Path} object
@param text: a string
@param limit: max number of results
@returns: yields L{Path} objects for children of C{path}
@raises IndexNotFoundError: if C{path} is not found in the index
Expand All @@ -569,7 +570,7 @@ def match_pages(self, path, text, limit=10):
page_id = self._pages.get_page_id(path) # can raise
return self._match_pages(page_id, text, limit)

def _match_pages(self, page_id, text, limit):
def _match_pages(self, page_id: int, text: str, limit: int):
# The LIKE keyword does not handle unicode case-insensitivity
# therefore we need python lower() to do the job
for row in self.db.execute(
Expand All @@ -578,15 +579,15 @@ def _match_pages(self, page_id, text, limit):
):
yield PageIndexRecord(row)

def match_all_pages(self, text, limit=10):
def match_all_pages(self, text: str, limit: int = 10) -> Generator[PageIndexRecord, None, None]:
'''Like C{match_pages()} except not limited a specific namespace'''
for row in self.db.execute(
'SELECT * FROM pages WHERE lowerbasename LIKE ? ORDER BY length(name), sortkey, name LIMIT ?',
("%%%s%%" % text.lower(), limit)
):
yield PageIndexRecord(row)

def walk(self, path=None):
def walk(self, path: Optional[Path] = None) -> Generator[PageIndexRecord, None, None]:
'''Generator function to yield all pages in the index, depth
first
Expand All @@ -601,16 +602,16 @@ def walk(self, path=None):
page_id = self._pages.get_page_id(path) if path else ROOT_ID # can raise
return self._pages.walk(page_id)

def walk_bottomup(self, path=None):
def walk_bottomup(self, path: Optional[Path] = None) -> Generator[PageIndexRecord, None, None]:
page_id = self._pages.get_page_id(path) if path else ROOT_ID # can raise
return self._pages.walk_bottomup(page_id)

def n_all_pages(self):
'''Returns to total number of pages in the index'''
def n_all_pages(self) -> int:
'''@returns: total number of pages in the index'''
c, = self.db.execute('SELECT COUNT(*) FROM pages').fetchone()
return c - 1 # don't count ROOT

def get_has_previous_has_next(self, path):
def get_has_previous_has_next(self, path: Path) -> bool:
if path.isroot:
raise ValueError('Can\'t use root')

Expand All @@ -630,10 +631,9 @@ def get_has_previous_has_next(self, path):

return not is_first, not is_last

def get_previous(self, path):
def get_previous(self, path: Path) -> Optional[Path]:
'''Get the previous path in the index, in the same order that
L{walk()} will yield them
@param path: a L{Path} object
@returns: a L{Path} object or C{None} if {path} is the first page in
the index
'''
Expand Down Expand Up @@ -672,10 +672,9 @@ def get_previous(self, path):
else:
return PageIndexRecord(r)

def get_next(self, path):
def get_next(self, path: Path) -> Optional[Path]:
'''Get the next path in the index, in the same order that
L{walk()} will yield them
@param path: a L{Path} object
@returns: a L{Path} object or C{None} if C{path} is the last page in
the index
'''
Expand Down Expand Up @@ -720,7 +719,7 @@ def get_next(self, path):
if r is None:
raise IndexConsistencyError('Missing parent')

def lookup_from_user_input(self, name, reference=None):
def lookup_from_user_input(self, name: str, reference: Path = None) -> Path:
'''Lookup a pagename based on user input
@param name: the user input as string
@param reference: a L{Path} in case relative links are supported as
Expand All @@ -743,7 +742,7 @@ def lookup_from_user_input(self, name, reference=None):
source, href, ignore_link_placeholders=False)
return pagename

def resolve_link(self, source, href):
def resolve_link(self, source: Path, href: HRef) -> Path:
'''Find the end point of a link
Depending on the link type (absolute, relative, or floating),
this method first determines the starting point of the link
Expand All @@ -758,11 +757,8 @@ def resolve_link(self, source, href):
id, pagename = self._pages.resolve_link(source, href)
return pagename

def create_link(self, source, target):
def create_link(self, source: Path, target: Path) -> HRef:
'''Determine best way to represent a link between two pages
@param source: a L{Path} object
@param target: a L{Path} object
@returns: a L{HRef} object
'''
if target == source: # weird edge case ..
return HRef(HREF_REL_FLOATING, target.basename)
Expand All @@ -772,7 +768,7 @@ def create_link(self, source, target):
href = self._find_floating_link(source, target)
return href or HRef(HREF_REL_ABSOLUTE, target.name)

def _find_floating_link(self, source, target):
def _find_floating_link(self, source: Path, target: Path) -> Optional[Path]:
# Relative links only resolve for pages that have a common parent
# with the source page. So we start finding the common parents and
# if that does not resolve (e.g. because same name also occurs on a
Expand Down Expand Up @@ -807,7 +803,7 @@ def try_link(names):
else:
return None # no floating link possible

def list_recent_changes(self, limit=None, offset=None):
def list_recent_changes(self, limit: Optional[int] = None, offset: Optional[int] = None) -> Generator[PageIndexRecord, None, None]:
assert not (offset and not limit), "Can't use offset without limit"
if limit:
selection = ' LIMIT %i OFFSET %i' % (limit, offset or 0)
Expand Down

0 comments on commit cde5e91

Please sign in to comment.