diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 47842bd2..17b271a0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,42 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.4.0
- hooks:
- # - id: trailing-whitespace
- # - id: end-of-file-fixer
- # - id: check-yaml
- - id: check-added-large-files
-- repo: https://github.com/PyCQA/pylint/
- rev: '2.6'
- hooks:
- - id: pylint
- name: pylint
- entry: pylint
- language: system
- types: [python]
- args: ['--rcfile', 'tests/.pylintrc']
- # exclude: tests/functional/|tests/input|tests/extensions/data|tests/regrtest_data/|tests/data/|doc/
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
+ # - id: trailing-whitespace
+ # - id: end-of-file-fixer
+ # - id: check-yaml
+ - id: check-added-large-files
+- repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+ name: isort (python)
+ args: [--profile, black, --filter-files]
+- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
+ rev: v2.2.0
+ hooks:
+ - id: pretty-format-toml
+ args: [--autofix]
+ - id: pretty-format-yaml
+ args: [--autofix]
+ exclude: .copier-answers.yml
+ # based on
+ # https://gitlab.com/smop/pre-commit-hooks/-/blob/master/.pre-commit-hooks.yaml
+- repo: https://github.com/akaihola/darker
+ rev: 1.3.2
+ hooks:
+ - id: darker
+- repo: local
+ hooks:
+ - id: pylint
+ name: pylint
+ entry: pylint
+ language: system
+ types: [python]
+ args: [-rn, -sn, --rcfile=tests/.pylintrc]
+ # "-rn", # Only display messages
+ # "-sn", # Don't display the score
+ # based on
+ # https://pylint.pycqa.org/en/latest/user_guide/pre-commit-integration.html
diff --git a/bukuserver/__main__.py b/bukuserver/__main__.py
index 61360bba..1981062e 100644
--- a/bukuserver/__main__.py
+++ b/bukuserver/__main__.py
@@ -1,4 +1,7 @@
-from . import server
+try:
+ from . import server
+except ImportError:
+ from bukuserver import server
if __name__ == '__main__':
diff --git a/bukuserver/forms.py b/bukuserver/forms.py
index 51a197f4..9d8dd3c5 100644
--- a/bukuserver/forms.py
+++ b/bukuserver/forms.py
@@ -1,7 +1,7 @@
"""Forms module."""
# pylint: disable=too-few-public-methods, missing-docstring
-from flask_wtf import FlaskForm
import wtforms
+from flask_wtf import FlaskForm
class SearchBookmarksForm(FlaskForm):
@@ -16,8 +16,7 @@ class HomeForm(SearchBookmarksForm):
class BookmarkForm(FlaskForm):
- url = wtforms.StringField(
- validators=[wtforms.validators.DataRequired(), wtforms.validators.URL(require_tld=False)])
+ url = wtforms.StringField(validators=[wtforms.validators.DataRequired()])
title = wtforms.StringField()
tags = wtforms.StringField()
description = wtforms.TextAreaField()
diff --git a/bukuserver/requirements.txt b/bukuserver/requirements.txt
index b13e5c86..62e43310 100644
--- a/bukuserver/requirements.txt
+++ b/bukuserver/requirements.txt
@@ -8,7 +8,7 @@ Flask-Admin>=1.5.1
Flask-API>=0.6.9
Flask-Bootstrap>=3.3.7.1
flask-paginate>=0.5.1
-flask-reverse-proxy-fix>=0.2.1
+flask-reverse-proxy-fix @ https://github.com/rachmadaniHaryono/flask-reverse-proxy-fix/archive/refs/tags/v0.2.2rc1.zip
Flask-WTF>=0.14.2
Flask>=1.0.2,<2.0
idna>=2.5
diff --git a/bukuserver/server.py b/bukuserver/server.py
index c800d871..9cf9bef9 100644
--- a/bukuserver/server.py
+++ b/bukuserver/server.py
@@ -1,28 +1,32 @@
#!/usr/bin/env python
# pylint: disable=wrong-import-order, ungrouped-imports
"""Server module."""
+import collections
+import os
+import sys
+import typing as T
from typing import Any, Dict, Union # NOQA; type: ignore
from unittest import mock
from urllib.parse import urlparse
-import os
-import sys
-from buku import BukuDb, __version__, network_handler
from flask.cli import FlaskGroup
from flask.views import MethodView
from flask_admin import Admin
-from flask_api import exceptions, FlaskAPI, status
+from flask_api import FlaskAPI, exceptions, status
from flask_bootstrap import Bootstrap
from flask_paginate import Pagination, get_page_parameter, get_per_page_parameter
+
+import buku
+from buku import BukuDb, __version__, network_handler
+
try:
from flask_reverse_proxy_fix.middleware import ReverseProxyPrefixFix
except ImportError:
ReverseProxyPrefixFix = None
-from markupsafe import Markup
import click
import flask
-from flask import ( # type: ignore
- __version__ as flask_version,
+from flask import __version__ as flask_version # type: ignore
+from flask import (
abort,
current_app,
flash,
@@ -32,11 +36,12 @@
request,
url_for,
)
+from markupsafe import Markup
try:
- from . import response, forms, views
+ from . import forms, response, views
except ImportError:
- from bukuserver import response, forms, views
+ from bukuserver import forms, response, views
STATISTIC_DATA = None
@@ -301,19 +306,56 @@ def shell_context():
return app
+def search_tag(
+ db: BukuDb, stag: T.Optional[str] = None, limit: T.Optional[int] = None
+) -> T.Tuple[T.List[str], T.Dict[str, int]]:
+ """search tag.
+
+ db:
+ buku db instance
+ stag:
+ search tag
+ limit:
+ positive integer limit
+
+ Returns
+ -------
+ tuple
+ list of unique tags sorted alphabetically and dictionary of tag and its usage count
+
+ Raises
+ ------
+ ValueError
+ if limit is not positive
+ """
+ if limit is not None and limit < 1:
+ raise ValueError("limit must be positive")
+ tags: T.Set[str] = set()
+ counter = collections.Counter()
+ query_list = ["SELECT DISTINCT tags , COUNT(tags) FROM bookmarks"]
+ if stag:
+ query_list.append("where tags LIKE :search_tag")
+ query_list.append("GROUP BY tags")
+ row: T.Tuple[str, int]
+ for row in db.cur.execute(" ".join(query_list), {"search_tag": f"%{stag}%"}):
+ for tag in row[0].strip(buku.DELIM).split(buku.DELIM):
+ if not tag:
+ continue
+ tags.add(tag)
+ counter[tag] += row[1]
+ return list(sorted(tags)), dict(counter.most_common(limit))
+
+
class ApiTagView(MethodView):
- def get(self, tag: Union[str, None]):
+ def get(self, tag: T.Optional[str]):
bukudb = get_bukudb()
if tag is None:
- tags = bukudb.get_tag_all()
- result = {'tags': tags[0]}
- return result
- tags = bukudb.get_tag_all()
+ return {"tags": search_tag(db=bukudb, limit=5)[0]}
+ tags = search_tag(db=bukudb, stag=tag)
if tag not in tags[1]:
raise exceptions.NotFound()
- res = dict(name=tag, usage_count=tags[1][tag])
- return res
+ return dict(name=tag, usage_count=tags[1][tag])
def put(self, tag: str):
bukudb = get_bukudb()
@@ -554,7 +596,7 @@ def delete(self):
return res
-class BookmarkletView(MethodView):
+class BookmarkletView(MethodView): # pylint: disable=too-few-public-methods
def get(self):
url = request.args.get('url')
title = request.args.get('title')
diff --git a/bukuserver/views.py b/bukuserver/views.py
index 736aca4e..30864cac 100644
--- a/bukuserver/views.py
+++ b/bukuserver/views.py
@@ -1,61 +1,73 @@
"""views module."""
+import functools
+import itertools
+import logging
from argparse import Namespace
from collections import Counter
from types import SimpleNamespace
from typing import Any, List, Optional, Tuple
from urllib.parse import urlparse
-import itertools
-import logging
+import arrow
+import wtforms
from flask import current_app, flash, redirect, request, url_for
from flask_admin.babel import gettext
from flask_admin.base import AdminIndexView, BaseView, expose
from flask_admin.model import BaseModelView
from flask_wtf import FlaskForm
-from jinja2 import Markup
-import arrow
-import wtforms
+from markupsafe import Markup
+
+import buku
try:
- from . import forms, filters as bs_filters
+ from . import filters as bs_filters
+ from . import forms
from .filters import BookmarkField, FilterType
except ImportError:
- from bukuserver import forms, filters as bs_filters
- from bukuserver.filters import BookmarkField, FilterType
+ from bukuserver import filters as bs_filters # type: ignore
+ from bukuserver import forms
+ from bukuserver.filters import BookmarkField, FilterType # type: ignore
STATISTIC_DATA = None
-DEFAULT_URL_RENDER_MODE = 'full'
+DEFAULT_URL_RENDER_MODE = "full"
DEFAULT_PER_PAGE = 10
LOG = logging.getLogger("bukuserver.views")
class CustomAdminIndexView(AdminIndexView):
-
- @expose('/')
+ @expose("/")
def index(self):
- return self.render('bukuserver/home.html', form=forms.HomeForm())
-
- @expose('/', methods=['POST',])
+ return self.render("bukuserver/home.html", form=forms.HomeForm())
+
+ @expose(
+ "/",
+ methods=[
+ "POST",
+ ],
+ )
def search(self):
"redirect to bookmark search"
form = forms.HomeForm()
bbm_filter = bs_filters.BookmarkBukuFilter(
- all_keywords=False, deep=form.deep.data, regex=form.regex.data)
+ all_keywords=False, deep=form.deep.data, regex=form.regex.data
+ )
op_text = bbm_filter.operation()
values_combi = sorted(itertools.product([True, False], repeat=3))
+ choosen_idx = None
for idx, (all_keywords, deep, regex) in enumerate(values_combi):
if deep == form.deep.data and regex == form.regex.data and not all_keywords:
choosen_idx = idx
- url_op_text = op_text.replace(', ', '_').replace(' ', ' ').replace(' ', '_')
- key = ''.join(['flt', str(choosen_idx), '_buku_', url_op_text])
- kwargs = {key: form.keyword.data}
- url = url_for('bookmark.index_view', **kwargs)
+ url_op_text = op_text.replace(", ", "_").replace(" ", " ").replace(" ", "_")
+ kwargs = {}
+ if choosen_idx:
+ key = "".join(["flt", str(choosen_idx), "_buku_", url_op_text])
+ kwargs = {key: form.keyword.data}
+ url = url_for("bookmark.index_view", **kwargs)
return redirect(url)
class CustomBukuDbModel: # pylint: disable=too-few-public-methods
-
def __init__(self, bukudb_inst, name):
self.bukudb = bukudb_inst
self.name = name
@@ -66,120 +78,130 @@ def __name__(self):
class BookmarkModelView(BaseModelView):
-
def _apply_filters(self, models, filters):
- for idx, flt_name, value in filters:
- flt = self._filters[idx]
- clean_value = flt.clean(value)
- models = list(flt.apply(models, clean_value))
+ for idx, _, value in filters:
+ if self._filters:
+ flt = self._filters[idx]
+ clean_value = flt.clean(value)
+ models = list(flt.apply(models, clean_value))
return models
def _create_ajax_loader(self, name, options):
pass
- def _list_entry(
- self, context: Any, model: Namespace, name: str) -> Markup:
+ def _list_entry(self, context: Any, model: Namespace, name: str) -> Markup:
+ LOG.debug("context: %s, name: %s", context, name)
parsed_url = urlparse(model.url)
- netloc, scheme = parsed_url.netloc, parsed_url.scheme
- is_scheme_valid = scheme in ('http', 'https')
+ netloc = parsed_url.netloc
tag_text = []
- tag_tmpl = '{0}'
- for tag in model.tags.split(','):
- if tag:
- tag_text.append(tag_tmpl.format(tag, url_for(
- 'bookmark.index_view', flt2_tags_contain=tag)))
- if not netloc:
- return Markup("""\
- {0.title}
{2}
{1}{0.description}
- """.format(
- model, ''.join(tag_text), Markup.escape(model.url)
- ))
- res = ''
- if not current_app.config.get('BUKUSERVER_DISABLE_FAVICON', False):
- netloc_tmpl = ' '
- res = netloc_tmpl.format(
- 'http://www.google.com/s2/favicons?domain=', netloc)
- title = model.title if model.title else '<EMPTY TITLE>'
- open_in_new_tab = current_app.config.get('BUKUSERVER_OPEN_IN_NEW_TAB', False)
- if is_scheme_valid and open_in_new_tab:
- res += '{1}'.format(model, title)
- elif is_scheme_valid and not open_in_new_tab:
- res += '{1}'.format(model, title)
- else:
- res += title
- if self.url_render_mode == 'netloc':
- res += ' ({0})'.format(
- netloc,
- url_for('bookmark.index_view', flt2_url_netloc_match=netloc)
+ br_tag = "
"
+ get_index_view_url = functools.partial(url_for, "bookmark.index_view")
+ for tag in filter(None, model.tags.split(",")):
+ tag_text.append(
+ f'{tag}'
+ )
+ tag_text_markup = "".join(tag_text)
+ if not netloc and not parsed_url.scheme:
+ escaped_url = Markup.escape(model.url)
+ return Markup(
+ f"""{model.title}{br_tag}{escaped_url}{br_tag}{tag_text_markup}{model.description}"""
+ )
+ res = []
+ if not current_app.config.get("BUKUSERVER_DISABLE_FAVICON", False) and netloc:
+ res.append(
+ f''
)
- res += '
'
- if not is_scheme_valid:
- res += model.url
- elif self.url_render_mode is None or self.url_render_mode == 'full':
- res += '{0.url}'.format(model)
- res += '
'
- if self.url_render_mode != 'netloc':
- res += tag_tmpl.format(
- 'netloc:{}'.format(netloc),
- url_for('bookmark.index_view', flt2_url_netloc_match=netloc)
+ title = model.title if model.title else "<EMPTY TITLE>"
+ open_in_new_tab = current_app.config.get("BUKUSERVER_OPEN_IN_NEW_TAB", False)
+ url_for_index_view_netloc = None
+ if netloc:
+ url_for_index_view_netloc = get_index_view_url(flt2_url_netloc_match=netloc)
+ if parsed_url.scheme and not open_in_new_tab:
+ target = 'target="_blank"' if open_in_new_tab else ""
+ res.append(f'{title}')
+ else:
+ res.append(title)
+ if self.url_render_mode == "netloc" and url_for_index_view_netloc:
+ res.append(f'({netloc})')
+ res.append(br_tag)
+ if not parsed_url.scheme:
+ res.extend((model.url, br_tag))
+ elif self.url_render_mode is None or self.url_render_mode == "full":
+ res.extend((f'{model.url}', br_tag))
+ if self.url_render_mode != "netloc" and url_for_index_view_netloc:
+ res.append(
+ f'netloc:{netloc}'
)
- res += ''.join(tag_text)
+ if tag_text_markup:
+ res.append("".join(tag_text))
description = model.description
if description:
- res += '
'
- res += description.replace('\n', '
')
- return Markup(res)
+ res.extend((br_tag, description.replace("\n", br_tag)))
+ return Markup("".join(res))
can_set_page_size = True
can_view_details = True
- column_filters = ['buku', 'id', 'url', 'title', 'tags']
- column_formatters = {'Entry': _list_entry,}
- column_list = ['Entry']
+ column_filters = ["buku", "id", "url", "title", "tags"]
+ column_formatters = {
+ "Entry": _list_entry,
+ }
+ column_list = ["Entry"]
create_modal = True
- create_modal_template = 'bukuserver/bookmark_create_modal.html'
- create_template = 'bukuserver/bookmark_create.html'
+ create_modal_template = "bukuserver/bookmark_create_modal.html"
+ create_template = "bukuserver/bookmark_create.html"
details_modal = True
edit_modal = True
- edit_modal_template = 'bukuserver/bookmark_edit_modal.html'
- edit_template = 'bukuserver/bookmark_edit.html'
+ edit_modal_template = "bukuserver/bookmark_edit_modal.html"
+ edit_template = "bukuserver/bookmark_edit.html"
named_filter_urls = True
def __init__(self, *args, **kwargs):
- self.bukudb = args[0]
- custom_model = CustomBukuDbModel(args[0], 'bookmark')
- args = [custom_model, ] + list(args[1:])
- self.page_size = kwargs.pop('page_size', DEFAULT_PER_PAGE)
- self.url_render_mode = kwargs.pop('url_render_mode', DEFAULT_URL_RENDER_MODE)
+ self.bukudb: buku.BukuDb = args[0]
+ custom_model = CustomBukuDbModel(args[0], "bookmark")
+ args = [
+ custom_model,
+ ] + list(args[1:])
+ self.page_size = kwargs.pop("page_size", DEFAULT_PER_PAGE)
+ self.url_render_mode = kwargs.pop("url_render_mode", DEFAULT_URL_RENDER_MODE)
super().__init__(*args, **kwargs)
def create_form(self, obj=None):
form = super().create_form(obj)
args = request.args
- if 'url' in args.keys() and not args.get("url").startswith('/bookmark/'):
- form.url.data = args.get("url")
- if 'title' in args.keys():
+ args_url = args.get("url")
+ if args_url and not args_url.startswith("/bookmark/"):
+ form.url.data = args_url
+ if "title" in args.keys():
form.title.data = args.get("title")
- if 'description' in args.keys():
+ if "description" in args.keys():
form.description.data = args.get("description")
return form
def create_model(self, form):
try:
- model = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
+ model = SimpleNamespace(
+ id=None, url=None, title=None, tags=None, description=None
+ )
form.populate_obj(model)
- vars(model).pop('id')
+ vars(model).pop("id")
self._on_model_change(form, model, True)
- tags_in = model.tags
- if not tags_in.startswith(','):
- tags_in = ',{}'.format(tags_in)
- if not tags_in.endswith(','):
- tags_in = '{},'.format(tags_in)
- self.model.bukudb.add_rec(
- url=model.url, title_in=model.title, tags_in=tags_in, desc=model.description)
+ if not model.url.strip():
+ raise ValueError(f"url invalid: {model.url}")
+ kwargs = {"url": model.url}
+ if model.tags.strip():
+ kwargs["tags_in"] = buku.parse_tags([model.tags])
+ for key, item in (("title_in", model.title), ("desc", model.description)):
+ if item.strip():
+ kwargs[key] = item
+ self.model.bukudb.add_rec(**kwargs)
except Exception as ex:
if not self.handle_view_exception(ex):
- flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
- LOG.exception('Failed to create record.')
+ msg = "Failed to create record."
+ flash(
+ gettext("%(msg)s %(error)s", msg=msg, error=str(ex)),
+ "error",
+ )
+ LOG.exception(msg)
return False
else:
self.after_model_change(form, model, True)
@@ -191,32 +213,43 @@ def delete_model(self, model):
res = self.bukudb.delete_rec(model.id)
except Exception as ex:
if not self.handle_view_exception(ex):
- flash(gettext('Failed to delete record. %(error)s', error=str(ex)), 'error')
- LOG.exception('Failed to delete record.')
+ msg = "Failed to delete record."
+ flash(
+ gettext("%(msg)s %(error)s", msg=msg, error=str(ex)),
+ "error",
+ )
+ LOG.exception(msg)
return False
else:
self.after_model_delete(model)
return res
- def get_list(self, page, sort_field, sort_desc, search, filters, page_size=None):
+ def get_list(self, page, sort_field, sort_desc, _, filters, page_size=None):
bukudb = self.bukudb
- contain_buku_search = any(x[1] == 'buku' for x in filters)
+ contain_buku_search = any(x[1] == "buku" for x in filters)
if contain_buku_search:
mode_id = [x[0] for x in filters]
if len(list(set(mode_id))) > 1:
- flash(gettext('Invalid search mode combination'), 'error')
+ flash(gettext("Invalid search mode combination"), "error")
return 0, []
keywords = [x[2] for x in filters]
+ flt = None
for idx, flt_name, value in filters:
- if flt_name == 'buku':
+ if flt_name == "buku" and self._filters:
flt = self._filters[idx]
- bookmarks = bukudb.searchdb(
- keywords, all_keywords=flt.all_keywords, deep=flt.deep, regex=flt.regex)
+ kwargs = (
+ dict(all_keywords=flt.all_keywords, deep=flt.deep, regex=flt.regex)
+ if flt
+ else {}
+ )
+ bookmarks = bukudb.searchdb(keywords, **kwargs)
else:
bookmarks = bukudb.get_rec_all()
bookmarks = self._apply_filters(bookmarks, filters)
if sort_field:
- key_idx = [x.value for x in BookmarkField if x.name.lower() == sort_field][0]
+ key_idx = [x.value for x in BookmarkField if x.name.lower() == sort_field][
+ 0
+ ]
bookmarks = sorted(bookmarks, key=lambda x: x[key_idx], reverse=sort_desc)
count = len(bookmarks)
if page_size and bookmarks:
@@ -226,13 +259,15 @@ def get_list(self, page, sort_field, sort_desc, search, filters, page_size=None)
bookmarks = []
data = []
for bookmark in bookmarks:
- bm_sns = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
+ bm_sns = SimpleNamespace(
+ id=None, url=None, title=None, tags=None, description=None
+ )
for field in list(BookmarkField):
if field == BookmarkField.TAGS:
value = bookmark[field.value]
- if value.startswith(','):
+ if value.startswith(","):
value = value[1:]
- if value.endswith(','):
+ if value.endswith(","):
value = value[:-1]
setattr(bm_sns, field.name.lower(), value)
else:
@@ -242,13 +277,15 @@ def get_list(self, page, sort_field, sort_desc, search, filters, page_size=None)
def get_one(self, id):
bookmark = self.model.bukudb.get_rec_by_id(id)
- bm_sns = SimpleNamespace(id=None, url=None, title=None, tags=None, description=None)
+ bm_sns = SimpleNamespace(
+ id=None, url=None, title=None, tags=None, description=None
+ )
for field in list(BookmarkField):
- if field == BookmarkField.TAGS and bookmark[field.value].startswith(','):
+ if field == BookmarkField.TAGS and bookmark[field.value].startswith(","):
value = bookmark[field.value]
- if value.startswith(','):
+ if value.startswith(","):
value = value[1:]
- if value.endswith(','):
+ if value.endswith(","):
value = value[:-1]
setattr(bm_sns, field.name.lower(), value)
else:
@@ -265,66 +302,102 @@ def scaffold_list_form(self, widget=None, validators=None):
pass
def scaffold_sortable_columns(self):
- return {x:x for x in self.scaffold_list_columns()}
+ return {x: x for x in self.scaffold_list_columns()}
def scaffold_filters(self, name):
res = []
- if name == 'buku':
+ if name == "buku":
values_combi = sorted(itertools.product([True, False], repeat=3))
for all_keywords, deep, regex in values_combi:
res.append(
- bs_filters.BookmarkBukuFilter(all_keywords=all_keywords, deep=deep, regex=regex)
+ bs_filters.BookmarkBukuFilter(
+ all_keywords=all_keywords, deep=deep, regex=regex
+ )
)
elif name == BookmarkField.ID.name.lower():
- res.extend([
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.GREATER),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.SMALLER),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.TOP_X),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.BOTTOM_X),
- ])
+ res.extend(
+ [
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_EQUAL
+ ),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_IN_LIST
+ ),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.GREATER),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.SMALLER),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.TOP_X),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.BOTTOM_X
+ ),
+ ]
+ )
elif name == BookmarkField.URL.name.lower():
+
def netloc_match_func(query, value, index):
return filter(lambda x: urlparse(x[index]).netloc == value, query)
- res.extend([
- bs_filters.BookmarkBaseFilter(name, 'netloc match', netloc_match_func),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
- ])
+ res.extend(
+ [
+ bs_filters.BookmarkBaseFilter(
+ name, "netloc match", netloc_match_func
+ ),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_EQUAL
+ ),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_IN_LIST
+ ),
+ ]
+ )
elif name == BookmarkField.TITLE.name.lower():
- res.extend([
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
- bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
- ])
+ res.extend(
+ [
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.EQUAL),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_EQUAL
+ ),
+ bs_filters.BookmarkBaseFilter(name, filter_type=FilterType.IN_LIST),
+ bs_filters.BookmarkBaseFilter(
+ name, filter_type=FilterType.NOT_IN_LIST
+ ),
+ ]
+ )
elif name == BookmarkField.TAGS.name.lower():
+
def tags_contain_func(query, value, index):
for item in query:
- for tag in item[index].split(','):
+ for tag in item[index].split(","):
if tag and tag == value:
yield item
def tags_not_contain_func(query, value, index):
for item in query:
- for tag in item[index].split(','):
+ for tag in item[index].split(","):
if tag and tag != value:
yield item
- res.extend([
- bs_filters.BookmarkBaseFilter(name, 'contain', tags_contain_func),
- bs_filters.BookmarkBaseFilter(name, 'not contain', tags_not_contain_func),
- bs_filters.BookmarkTagNumberEqualFilter(name, 'number equal'),
- bs_filters.BookmarkTagNumberNotEqualFilter(name, 'number not equal'),
- bs_filters.BookmarkTagNumberGreaterFilter(name, 'number greater than'),
- bs_filters.BookmarkTagNumberSmallerFilter(name, 'number smaller than'),
- ])
+ res.extend(
+ [
+ bs_filters.BookmarkBaseFilter(name, "contain", tags_contain_func),
+ bs_filters.BookmarkBaseFilter(
+ name, "not contain", tags_not_contain_func
+ ),
+ bs_filters.BookmarkTagNumberEqualFilter(name, "number equal"),
+ bs_filters.BookmarkTagNumberNotEqualFilter(
+ name, "number not equal"
+ ),
+ bs_filters.BookmarkTagNumberGreaterFilter(
+ name, "number greater than"
+ ),
+ bs_filters.BookmarkTagNumberSmallerFilter(
+ name, "number smaller than"
+ ),
+ ]
+ )
elif name in self.scaffold_list_columns():
pass
else:
@@ -335,25 +408,26 @@ def scaffold_form(self):
cls = forms.BookmarkForm
return cls
- def update_model(self, form, model):
+ def update_model(self, form: forms.BookmarkForm, model: Namespace):
res = False
try:
- original_tags = model.tags
form.populate_obj(model)
self._on_model_change(form, model, False)
- self.bukudb.delete_tag_at_index(model.id, original_tags)
- tags_in = model.tags
- if not tags_in.startswith(','):
- tags_in = ',{}'.format(tags_in)
- if not tags_in.endswith(','):
- tags_in = '{},'.format(tags_in)
res = self.bukudb.update_rec(
- model.id, url=model.url, title_in=model.title, tags_in=tags_in,
- desc=model.description)
+ model.id,
+ url=model.url,
+ title_in=model.title,
+ tags_in=buku.parse_tags([model.tags]),
+ desc=model.description,
+ )
except Exception as ex:
if not self.handle_view_exception(ex):
- flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
- LOG.exception('Failed to update record.')
+ msg = "Failed to update record."
+ flash(
+ gettext("%(msg)s %(error)s", msg=msg, error=str(ex)),
+ "error",
+ )
+ LOG.exception(msg)
return False
else:
self.after_model_change(form, model, False)
@@ -361,45 +435,53 @@ def update_model(self, form, model):
class TagModelView(BaseModelView):
-
def _create_ajax_loader(self, name, options):
pass
def _apply_filters(self, models, filters):
- for idx, flt_name, value in filters:
- flt = self._filters[idx]
- clean_value = flt.clean(value)
- models = list(flt.apply(models, clean_value))
+ for idx, _, value in filters:
+ if self._filters:
+ flt = self._filters[idx]
+ clean_value = flt.clean(value)
+ models = list(flt.apply(models, clean_value))
return models
- def _name_formatter(self, context, model, name):
+ def _name_formatter(self, _, model, name):
data = getattr(model, name)
if not data:
- return Markup('{}'.format(
- url_for('bookmark.index_view', flt2_tags_number_equal=0),
- '<EMPTY TAG>'
- ))
- return Markup('{}'.format(
- url_for('bookmark.index_view', flt1_tags_contain=data), data
- ))
+ return Markup(
+ '{}'.format(
+ url_for("bookmark.index_view", flt2_tags_number_equal=0),
+ "<EMPTY TAG>",
+ )
+ )
+ return Markup(
+ '{}'.format(
+ url_for("bookmark.index_view", flt1_tags_contain=data), data
+ )
+ )
can_create = False
can_set_page_size = True
- column_filters = ['name', 'usage_count']
- column_formatters = {'name': _name_formatter,}
+ column_filters = ["name", "usage_count"]
+ column_formatters = {
+ "name": _name_formatter,
+ }
def __init__(self, *args, **kwargs):
self.bukudb = args[0]
- custom_model = CustomBukuDbModel(args[0], 'tag')
- args = [custom_model, ] + list(args[1:])
- self.page_size = kwargs.pop('page_size', DEFAULT_PER_PAGE)
+ custom_model = CustomBukuDbModel(args[0], "tag")
+ args = [
+ custom_model,
+ ] + list(args[1:])
+ self.page_size = kwargs.pop("page_size", DEFAULT_PER_PAGE)
super().__init__(*args, **kwargs)
def scaffold_list_columns(self):
- return ['name', 'usage_count']
+ return ["name", "usage_count"]
def scaffold_sortable_columns(self):
- return {x:x for x in self.scaffold_list_columns()}
+ return {x: x for x in self.scaffold_list_columns()}
def scaffold_form(self):
class CustomForm(FlaskForm): # pylint: disable=too-few-public-methods
@@ -411,21 +493,28 @@ def scaffold_list_form(self, widget=None, validators=None):
pass
def get_list(
- self,
- page: int,
- sort_field: str,
- sort_desc: bool,
- search: Optional[Any],
- filters: List[Tuple[int, str, str]],
- page_size: int = None) -> Tuple[int, List[SimpleNamespace]]:
+ self,
+ page: int,
+ sort_field: str,
+ sort_desc: bool,
+ search: Optional[Any],
+ filters: List[Tuple[int, str, str]],
+ page_size: int = None,
+ ) -> Tuple[int, List[SimpleNamespace]]:
+ logging.debug("search: %s", search)
bukudb = self.bukudb
tags = bukudb.get_tag_all()[1]
tags = sorted(tags.items())
tags = self._apply_filters(tags, filters)
- sort_field_dict = {'usage_count': 1, 'name': 0}
+ sort_field_dict = {"usage_count": 1, "name": 0}
if sort_field in sort_field_dict:
- tags = list(sorted(
- tags, key=lambda x: x[sort_field_dict[sort_field]], reverse=sort_desc))
+ tags = list(
+ sorted(
+ tags,
+ key=lambda x: x[sort_field_dict[sort_field]],
+ reverse=sort_desc,
+ )
+ )
count = len(tags)
if page_size and tags:
tags = list(chunks(tags, page_size))[page]
@@ -453,21 +542,27 @@ def top_most_common_func(query, value, index):
most_common_item = [x[0] for x in most_common]
return filter(lambda x: x[index] in most_common_item, query)
- res.extend([
- bs_filters.TagBaseFilter(name, filter_type=FilterType.EQUAL),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.IN_LIST),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
- ])
- if name == 'usage_count':
- res.extend([
- bs_filters.TagBaseFilter(name, filter_type=FilterType.GREATER),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.SMALLER),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.TOP_X),
- bs_filters.TagBaseFilter(name, filter_type=FilterType.BOTTOM_X),
- bs_filters.TagBaseFilter(name, 'top most common', top_most_common_func),
- ])
- elif name == 'name':
+ res.extend(
+ [
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.EQUAL),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_EQUAL),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.IN_LIST),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.NOT_IN_LIST),
+ ]
+ )
+ if name == "usage_count":
+ res.extend(
+ [
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.GREATER),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.SMALLER),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.TOP_X),
+ bs_filters.TagBaseFilter(name, filter_type=FilterType.BOTTOM_X),
+ bs_filters.TagBaseFilter(
+ name, "top most common", top_most_common_func
+ ),
+ ]
+ )
+ elif name == "name":
pass
else:
return super().scaffold_filters(name)
@@ -480,8 +575,12 @@ def delete_model(self, model):
res = self.bukudb.delete_tag_at_index(0, model.name, chatty=False)
except Exception as ex:
if not self.handle_view_exception(ex):
- flash(gettext('Failed to delete record. %(error)s', error=str(ex)), 'error')
- LOG.exception('Failed to delete record.')
+ msg = "Failed to delete record."
+ flash(
+ gettext("%(msg)s %(error)s", msg=msg, error=str(ex)),
+ "error",
+ )
+ LOG.exception(msg)
return False
else:
self.after_model_delete(model)
@@ -496,8 +595,12 @@ def update_model(self, form, model):
res = self.bukudb.replace_tag(original_name, [model.name])
except Exception as ex:
if not self.handle_view_exception(ex):
- flash(gettext('Failed to update record. %(error)s', error=str(ex)), 'error')
- LOG.exception('Failed to update record.')
+ msg = "Failed to update record."
+ flash(
+ gettext("%(msg)s %(error)s", msg=msg, error=str(ex)),
+ "error",
+ )
+ LOG.exception(msg)
return False
else:
self.after_model_change(form, model, False)
@@ -508,46 +611,55 @@ def create_model(self, form):
class StatisticView(BaseView): # pylint: disable=too-few-public-methods
-
def __init__(self, *args, **kwargs):
self.bukudb = args[0]
args = list(args[1:])
super().__init__(*args, **kwargs)
- @expose('/', methods=('GET', 'POST'))
+ @expose("/", methods=("GET", "POST"))
def index(self):
bukudb = self.bukudb
global STATISTIC_DATA
statistic_data = STATISTIC_DATA
- if not statistic_data or request.method == 'POST':
+ if not statistic_data or request.method == "POST":
all_bookmarks = bukudb.get_rec_all()
netloc = [urlparse(x[1]).netloc for x in all_bookmarks]
tag_set = [x[3] for x in all_bookmarks]
tag_items = []
for tags in tag_set:
- tag_items.extend([x.strip() for x in tags.split(',') if x.strip()])
+ tag_items.extend([x.strip() for x in tags.split(",") if x.strip()])
tag_counter = Counter(tag_items)
title_items = [x[2] for x in all_bookmarks]
title_counter = Counter(title_items)
statistic_datetime = arrow.now()
STATISTIC_DATA = {
- 'datetime': statistic_datetime,
- 'netloc': netloc,
- 'tag_counter': tag_counter,
- 'title_counter': title_counter,
+ "datetime": statistic_datetime,
+ "netloc": netloc,
+ "tag_counter": tag_counter,
+ "title_counter": title_counter,
}
else:
- netloc = statistic_data['netloc']
- statistic_datetime = statistic_data['datetime']
- tag_counter = statistic_data['tag_counter']
- title_counter = statistic_data['title_counter']
+ netloc = statistic_data["netloc"]
+ statistic_datetime = statistic_data["datetime"]
+ tag_counter = statistic_data["tag_counter"]
+ title_counter = statistic_data["title_counter"]
netloc_counter = Counter(netloc)
unique_netloc_len = len(set(netloc))
colors = [
- "#F7464A", "#46BFBD", "#FDB45C", "#FEDCBA",
- "#ABCDEF", "#DDDDDD", "#ABCABC", "#4169E1",
- "#C71585", "#FF4500", "#FEDCBA", "#46BFBD"]
+ "#F7464A",
+ "#46BFBD",
+ "#FDB45C",
+ "#FEDCBA",
+ "#ABCDEF",
+ "#DDDDDD",
+ "#ABCABC",
+ "#4169E1",
+ "#C71585",
+ "#FF4500",
+ "#FEDCBA",
+ "#46BFBD",
+ ]
show_netloc_table = False
if unique_netloc_len > len(colors):
max_netloc_item = len(colors)
@@ -558,7 +670,9 @@ def index(self):
max_netloc_item = unique_netloc_len
most_common_netlocs = netloc_counter.most_common(max_netloc_item)
most_common_netlocs = [
- [val[0], val[1], netloc_colors[idx]] for idx, val in enumerate(most_common_netlocs)]
+ [val[0], val[1], netloc_colors[idx]]
+ for idx, val in enumerate(most_common_netlocs)
+ ]
unique_tag_len = len(tag_counter)
show_tag_rank_table = False
@@ -571,7 +685,9 @@ def index(self):
max_tag_item = unique_tag_len
most_common_tags = tag_counter.most_common(max_tag_item)
most_common_tags = [
- [val[0], val[1], tag_colors[idx]] for idx, val in enumerate(most_common_tags)]
+ [val[0], val[1], tag_colors[idx]]
+ for idx, val in enumerate(most_common_tags)
+ ]
unique_title_len = len(title_counter)
show_title_rank_table = False
@@ -584,10 +700,12 @@ def index(self):
max_title_item = unique_title_len
most_common_titles = title_counter.most_common(max_title_item)
most_common_titles = [
- [val[0], val[1], title_colors[idx]] for idx, val in enumerate(most_common_titles)]
+ [val[0], val[1], title_colors[idx]]
+ for idx, val in enumerate(most_common_titles)
+ ]
return self.render(
- 'bukuserver/statistic.html',
+ "bukuserver/statistic.html",
most_common_netlocs=most_common_netlocs,
netloc_counter=netloc_counter,
show_netloc_table=show_netloc_table,
@@ -598,10 +716,12 @@ def index(self):
title_counter=title_counter,
show_title_rank_table=show_title_rank_table,
datetime=statistic_datetime,
- datetime_text=statistic_datetime.humanize(arrow.now(), granularity='second'),
+ datetime_text=statistic_datetime.humanize(
+ arrow.now(), granularity="second"
+ ),
)
def chunks(arr, n):
n = max(1, n)
- return (arr[i:i+n] for i in range(0, len(arr), n))
+ return (arr[i : i + n] for i in range(0, len(arr), n))
diff --git a/setup.py b/setup.py
index 0a1baa70..274cc1b7 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@
import re
import shutil
-from setuptools import setup, find_packages
+from setuptools import find_packages, setup
if os.path.isfile('buku'):
shutil.copyfile('buku', 'buku.py')
@@ -34,29 +34,29 @@
server_require = [
- 'appdirs>=1.4.3',
- 'arrow>=0.12.1',
- 'beautifulsoup4>=4.5.3',
- 'cffi>=1.9.1',
- 'click>=6.7',
- 'Flask-Admin>=1.5.1',
- 'Flask-API>=0.6.9',
- 'Flask-Bootstrap>=3.3.7.1',
- 'flask-paginate>=0.5.1',
- 'flask-reverse-proxy-fix>=0.2.1',
- 'Flask-WTF>=0.14.2',
- 'Flask>=1.0.2,<2.0',
- 'idna>=2.5',
- 'itsdangerous>=0.24',
- 'Jinja2>=2.10.1',
- 'MarkupSafe>=1.0',
- 'packaging>=16.8',
- 'pyasn1>=0.2.3',
- 'pycparser>=2.17',
- 'requests>=2.21.0',
- 'six>=1.10.0',
- 'urllib3>=1.25.2',
- 'Werkzeug>=0.11.15',
+ "appdirs>=1.4.3",
+ "arrow>=0.12.1",
+ "beautifulsoup4>=4.5.3",
+ "cffi>=1.9.1",
+ "click>=6.7",
+ "Flask-Admin>=1.5.1",
+ "Flask-API>=0.6.9",
+ "Flask-Bootstrap>=3.3.7.1",
+ "flask-paginate>=0.5.1",
+ "flask-reverse-proxy-fix @ https://github.com/rachmadaniHaryono/flask-reverse-proxy-fix/archive/refs/tags/v0.2.2rc1.zip",
+ "Flask-WTF>=0.14.2",
+ "Flask>=1.0.2,<2.0",
+ "idna>=2.5",
+ "itsdangerous>=0.24",
+ "Jinja2>=2.10.1",
+ "MarkupSafe>=1.0",
+ "packaging>=16.8",
+ "pyasn1>=0.2.3",
+ "pycparser>=2.17",
+ "requests>=2.21.0",
+ "six>=1.10.0",
+ "urllib3>=1.25.2",
+ "Werkzeug>=0.11.15",
]
setup(
diff --git a/tests/test_buku.py b/tests/test_buku.py
index aaa4644f..e6c4bb48 100644
--- a/tests/test_buku.py
+++ b/tests/test_buku.py
@@ -1,17 +1,17 @@
"""test module."""
-from itertools import product
-from unittest import mock
-from urllib.parse import urlparse
import json
import logging
import os
import signal
import sys
import unittest
+from itertools import product
+from unittest import mock
+from urllib.parse import urlparse
import pytest
-from buku import is_int, parse_tags, prep_tag_search
+from buku import DELIM, is_int, prep_tag_search
only_python_3_5 = pytest.mark.skipif(
sys.version_info < (3, 5), reason="requires Python 3.5 or later")
@@ -32,6 +32,7 @@
def test_is_bad_url(url, exp_res):
"""test func."""
import buku
+
res = buku.is_bad_url(url)
assert res == exp_res
@@ -47,12 +48,14 @@ def test_is_bad_url(url, exp_res):
def test_is_ignored_mime(url, exp_res):
"""test func."""
import buku
+
assert exp_res == buku.is_ignored_mime(url)
def test_gen_headers():
"""test func."""
import buku
+
exp_myheaders = {
'Accept-Encoding': 'gzip,deflate',
'User-Agent': buku.USER_AGENT,
@@ -70,28 +73,55 @@ def test_get_PoolManager(m_myproxy):
"""test func."""
with mock.patch('buku.urllib3'):
import buku
+
buku.myproxy = m_myproxy
assert buku.get_PoolManager()
@pytest.mark.parametrize(
- 'keywords, exp_res',
+ "keywords, exp_res",
[
- (None, None),
- ([], None),
- (['tag1', 'tag2'], ',tag1 tag2,'),
- (['tag1,tag2', 'tag3'], ',tag1,tag2 tag3,'),
- ]
+ ("", DELIM),
+ (",", DELIM),
+ ("tag1, tag2", ",t a g 1,t a g 2,"),
+ ([" a tag , , , ,\t,\n,\r,\x0b,\x0c"], ",a tag,"), # whitespaces
+ ([",,,,,"], ","), # empty tags
+ (["\"tag\",'tag',tag"], ",\"tag\",'tag',tag,"), # escaping quotes
+ (["tag,tag, tag, tag,tag , tag "], ",tag,"), # duplicates, excessive spaces
+ (["tag1", "tag2", "tag3"], ",tag1 tag2 tag3,"),
+ (["tag1", "tag2"], ",tag1 tag2,"),
+ (["tag1"], ",tag1,"),
+ (["tag1,tag2", "tag3"], ",tag1,tag2 tag3,"),
+ (["tag1,tag2", "tag3,tag4"], ",tag1,tag2 tag3,tag4,"),
+ (["tag1,tag2"], ",tag1,tag2,"),
+ (["z_tag,a_tag,n_tag"], ",a_tag,n_tag,z_tag,"), # sorting tags
+ ([' '], ","),
+ ([''], ","),
+ ([','], ","),
+ ([], ","), # call with empty list
+ ([None], ","),
+ (None, None), # call with None
+ # combo
+ (
+ [',,z_tag, a tag ,\t,,, ,n_tag ,n_tag, a_tag, \na tag ,\r, "a_tag"'],
+ ',"a_tag",a tag,a_tag,n_tag,z_tag,',
+ ),
+ ],
)
def test_parse_tags(keywords, exp_res):
"""test func."""
import buku
+
if keywords is None:
- pass
- elif not keywords:
- exp_res = buku.DELIM
- res = buku.parse_tags(keywords)
- assert res == exp_res
+ assert buku.parse_tags(keywords) is None
+ else:
+ assert buku.parse_tags(keywords) == exp_res
+
+
+def test_parse_tags_no_args():
+ import buku
+
+ assert buku.parse_tags() == DELIM
@pytest.mark.parametrize(
@@ -151,6 +181,7 @@ def test_print_rec_with_filter(records, field_filter, exp_res):
"""test func."""
with mock.patch('buku.print', create=True) as m_print:
import buku
+
buku.print_rec_with_filter(records, field_filter)
for res in exp_res:
m_print.assert_any_call(res)
@@ -471,32 +502,6 @@ def test_piped_input(argv, pipeargs, isatty):
class TestHelpers(unittest.TestCase):
# @unittest.skip('skipping')
- def test_parse_tags(self):
- # call with None
- parsed = parse_tags(None)
- self.assertIsNone(parsed)
- # call with empty list
- parsed = parse_tags([])
- self.assertEqual(parsed, ",")
- # empty tags
- parsed = parse_tags([",,,,,"])
- self.assertEqual(parsed, ",")
- # sorting tags
- parsed = parse_tags(["z_tag,a_tag,n_tag"])
- self.assertEqual(parsed, ",a_tag,n_tag,z_tag,")
- # whitespaces
- parsed = parse_tags([" a tag , , , ,\t,\n,\r,\x0b,\x0c"])
- self.assertEqual(parsed, ",a tag,")
- # duplicates, excessive spaces
- parsed = parse_tags(["tag,tag, tag, tag,tag , tag "])
- self.assertEqual(parsed, ",tag,")
- # escaping quotes
- parsed = parse_tags(["\"tag\",\'tag\',tag"])
- self.assertEqual(parsed, ",\"tag\",\'tag\',tag,")
- # combo
- parsed = parse_tags([",,z_tag, a tag ,\t,,, ,n_tag ,n_tag, a_tag, \na tag ,\r, \"a_tag\""])
- self.assertEqual(parsed, ",\"a_tag\",a tag,a_tag,n_tag,z_tag,")
-
# @unittest.skip('skipping')
def test_is_int(self):
self.assertTrue(is_int('0'))
@@ -559,8 +564,9 @@ def test_sigint_handler(capsys):
)
def test_network_handler_with_url(url, exp_res):
"""test func."""
- import buku
import urllib3
+
+ import buku
buku.urllib3 = urllib3
buku.myproxy = None
res = buku.network_handler(url)
@@ -668,8 +674,9 @@ def test_import_org(tmpdir, newtag, exp_res):
)
def test_import_html(html_text, exp_res):
"""test method."""
- from buku import import_html
from bs4 import BeautifulSoup
+
+ from buku import import_html
html_soup = BeautifulSoup(html_text, 'html.parser')
res = list(import_html(html_soup, False, None))
for item, exp_item in zip(res, exp_res):
@@ -677,8 +684,9 @@ def test_import_html(html_text, exp_res):
def test_import_html_and_add_parent():
- from buku import import_html
from bs4 import BeautifulSoup
+
+ from buku import import_html
html_text = """