Commit 854e0378 authored by Louis's avatar Louis
Browse files

Format code using black

parent c05d4f6f
Pipeline #62748 passed with stages
in 1 minute and 16 seconds
image: paternal/pythons
image: paternal/pythons:buster
tox:
script:
......
repos:
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.6
......@@ -19,49 +19,49 @@ import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath(".."))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'sphinxarg.ext',
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinxarg.ext",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = 'pypimonitor'
copyright = '2016, Louis Paternault'
author = 'Louis Paternault'
project = "pypimonitor"
copyright = "2016, Louis Paternault"
author = "Louis Paternault"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.3.1'
version = "0.3.1"
# The full version, including alpha/beta/rc tags.
release = version
......@@ -74,201 +74,200 @@ language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# Autodoc options
autodoc_member_order = 'bysource'
autodoc_member_order = "bysource"
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'alabaster'
# html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#html_title = 'pypimonitor v0.2.0'
# html_title = 'pypimonitor v0.2.0'
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#html_last_updated_fmt = None
# html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#html_search_language = 'en'
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#html_search_options = {'type': 'default'}
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'pypimonitordoc'
htmlhelp_basename = "pypimonitordoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
# Latex figure (float) alignment
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'pypimonitor.tex', 'PypiMonitor Documentation',
'Louis Paternault', 'manual'),
(
master_doc,
"pypimonitor.tex",
"PypiMonitor Documentation",
"Louis Paternault",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'pypimonitor', 'PypiMonitor Documentation',
[author], 1)
]
man_pages = [(master_doc, "pypimonitor", "PypiMonitor Documentation", [author], 1)]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
......@@ -277,23 +276,29 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'pypimonitor', 'PypiMonitor Documentation',
author, 'pypimonitor', 'One line description of project.',
'Miscellaneous'),
(
master_doc,
"pypimonitor",
"PypiMonitor Documentation",
author,
"pypimonitor",
"One line description of project.",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3': None}
intersphinx_mapping = {"https://docs.python.org/3": None}
......@@ -5,4 +5,7 @@ dummy-variables-rgx=_|dummy
ignored-modules = straight
[MESSAGES CONTROL]
disable= logging-format-interpolation, I
disable=
logging-format-interpolation,
# Let black handle formatting
bad-continuation,
......@@ -40,23 +40,20 @@ from pypimonitor.cell.base import Error
VERSION = "0.3.1"
LOGGER = logging.getLogger(__name__)
CLIENT = xmlrpclib.ServerProxy('https://pypi.python.org/pypi')
CLIENT = xmlrpclib.ServerProxy("https://pypi.python.org/pypi")
def default_data(name):
"""Return default dummy data for a given package.
It is used for packages that are not published on Pypi.
"""
return {
"info": {
"name": name,
},
"releases": {},
"urls": [],
}
return {"info": {"name": name}, "releases": {}, "urls": []}
class Counter:
"""Each call to :meth:`Counter.count` increments a counter, and return its value."""
# pylint: disable=too-few-public-methods
def __init__(self):
......@@ -67,6 +64,7 @@ class Counter:
self.value += 1
return self.value
def random_color(seed, saturation, value):
"""Return a pseudo-random color."""
old_seed = random.random()
......@@ -75,11 +73,13 @@ def random_color(seed, saturation, value):
random.seed(old_seed)
return "#%02x%02x%02x" % tuple([int(value * 255) for value in rgb])
def iter_user_packages(user):
"""Iterate over packages owned by a given user."""
for _, package in CLIENT.user_packages(user):
yield package
class Renderer:
"""Get information about packages, and produce an HTML dashboard to monitor them."""
......@@ -89,53 +89,60 @@ class Renderer:
# Processing config
self.config = config
if "cells" not in self.config:
self.config['cells'] = list(set(itertools.chain(*[
package.keys()
for package
in self.config['packages'].values()
])))
for package in self.config['packages']:
if self.config['packages'][package] is None:
self.config['packages'][package] = {}
for cell in self.config['cells']:
if self.config['packages'][package].get(cell, None) is None:
self.config['packages'][package][cell] = {}
self.config["cells"] = list(
set(
itertools.chain(
*[
package.keys()
for package in self.config["packages"].values()
]
)
)
)
for package in self.config["packages"]:
if self.config["packages"][package] is None:
self.config["packages"][package] = {}
for cell in self.config["cells"]:
if self.config["packages"][package].get(cell, None) is None:
self.config["packages"][package][cell] = {}
# Set default values
for key in self.config.get('default', {}):
if key not in self.config['packages'][package]:
self.config['packages'][package][key] = self.config['default'][key]
if 'cell' not in self.config['packages'][package][cell]:
self.config['packages'][package][cell]['cell'] = cell
for key in self.config.get("default", {}):
if key not in self.config["packages"][package]:
self.config["packages"][package][key] = self.config["default"][
key
]
if "cell" not in self.config["packages"][package][cell]:
self.config["packages"][package][cell]["cell"] = cell
# Processing packages
self.packages = {}
for name in sorted(config['packages']):
for name in sorted(config["packages"]):
try:
self.packages[name] = Package(name, require_pypi=require_pypi)
except Exception as error: # pylint: disable=broad-except
except Exception as error: # pylint: disable=broad-except
self.log_error("Ignoring package '{}': {}".format(name, error))
continue
# Processing form
self.form = form
if self.form is not None:
for key in ['pkg', 'user', 'col']:
self.form[key] = ",".join(set(
itertools.chain(*[item.split(",") for item in self.form.get(key, [])])
))
for key in ["pkg", "user", "col"]:
self.form[key] = ",".join(
set(
itertools.chain(
*[item.split(",") for item in self.form.get(key, [])]
)
)
)
# Loading plugins
self.cells = {
plugin.keyword: plugin(self)
for plugin
in load_cell_plugins()
}
self.cells['error'] = Error(self)
self.cells = {plugin.keyword: plugin(self) for plugin in load_cell_plugins()}
self.cells["error"] = Error(self)
@classmethod
def from_yaml(cls, filename, form=None):
"""Factory to return a :class:`Renderer` from a yaml file."""
with open(filename, 'r') as stream:
with open(filename, "r") as stream:
return cls(yaml.safe_load(stream.read()), form=form)
@classmethod
......@@ -147,31 +154,31 @@ class Renderer:
if packages is None:
packages = []
packages = [
item.strip() for item in
itertools.chain(*[item.split(",") for item in packages])
]
item.strip()
for item in itertools.chain(*[item.split(",") for item in packages])
]
# Processing users
if users is None:
users = []
users = [item.strip() for item in itertools.chain(*[item.split(",") for item in users])]
users = [
item.strip()
for item in itertools.chain(*[item.split(",") for item in users])
]
for user in users:
try:
packages.extend(iter_user_packages(user))
except urllib.error.HTTPError as error:
LOGGER.error("Ignoring user '{}': {}".format(
user,
str(error),
))
LOGGER.error("Ignoring user '{}': {}".format(user, str(error)))
config['packages'] = {pkg: {} for pkg in set(packages)}
config["packages"] = {pkg: {} for pkg in set(packages)}
# Processing cells
if cells is None:
cells = []
cells = list(itertools.chain(*[item.split(",") for item in cells]))
if cells:
config['cells'] = cells
config["cells"] = cells
return cls(config, form=form, require_pypi=True)
......@@ -189,27 +196,30 @@ class Renderer:
@staticmethod
def _get_environment():
templatedir = os.path.join(
pkg_resources.resource_filename(__name__, 'data'),
'templates',
)
pkg_resources.resource_filename(__name__, "data"), "templates"
)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(templatedir))
env.globals['counter'] = Counter().count
env.globals['now'] = datetime.datetime.now()
env.filters['datetime'] = operator.methodcaller('strftime', "%Y-%m-%d %H:%M:%S")
env.filters['color'] = random_color
env.globals["counter"] = Counter().count
env.globals["now"] = datetime.datetime.now()
env.filters["datetime"] = operator.methodcaller("strftime", "%Y-%m-%d %H:%M:%S")
env.filters["color"] = random_color
return env
def render(self):
"""Return the HTML code."""
return self._get_environment().get_template("index.html").render( # pylint: disable=no-member
config=self.config,
cells=self.cells,
packages=self.packages,
form=self.form,
errors=self.errors,
version=VERSION,
return (
self._get_environment()
.get_template("index.html")
.render( # pylint: disable=no-member
config=self.config,
cells=self.cells,
packages=self.packages,
form=self.form,
errors=self.errors,
version=VERSION,
)
)
def log_error(self, message):
"""Log error, both to HTML page and console."""
......@@ -219,40 +229,38 @@ class Renderer:
class Package:
"""Gather information about a single package."""
# pylint: disable=too-few-public-methods
def __init__(self, name, require_pypi=False):
LOGGER.info("Retrieving information about pypi package '{}'...\n".format(name))
try:
self.rawdata = json.loads(
requests.get(
"http://pypi.python.org/pypi/{}/json".format(name)
).text
)
except Exception as error: # pylint: disable=broad-except
requests.get("http://pypi.python.org/pypi/{}/json".format(name)).text
)
except Exception as error: # pylint: disable=broad-except
if require_pypi:
raise Exception(