From 38727bba9ccd872706c60bdf45cad4cbb11cad1f Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jun 22 2018 09:11:49 +0000 Subject: Merge #281 `Build documentation by sphinx` --- diff --git a/.gitignore b/.gitignore index c80d326..7dad08e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ tags htmlcov/ /.eggs/ .env/ + +doc/build/ +doc/source/cli.rst +doc/source/commands/ +doc/source/man_pages.json diff --git a/MANIFEST.in b/MANIFEST.in index b935e18..9043293 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ include COPYING-koji include LGPL include CHANGELOG.rst include README.rst -include doc/rpkg_man_page.py include bin/rpkg recursive-include tests * recursive-include etc * @@ -13,3 +12,12 @@ include requirements/*.txt global-exclude *.pyc global-exclude __pycache__ + +include doc/Makefile +recursive-include doc *.rst *.py *.tmpl + +# RST files under commands directory and other listed files are generated by +# script when run make each time. They should not be included source +# distribution package. +prune doc/source/commands/ +exclude doc/source/cli.rst doc/source/man_pages.json diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..6b8eef4 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,33 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXBUILD = sphinx-build +SPHINXPROJ = rpkg +SOURCEDIR = source +BUILDDIR = build + + +all: clean-command-docs generate-man-pages-list html man +.PHONY: all + +clean-files: + @rm -f "$(SOURCEDIR)/commands/*.rst" + @rm -f $(SOURCEDIR)/cli.rst +.PHONY: clean-files + +clean-man-pages: + @rm -rf "$(BUILDDIR)/man/" +.PHONY: clean-man-pages + +generate-man-pages-list: + @python3 generate_commands_docs.py +.PHONY: generate-man-pages-list + +man: clean-man-pages + @$(SPHINXBUILD) -b man "$(SOURCEDIR)" "$(BUILDDIR)/man/" +.PHONY: man + +html: clean-files generate-man-pages-list + @$(SPHINXBUILD) -E -b html "$(SOURCEDIR)" "$(BUILDDIR)/html/" +.PHONY: html diff --git a/doc/generate_commands_docs.py b/doc/generate_commands_docs.py new file mode 100644 index 0000000..7e0d152 --- /dev/null +++ b/doc/generate_commands_docs.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# generate_man_pages.py - script to generate documents for commands +# +# Copyright (C) 2017 Red Hat Inc. +# Author(s): Chenxiong Qi +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. See http://www.gnu.org/copyleft/gpl.html for +# the full text of the license. + +from __future__ import print_function + +import argparse +import json +import logging +import os +import re +import six +import sys + +logging.basicConfig(level=logging.CRITICAL) +logger = logging.getLogger('Generator') + +join = os.path.join + +PROJECT_ROOT = os.path.abspath(join(os.path.dirname(__file__), '..')) +sys.path.insert(0, PROJECT_ROOT) + +DOC_DIR = join(PROJECT_ROOT, 'doc') +DOC_SOURCE_DIR = join(DOC_DIR, 'source') +COMMANDS_DOCS_DIR = join(DOC_SOURCE_DIR, 'commands') +MAN_PAGES_JSON_FILE = join(DOC_SOURCE_DIR, 'man_pages.json') + +if not os.path.exists(COMMANDS_DOCS_DIR): + os.mkdir(COMMANDS_DOCS_DIR) + +command_option_template = '''\ +.. option:: {name_metavar} + + {description} +''' + +command_arguments_template = '''\ +Arguments +--------- + +{arguments} +''' + + +class TemplateNotFound(Exception): + """Custom document template file is not found""" + + +def read_template(name, template_dir=None): + """Find and read template content + + :param str name: the template name. If a template file name is + `cli.rst.tmpl`, `cli.rst` should be passed to this argument. + :param str template_dir: a directory from where to find custom template + file. If omitted, template file will be found from templates directory + which is located at same place with this script. + :return: content of the template file. + :raises TemplateNotFound: if template file is not found. + """ + search_dir = template_dir or join(DOC_SOURCE_DIR, 'templates') + filename = join(search_dir, '{0}.tmpl'.format(name)) + if not os.path.exists(filename): + raise TemplateNotFound() + with open(filename, 'r') as f: + return f.read() + + +def format_literal(s): + """Format possible inline literal text + + For example, if string contains text --some-option, it will be formatted + to ``--some-option``. + """ + regex = '(--[a-z]+(-[a-z]+)+)' + return re.sub(regex, r'``\1``', s) + + +def generate_man_pages_list_file(parser, cli_subparsers, app_manpage_subject): + """Generate registered commands list for generating man pages + + The generated content will be written into a JSON file. Each command will + be mapped to man page information format that sphinx requires, which is + + (source start file, name, description, authors, manual section) + + But, we don't write the forth column authors, that will be handled + (inserted) by the piece of code inside `conf.py`. + + So, the final JSON file will contain data dumped from this structure + + [ + ('commands/{command name}', '{command name}, 'description', 1), + ... + ] + """ + action_helps = dict(( + (action.dest, action.help) + for action in cli_subparsers._get_subactions() + )) + man_pages_list = [ + [ + 'commands/{0}'.format(parser_name), + command_parser.prog.replace(' ', '-'), + action_helps[parser_name], + 1 + ] + for parser_name, command_parser in cli_subparsers.choices.items() + if parser_name in action_helps + ] + + # Add application manpage manually. For example, in a downstream client + # tool built on top of rpkg, this is for running `man fedpkg`. + man_pages_list += [ + [ + 'commands/{0}'.format(parser.prog), + parser.prog, + app_manpage_subject, + 1 + ] + ] + + logger.info('Write man_pages JSON file: %s', + os.path.relpath(MAN_PAGES_JSON_FILE)) + with open(MAN_PAGES_JSON_FILE, 'w+') as f: + json.dump(man_pages_list, f, indent=2) + + +def generate_command_rst_docs(cli_subparser, template_dir=None): + """Write command document into source/commands/ directory + + Man pages and HTML pages will be generated by sphinx from this directory. + + Each document is formatted in reStructuredText. + """ + for command_name, parser in [item for item in cli_subparser.choices.items() + if item[1].add_help]: + arguments_text = [] + options_text = [] + for action in parser._actions: + if action.option_strings: + # Optional arguments + options_text.append(command_option_template.format( + name_metavar=' '.join(action.option_strings), + description=format_literal(action.help))) + else: + # Positional arguments + arguments_text.append(command_option_template.format( + name_metavar=action.dest, + description=action.help, + )) + + # We need 4 space indent for each line in usage in order to be rendered + # in code block. + # Meanwhile, the text "usage: " is not useful for rendering HTML and + # man pages. So, remove it. And don't forget that besides adding + # indent, we also have to remove 7 space characters from the second + # line and rest. + buf = six.moves.StringIO(parser.format_usage().strip()) + first_n_chars = len('usage: ') + try: + usage = ''.join([ + ' {0}'.format(line[first_n_chars:]) for line in buf + ]) + finally: + buf.close() + + if arguments_text: + arguments = command_arguments_template.format( + arguments=os.linesep.join(arguments_text)) + else: + arguments = '' + + tmpl = read_template('command.rst', template_dir=template_dir) + command_rst_content = tmpl.format( + name=command_name, + name_section_marker='=' * len(command_name), + usage=usage, + description=parser.description or '', + arguments=arguments, + options=os.linesep.join(options_text)) + + filename = join(COMMANDS_DOCS_DIR, '{0}.rst'.format(command_name)) + logger.info('Write document: %s', os.path.relpath(filename)) + with open(filename, 'w+') as rst: + rst.write(command_rst_content) + + +def generate_app_synopsis_global_options(parser, template_dir=None): + """ + Generate text of synopsis and global options that will be filled into + cli.rst and [app name].rst inside commands directory. + """ + def _format_option(formatter, action): + name_metavar = formatter._format_action_invocation(action) + description = ' '.join(formatter._expand_help(action).split('\n')) + return command_option_template.format( + name_metavar=name_metavar, description=description) + + formatter = parser._get_formatter() + options = [ + _format_option(formatter, action) + for action in parser._get_optional_actions() + ] + + tmpl = read_template('cli_synopsis_global_options.rst', + template_dir=template_dir) + return tmpl.format(name=parser.prog, options='\n'.join(options)) + + +def generate_cli_rst(parser, cli_subparsers, template_dir=None): + """Generate cli.rst for generating HTML document""" + commands_list = [ + ' commands/{0}'.format(parser_name) + for parser_name, cmd_parser in cli_subparsers.choices.items() + if cmd_parser.add_help + ] + synopsis_global_options = generate_app_synopsis_global_options( + parser, template_dir=template_dir) + + tmpl = read_template('cli.rst', template_dir=template_dir) + with open(join(DOC_SOURCE_DIR, 'cli.rst'), 'w+') as f: + f.write(tmpl.format( + synopsis_global_options=synopsis_global_options, + commands_list='\n'.join(commands_list))) + + +def build_cli_parser(): + def validate_template_dir(value): + if not os.path.exists(value): + raise argparse.ArgumentTypeError( + '{0} does not exists.'.format(value)) + if not os.path.isdir(value): + raise argparse.ArgumentTypeError( + '{0} is not a directory.'.format(value)) + return value + + parser = argparse.ArgumentParser( + description='Generate commands RST documents in docs/source/commands' + ' for rendering HTML and man pages.') + parser.add_argument( + '--app-name', + required=False, + metavar='NAME', + dest='app_name', + default='sample-rpkg', + help='Application name of the downstream client tool built on top of' + ' rpkg. By default, for rpkg itself, sample-rpkg is used.') + parser.add_argument( + '-v', '--verbose', + required=False, + default=False, + dest='verbose', + action='store_true', + help='Verbose output.') + parser.add_argument( + '--template-dir', + required=False, + metavar='DIR', + type=validate_template_dir, + default=join(DOC_DIR, 'templates'), + help='From where to find custom document templates.') + parser.add_argument( + '--app-manpage-subject', + required=False, + metavar='SUBJECT', + dest='app_manpage_subject', + default='A utility to build RPM, container and module', + help='The subject shown in sample rpkg executable manpage.' + ) + return parser + + +if __name__ == '__main__': + import pyrpkg.cli + + cli_parser = build_cli_parser() + args = cli_parser.parse_args() + if args.verbose: + logger.level = logging.DEBUG + + client = pyrpkg.cli.cliClient(name=args.app_name, config=None) + + logger.info('Generate CLI RST for generating HTML document') + generate_cli_rst( + client.parser, client.subparsers, template_dir=args.template_dir) + + logger.info('Generate man_pages list from registered commands that will be' + ' read in conf.py') + generate_man_pages_list_file( + client.parser, client.subparsers, args.app_manpage_subject) + + logger.info('Generate commands RST documents from registered commands into' + ' source/commands/.') + generate_command_rst_docs( + client.subparsers, template_dir=args.template_dir) diff --git a/doc/rpkg_man_page.py b/doc/rpkg_man_page.py deleted file mode 100644 index 004dd59..0000000 --- a/doc/rpkg_man_page.py +++ /dev/null @@ -1,27 +0,0 @@ -# Print a man page from the help texts. -# -# Copyright (C) 2011 Red Hat Inc. -# Author(s): Jesse Keating -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. See http://www.gnu.org/copyleft/gpl.html for -# the full text of the license. - - -import os -import sys - - -if __name__ == '__main__': - module_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) - sys.path.insert(0, module_path) - - import pyrpkg.man_gen - import pyrpkg.cli - client = pyrpkg.cli.cliClient(name='rpkg', config=None) - pyrpkg.man_gen.generate(client.parser, - client.subparsers, - identity='rpkg', - sourceurl='https://pagure.io/rpkg/') diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/doc/source/_static/.placeholder diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..1042b23 --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,82 @@ +.. _api: + +API +=== + +Exceptions +---------- + +.. autoexception:: pyrpkg.errors.rpkgError +.. autoexception:: pyrpkg.errors.rpkgAuthError +.. autoexception:: pyrpkg.errors.UnknownTargetError +.. autoexception:: pyrpkg.errors.HashtypeMixingError +.. autoexception:: pyrpkg.errors.MalformedLineError +.. autoexception:: pyrpkg.errors.InvalidHashType +.. autoexception:: pyrpkg.errors.DownloadError +.. autoexception:: pyrpkg.errors.UploadError + + +cli +--- + +.. autoclass:: pyrpkg.cli.cliClient + :members: + :undoc-members: + :private-members: + + +commands +-------- + +.. autoclass:: pyrpkg.__init__.NullHandler + :members: + :undoc-members: + +.. autoclass:: pyrpkg.__init__.Commands + :members: + :undoc-members: + :private-members: + + +Lookaside +--------- + +.. autoclass:: pyrpkg.lookaside.CGILookasideCache + :members: + :undoc-members: + + +Sources +------- + +.. autoclass:: pyrpkg.sources.SourcesFile + :members: + :undoc-members: + +.. autoclass:: pyrpkg.sources.SourceFileEntry + :members: + :undoc-members: + +.. autoclass:: pyrpkg.sources.BSDSourceFileEntry + :members: + :undoc-members: + + +gitignore +--------- + +.. autoclass:: pyrpkg.gitignore.GitIgnore + :members: + :undoc-members: + + +Utilities +--------- + +.. autoclass:: pyrpkg.utils.cached_property + :members: + :undoc-members: + +.. autofunction:: pyrpkg.utils.warn_deprecated +.. autofunction:: pyrpkg.utils._log_value +.. autofunction:: pyrpkg.utils.log_result \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..f291425 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# +# rpkg documentation build configuration file, created by +# sphinx-quickstart on Tue Dec 26 21:06:01 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# 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. +# +import os +import sys +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' + +# 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'] + +# Add any paths that contain templates here, relative to this directory. +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' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'rpkg' +copyright = u'2017, rpkg team' +author = u'rpkg team' + +# 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 = u'1.51' +# The full version, including alpha/beta/rc tags. +release = u'1.51' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# 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 = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- 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 = 'sphinx_rtd_theme' + +# 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 = {} + +# 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'globaltoc.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'rpkgdoc' + + +# -- 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', +} + +# 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, 'rpkg.tex', u'rpkg Documentation', + u'rpkg team', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +# +# Man pages are generated from registered commands in each register_* method. +# Script generate_man_pages.py must run before `make man'. + +if os.path.exists('man_pages.json'): + import json + with open('man_pages.json', 'r') as f: + man_pages = json.load(f) + [man_info.insert(3, [author]) for man_info in man_pages] +else: + man_pages = [] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'rpkg', u'rpkg Documentation', + author, 'rpkg', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst new file mode 100644 index 0000000..382be14 --- /dev/null +++ b/doc/source/configuration.rst @@ -0,0 +1,5 @@ +.. _configuration: + +Configuration +============= + diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..ce26967 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,24 @@ +.. rpkg documentation master file, created by + sphinx-quickstart on Tue Dec 26 21:06:01 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to rpkg's documentation! +================================ + +.. toctree:: + :maxdepth: 2 + + intro + configuration + cli + api + migrations + release_notes + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/intro.rst b/doc/source/intro.rst new file mode 100644 index 0000000..d9b25df --- /dev/null +++ b/doc/source/intro.rst @@ -0,0 +1,3 @@ +.. _intro: + +.. include:: ../../README.rst \ No newline at end of file diff --git a/doc/source/migrations.rst b/doc/source/migrations.rst new file mode 100644 index 0000000..f420434 --- /dev/null +++ b/doc/source/migrations.rst @@ -0,0 +1,12 @@ +.. _migrations: + +Migration +========= + +Here are migration guide to migrate existing code based on latest rpkg code +base. + +.. toctree:: + :maxdepth: 1 + + migrations/use-kojiprofile diff --git a/doc/source/migrations/use-kojiprofile.rst b/doc/source/migrations/use-kojiprofile.rst new file mode 100644 index 0000000..5ae11ed --- /dev/null +++ b/doc/source/migrations/use-kojiprofile.rst @@ -0,0 +1,22 @@ +.. _use-kojiprofile: + +Use kojiprofile +=============== + +``kojiprofile`` is supported from version 1.50, and ``kojiconfig`` is +deprecated at same time. To migrate to ``kojiprofile``, please follow these +steps below. + +* Add ``kojiprofile`` in your application's configuration file. For example, + + .. code-block:: ini + + [myapp] + kojiprofile = koji + +* Remove deprecated ``kojiconfig`` from configuration file. + +* Replace argument ``kojiconfig`` with ``kojiprofile`` in ``Commands.__init__``. + +* Remove argument ``kojiconfig`` from ``Commands.container_build_koji`` + argument list. diff --git a/doc/source/release_notes.rst b/doc/source/release_notes.rst new file mode 100644 index 0000000..e4b6d54 --- /dev/null +++ b/doc/source/release_notes.rst @@ -0,0 +1,10 @@ +.. _release_notes: + +Release Notes +============= + +.. include release notes of each version from directory release_notes. + +.. toctree:: + :maxdepth: 1 + diff --git a/doc/templates/app.rst.tmpl b/doc/templates/app.rst.tmpl new file mode 100644 index 0000000..980ff1e --- /dev/null +++ b/doc/templates/app.rst.tmpl @@ -0,0 +1,24 @@ +.. _{name}: + +{name} +{name_section_marker} + +.. program:: {name} + +{synopsis_global_options} + +Commands +-------- + +{commands} + +Reporting Bugs +-------------- + +Login and create a issue from https://pagure.io/rpkg/issues/. + +Notes +----- + +* Source code: https://pagure.io/rpkg +* Documentation: https://docs.pagure.org/rpkg/ diff --git a/doc/templates/cli.rst.tmpl b/doc/templates/cli.rst.tmpl new file mode 100644 index 0000000..33389b8 --- /dev/null +++ b/doc/templates/cli.rst.tmpl @@ -0,0 +1,19 @@ +.. _cli: + +CLI +=== + +rpkg is a library to build command line utility to build RPM, container and +module in local machine or remote build system. There is no executable to be +shipped from rpkg project. This page shows what command line interface and +commands will be available in any packaging tool that is built on top of rpkg. + +{synopsis_global_options} + +Commands +-------- + +.. toctree:: + :maxdepth: 1 + +{commands_list} diff --git a/doc/templates/cli_synopsis_global_options.rst.tmpl b/doc/templates/cli_synopsis_global_options.rst.tmpl new file mode 100644 index 0000000..a6dbaa9 --- /dev/null +++ b/doc/templates/cli_synopsis_global_options.rst.tmpl @@ -0,0 +1,19 @@ +Synopsis +-------- + +:: + + {name} [ global_options ] command [ command_options ] [ command_arguments ] + {name} help + {name} command --help + +Description +----------- + +{name} is a library to build command line appliaction to build RPM, container +and module locally or in remote build system. + +Global Options +-------------- + +{options} diff --git a/doc/templates/command.rst.tmpl b/doc/templates/command.rst.tmpl new file mode 100644 index 0000000..096ef11 --- /dev/null +++ b/doc/templates/command.rst.tmpl @@ -0,0 +1,25 @@ +.. _{name}: + +{name} +{name_section_marker} + +.. program:: {name} + +Synopsis +-------- + +:: + +{usage} + +Description +----------- + +{description} + +{arguments} + +Options +------- + +{options} diff --git a/doc/use-kojiprofile.rst b/doc/use-kojiprofile.rst deleted file mode 100644 index 38fae90..0000000 --- a/doc/use-kojiprofile.rst +++ /dev/null @@ -1,20 +0,0 @@ -Use kojiprofile -=============== - -``kojiprofile`` is supported from version 1.50, and ``kojiconfig`` is -deprecated at same time. To migrate to ``kojiprofile``, please follow these -steps below. - -* Add ``kojiprofile`` in your application's configuration file. For example, - - :: - - [myapp] - kojiprofile = koji - -* Remove deprecated ``kojiconfig`` from configuration file. - -* Replace argument ``kojiconfig`` with ``kojiprofile`` in ``Commands.__init__``. - -* Remove argument ``kojiconfig`` from ``Commands.container_build_koji`` - argument list. diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py index 3c8f5b3..052b94a 100644 --- a/pyrpkg/__init__.py +++ b/pyrpkg/__init__.py @@ -196,12 +196,13 @@ class Commands(object): def lookasidecache(self): """A helper to interact with the lookaside cache - This is a pyrpkg.lookaside.CGILookasideCache instance, providing all - the needed stuff to communicate with a Fedora-style lookaside cache. - Downstream users of the pyrpkg API may override this property with their own, returning their own implementation of a lookaside cache helper object. + + :return: lookaside cache instance providing all the needed stuff to + communicate with a Fedora-style lookaside cache. + :rtype: :py:class:`pyrpkg.lookaside.CGILookasideCache` """ return CGILookasideCache( self.lookasidehash, self.lookaside, self.lookaside_cgi, @@ -397,11 +398,9 @@ class Commands(object): def load_branch_merge(self): """Find the remote tracking branch from the branch we're on. - The goal of this function is to catch if we are on a branch we - - can make some assumptions about. If there is no merge point - - then we raise and ask the user to specify. + The goal of this function is to catch if we are on a branch we can make + some assumptions about. If there is no merge point then we raise and + ask the user to specify. """ if self.dist: @@ -922,28 +921,36 @@ class Commands(object): # Define some helper functions, they start with _ def _has_krb_creds(self): - """Kerberos authentication is disabled if neither gssapi nor krbV is available""" + """ + Kerberos authentication is disabled if neither gssapi nor krbV is + available + """ return cccolutils.has_creds() def _run_command(self, cmd, shell=False, env=None, pipe=[], cwd=None): """Run the given command. - _run_command is able to run single command or two commands via pipe. + `_run_command` is able to run single command or two commands via pipe. Whatever the way to run the command, output to both stdout and stderr will not be captured and output to terminal directly, that is useful for caller to redirect. - cmd is a list of the command and arguments - - shell is whether to run in a shell or not, defaults to False - - env is a dict of environment variables to use (if any) - - pipe is a command to pipe the output of cmd into - - cwd is the optional directory to run the command from - - Raises on error, or returns nothing. + :param cmd: executable and arguments to run. Depending on argument + `shell`, `cmd` could be a list if `shell` is `False`, and a string + if `False` is passed to `shell`. + :type cmd: str or list + :param bool shell: whether to run in a shell or not, defaults to + `False`. + :param env: environment variables to use (if any) + :type env: dict(str, str) + :param pipe: command to pipe the output of `cmd` into. Same as argument + `cmd`, `pipe` could be a string or list depending on value of + `shell`. + :type pipe: str or list + :param str cwd: optional directory to run the command from + :raises rpkgError: any error raised from underlying + `subprocess` while executing command in local system will be mapped + to :py:exc:`rpkgError`. """ # Process any environment variables. @@ -985,16 +992,14 @@ class Commands(object): def _newer(self, file1, file2): """Compare the last modification time of the given files - Returns True is file1 is newer than file2 - + :return: True if file1 is newer than file2 + :rtype: bool """ return os.path.getmtime(file1) > os.path.getmtime(file2) def _get_build_arches_from_spec(self): - """Given the path to an spec, retrieve the build arches - - """ + """Given the path to an spec, retrieve the build arches""" spec = os.path.join(self.path, self.spec) try: @@ -1013,7 +1018,6 @@ class Commands(object): """Given the path to an srpm, determine the possible build arches Use supplied arches as a filter, only return compatible arches - """ archlist = arches @@ -1064,7 +1068,11 @@ class Commands(object): self.repo.git.fetch(remote) def _list_branches(self, fetch=True): - """Returns a tuple of local and remote branch names""" + """Returns a tuple of local and remote branch names + + :return: a pair of local branch names and remote branch names. + :rtype: tuple(list, list) + """ if fetch: self._fetch_remotes() @@ -1115,10 +1123,9 @@ class Commands(object): def _get_namespace_giturl(self, module): """Get the namespaced git url, if DistGit namespaces enabled - Takes a module name - - Returns a string of giturl - + :param str module: a module name + :return: giturl with proper namespace. + :rtype: str """ if self.distgit_namespaced: @@ -1138,10 +1145,9 @@ class Commands(object): def _get_namespace_anongiturl(self, module): """Get the namespaced git url, if DistGit namespaces enabled - Takes a module name - - Returns a string of giturl - + :param str module: a module name + :return: anonymous giturl with a proper namespace. + :rtype: str """ if self.distgit_namespaced: @@ -1158,13 +1164,10 @@ class Commands(object): def add_tag(self, tagname, force=False, message=None, file=None): """Add a git tag to the repository - Takes a tagname - - Optionally can force the tag, include a message, - or reference a message file. - - Runs the tag command and returns nothing - + :param str tagname: tag name. + :param bool force: Optionally can force the tag, include a message, or + reference a message file. + :param str file: Optional. File containing tag message. """ cmd = ['git', 'tag'] @@ -1185,12 +1188,9 @@ class Commands(object): def clean(self, dry=False, useignore=True): """Clean a module checkout of untracked files. - Can optionally perform a dry-run - - Can optionally not use the ignore rules - - Logs output and returns nothing - + :param bool dry: Can optionally perform a dry-run. Defaults to `False`. + :param bool useignore: Can optionally not use the ignore rules. + Defaults to `True`. """ # setup the command, this could probably be done with some python api... @@ -1209,21 +1209,16 @@ class Commands(object): anon=False, target=None): """Clone a repo, optionally check out a specific branch. - module is the name of the module to clone - - path is the basedir to perform the clone in - - branch is the name of a branch to checkout instead of /master - - bare_dir is the name of a directory to make a bare clone to, if this - is a bare clone. None otherwise. - - anon is whether or not to clone anonymously - - target is the name of the folder in which to clone the repo - - Logs the output and returns nothing. - + :param str module: the name of the module to clone + :param str path: an optional basedir to perform the clone in. + :param str branch: an optional name of a branch to checkout instead of + `/master`. + :param str bare_dir: an optional name of a directory to make a bare + clone to if this is a bare clone. `None` otherwise. + :param bool anon: whether or not to clone anonymously. Defaults to + `False`. + :param str target: an optional name of the folder in which to clone the + repo. """ if not path: @@ -1285,10 +1280,11 @@ class Commands(object): def clone_with_dirs(self, module, anon=False, target=None): """Clone a repo old style with subdirs for each branch. - module is the name of the module to clone - - gitargs is an option list of arguments to git clone - + :param str module: name of the module to clone + :param bool anon: whether or not to clone anonymously. Defaults to + `False`. + :param str target: an optional name of the folder in which to clone the + repo. """ self._push_url = None @@ -1362,16 +1358,12 @@ class Commands(object): def commit(self, message=None, file=None, files=[], signoff=False): """Commit changes to a module (optionally found at path) - Can take a message to use as the commit message - - a file to find the commit message within - - and a list of files to commit. - Requires the caller be a real tty or a message passed. - Logs the output and returns nothing. - + :param str message: an optional message to use as the commit message + :param str file: an optional file to find the commit message within + :param list files: an optional list to list files to commit. + :param bool signoff: signoff commit optionally. Defaults to `False`. """ # First lets see if we got a message or we're on a real tty: @@ -1416,13 +1408,10 @@ class Commands(object): def diff(self, cached=False, files=[]): """Execute a git diff - optionally diff the cached or staged changes - - Takes an optional list of files to diff relative to the module base - directory - - Logs the output and returns nothing - + :param bool cached: optionally diff the cached or staged changes. + Defaults to not. + :param list files: optional list of files to diff relative to the + module base directory. """ # Things work better if we're in our module directory @@ -1510,14 +1499,12 @@ class Commands(object): def import_srpm(self, srpm): """Import the contents of an srpm into a repo. - srpm: File to import contents from - This function will add/remove content to match the srpm, - upload new files to the lookaside, and stage the changes. - Returns a list of files to upload. - + :param str srpm: file to import contents from. + :return: a list of files to upload. + :rtype: list """ # bail if we're dirty if self.repo.is_dirty(): @@ -1584,9 +1571,8 @@ class Commands(object): def list_tag(self, tagname='*'): """List all tags in the repository which match a given tagname. - The optional `tagname` argument may be a shell glob (it is matched - with fnmatch). - + :param str tagname: an optional shell glob to match part of tags (it + is matched with `fnmatch`). Defaults to '`*`' to list all tags. """ if tagname is None: tagname = '*' @@ -2200,9 +2186,15 @@ class Commands(object): Takes localargs (passed to rpmbuild), arch to build for, and hashtype to build with. - Writes output to a log file and logs it to the logger + Writes output to a log file and logs it to the logger. Log file is + written into current working directory and in format + `.build-{version}-{release}.log`. - Returns the returncode from the build call + :param str arch: to optionally build for a specific arch. + :param str hashtype: an alternative algorithm used for payload file + digests. + :param str builddir: an alternative builddir. + :raises rpkgError: if underlying `rpmbuild` fails. """ # This could really use a list of arches to build for and loop over @@ -2245,8 +2237,12 @@ class Commands(object): def mock_config(self, target=None, arch=None): """Generate a mock config based on branch data. - Can use option target and arch to override autodiscovery. - Will return the mock config file text. + :param str target: an alternative build target, otherwise default build + target will be used. + :param str arch: an alternative arch, otherwise local system arch will + be used. + :return: the mock config content got from Koji. + :rtype: str """ # Figure out some things about ourself. @@ -2283,7 +2279,8 @@ class Commands(object): If files are found in system config directory for mock they are copied to mock config directory defined as method's argument. Otherwise empty - files are created.""" + files are created. + """ for filename in filenames: system_filename = '/etc/mock/%s' % filename tmp_filename = os.path.join(config_dir, filename) @@ -2309,7 +2306,8 @@ class Commands(object): processing, temporary directory is removed. Otherwise it caller's responsibility to remove this directory. - Returns used config directory""" + Returns used config directory + """ if not root: root = self.mockconfig if not config_dir: @@ -2401,7 +2399,13 @@ class Commands(object): def upload(self, files, replace=False): """Upload source file(s) in the lookaside cache - Can optionally replace the existing tracked sources + Both file `sources` and `.gitignore` will be updated with uploaded + files, and added to index tree eventually. + + :param iterable files: an iterable of files to upload. + :param bool replace: optionally replace the existing tracked sources. + Defaults to `False`. + :raises rpkgError: if failed to add a file to file `sources`. """ sourcesf = SourcesFile(self.sources_filename, self.source_entry_type, @@ -2441,12 +2445,11 @@ class Commands(object): self.repo.index.add(['sources', '.gitignore']) def prep(self, arch=None, builddir=None): - """Run rpm -bp on a module - - optionally for a specific arch, or - define an alternative builddir + """Run `rpmbuild -bp` on a module - Logs the output and returns nothing + :param str arch: optional to run prep section for a specific arch. By + default, local system arch will be used. + :param str builddir: an alternative builddir. """ # setup the rpm command @@ -2466,7 +2469,11 @@ class Commands(object): def srpm(self, hashtype=None): """Create an srpm using hashtype from content in the module - Requires sources already downloaded. + Requires sources already downloaded. The generated SRPM file will be + put into package repository directory. + + :param str hashtype: an alternative algorithm used for payload file + digests. """ self.srpmname = os.path.join(self.path, @@ -2497,7 +2504,8 @@ class Commands(object): def unused_patches(self): """Discover patches checked into source control that are not used - Returns a list of unused patches, which may be empty. + :return: a list of unused patches, which may be empty. + :rtype: list """ # Create a list for unused patches @@ -2533,9 +2541,11 @@ class Commands(object): def _byte_offset_to_line_number(self, text, offset): """ - Convert byte offset (given by e.g. DecodeError) to human readable + Convert byte offset (given by e.g. `DecodeError`) to human readable format (line number and char position) - Return a list with line number and char offset + + :return: a pair of line number and char offset. + :rtype: list(int, int) """ offset_inc = 0 line_num = 1 @@ -2548,9 +2558,9 @@ class Commands(object): return [line_num, offset - offset_inc + 1] def verify_files(self, builddir=None): - """Run rpmbuild -bl on a module to verify the %files section + """Run `rpmbuild -bl` on a module to verify the `%files` section - optionally define an alternate builddir + :param str builddir: optionally define an alternate builddir. """ # setup the rpm command @@ -2683,20 +2693,19 @@ class Commands(object): oidc_client_secret=None, oidc_scopes=None): """ Cancel an MBS build - :param api_url: a string of the URL of the MBS API - :param build_id: an integer of the build ID to cancel - :param auth_method: a string of the authentication method used by the - MBS - :kwarg oidc_id_provider: a string of the OIDC provider when MBS is - using OIDC for authentication - :kwarg oidc_client_id: a string of the OIDC client ID when MBS is - using OIDC for authentication - :kwarg oidc_client_secret: a string of the OIDC client secret when MBS - is using OIDC for authentication. Based on the OIDC setup, this could - be None. - :kwarg oidc_scopes: a list of OIDC scopes when MBS is using OIDC for - authentication - :return: None + + :param str api_url: URL of the MBS API + :param int build_id: build ID to cancel + :param str auth_method: authentication method used by the MBS + :kwarg str oidc_id_provider: the OIDC provider when MBS is using OIDC + for authentication + :param str oidc_client_id: the OIDC client ID when MBS is using OIDC + for authentication + :param str oidc_client_secret: the OIDC client secret when MBS is using + OIDC for authentication. Based on the OIDC setup, this could be + `None`. + :param list oidc_scopes: a list of OIDC scopes when MBS is using OIDC + for authentication """ # Make sure the build they are trying to cancel exists self.module_get_build(api_url, build_id) @@ -2716,9 +2725,9 @@ class Commands(object): def module_build_info(self, api_url, build_id): """ Show information about an MBS build - :param api_url: a string of the URL of the MBS API - :param build_id: an integer of the build ID to query MBS about - :return: None + + :param str api_url: URL of the MBS API + :param int build_id: build ID to query MBS about """ # Load the Koji session anonymously so we get access to the Koji web # URL @@ -2769,13 +2778,15 @@ class Commands(object): def module_get_url(self, api_url, build_id, action='GET'): """ Get the proper MBS API URL for the desired action - :param api_url: a string of the URL of the MBS API - :param build_id: an integer of the module build desired. If this is set - to None, then the base URL for all module builds is returned. - :kwarg action: a string determining the HTTP action. If this is set to - GET, then the URL will contain `?verbose=true`. Any other value will - not have verbose set. - :return: a string of the desired MBS API URL + + :param str api_url: a string of the URL of the MBS API + :param int build_id: an integer of the module build desired. If this is + set to None, then the base URL for all module builds is returned. + :param str action: a string determining the HTTP action. If this is set + to `GET`, then the URL will contain `?verbose=true`. Any other + value will not have verbose set. + :return: a string of the desired MBS API URL. + :rtype: str """ url = urljoin(api_url, 'module-builds/') if build_id is not None: @@ -2791,8 +2802,10 @@ class Commands(object): def module_get_koji_state_dict(): """ Get a dictionary of Koji build states with the keys being strings and - the values being their associated integer + the values being their associated integer. + :return: a dictionary of Koji build states + :rtype: dict """ state_names = dict([(v, k) for k, v in koji.BUILD_STATES.items()]) state_names[None] = 'undefined' @@ -2803,12 +2816,14 @@ class Commands(object): Determines the proper SCM URL and branch based on the arguments. If the user doesn't specify an SCM URL and branch, then the git repo the user is currently in is used instead. - :kwarg scm_url: a string of the module's SCM URL - :kwarg branch: a string of the module's branch - :kwarg check_repo: a boolean that determines if check_repo should be - run when an scm_url is not provided. + + :param str scm_url: a string of the module's SCM URL + :param str branch: a string of the module's branch + :kwarg bool check_repo: a boolean that determines if check_repo should + be run when an scm_url is not provided. :return: a tuple containing a string of the SCM URL and a string of the - branch + branch + :rtype: tuple """ if not scm_url and check_repo: # Make sure the local repo is clean (no unpushed changes) if the @@ -2842,19 +2857,25 @@ class Commands(object): mbs_config_section=None, default_streams=None): """ A wrapper for `mbs-manager build_module_locally`. - :param file_path: a string, path of the module's modulemd yaml file. - :param stream: a string, stream of the module. - :kwarg local_builds_nsvs: a list of localbuild ids to import into MBS - before running this local build. - :kwarg verbose: a boolean specifying if mbs-manager should be verbose. - This is overridden by self.quiet. - :kwarg debug: a boolean specifying if mbs-manager should be debug. - This is overridden by self.quiet and verbose. - :kwarg skip_tests: a boolean determining if the check sections should be skipped - :kwarg mbs_config: a string, path to alternative MBS config file to use. - :kwarg mbs_config_section: a string, name of alternative config section to use. - :kwargs default_streams: a list, contains strings with default name:stream pairs - which are passed to mbs-manager using the '-s' command line argument. + + :param str file_path: a string, path of the module's modulemd yaml file. + :param str stream: a string, stream of the module. + :param list local_builds_nsvs: a list of localbuild ids to import into + MBS before running this local build. + :param bool verbose: a boolean specifying if mbs-manager should be + verbose. This is overridden by self.quiet. + :param bool debug: a boolean specifying if mbs-manager should be debug. + This is overridden by self.quiet and verbose. + :param bool skip_tests: a boolean determining if the check sections + should be skipped. + :param str mbs_config: a string, path to alternative MBS config file to + use. + :param str mbs_config_section: a string, name of alternative config + section to use. + :param default_streams: a list, contains strings with default + `name:stream` pairs which are passed to mbs-manager using the '-s' + command line argument. + :type default_streams: list[str] :return: None """ command = ['mbs-manager'] @@ -2898,12 +2919,12 @@ class Commands(object): def module_overview(self, api_url, limit=10, finished=True): """ Show the overview of the latest builds in MBS - :param api_url: a string of the URL of the MBS API - :kwarg limit: an integer of the number of most recent module builds to - display. This defaults to 10. - :kwarg finished: a boolean that determines if only finished or - unfinished module builds should be displayed. This defaults to True. - :return: None + + :param str api_url: a string of the URL of the MBS API + :param int limit: an integer of the number of most recent module builds + to display. This defaults to 10. + :param bool finished: a boolean that determines if only finished or + unfinished module builds should be displayed. This defaults to True. """ # Don't let the user cause problems by specifying a negative limit if limit < 1: @@ -2930,8 +2951,9 @@ class Commands(object): """ Private function that is used for multithreading later on to get the desired amount of builds for a specific state. + :param state: an integer representing the build state to query for - :return: yields dictionaries of the builds found + :return: a generator to yield dictionaries of the builds found """ total = 0 page = 1 @@ -3006,21 +3028,23 @@ class Commands(object): oidc_scopes=None, **kwargs): """ Sends authorized request to MBS - :param verb: a string of the HTTP verb of the request (e.g. POST) - :param url: a string of the URL to make the request on - :param body: a dictionary of the data to send in the authorized request - :param auth_method: a string of the authentication method used by the - MBS - :kwarg oidc_id_provider: a string of the OIDC provider when MBS is - using OIDC for authentication - :kwarg oidc_client_id: a string of the OIDC client ID when MBS is - using OIDC for authentication - :kwarg oidc_client_secret: a string of the OIDC client secret when MBS - is using OIDC for authentication. Based on the OIDC setup, this could - be None. - :kwarg oidc_scopes: a list of OIDC scopes when MBS is using OIDC for - authentication - :kwarg **kwargs: any additional python-requests keyword arguments + + :param str verb: a string of the HTTP verb of the request (e.g. `POST`) + :param str url: a string of the URL to make the request on. + :param dict body: a dictionary of the data to send in the authorized + request. + :param str auth_method: a string of the authentication method used by + the MBS. Valid methods are `oidc` and `kerberos`. + :param str oidc_id_provider: a string of the OIDC provider when MBS is + using OIDC for authentication + :param str oidc_client_id: a string of the OIDC client ID when MBS is + using OIDC for authentication + :param str oidc_client_secret: a string of the OIDC client secret when + MBS is using OIDC for authentication. Based on the OIDC setup, this + could be None. + :param list oidc_scopes: a list of OIDC scopes when MBS is using OIDC + for authentication + :param kwargs: any additional python-requests keyword arguments. :return: a python-requests response object """ if auth_method == 'oidc': @@ -3065,24 +3089,26 @@ class Commands(object): oidc_scopes=None): """ Submit a module build to the MBS + :param api_url: a string of the URL of the MBS API :param scm_url: a string of the module's SCM URL :param branch: a string of the module's branch - :param auth_method: a string of the authentication method used by the - MBS + :param str auth_method: a string of the authentication method used by + the MBS. :param optional: an optional list of "key=value" to be passed in with - the MBS build submission - :kwarg oidc_id_provider: a string of the OIDC provider when MBS is - using OIDC for authentication - :kwarg oidc_client_id: a string of the OIDC client ID when MBS is - using OIDC for authentication - :kwarg oidc_client_secret: a string of the OIDC client secret when MBS - is using OIDC for authentication. Based on the OIDC setup, this could - be None. - :kwarg oidc_scopes: a list of OIDC scopes when MBS is using OIDC for - authentication + the MBS build submission. + :type optional: list[str] + :param str oidc_id_provider: a string of the OIDC provider when MBS is + using OIDC for authentication. + :param str oidc_client_id: a string of the OIDC client ID when MBS is + using OIDC for authentication + :param str oidc_client_secret: a string of the OIDC client secret when + MBS is using OIDC for authentication. Based on the OIDC setup, this + could be None. :kwarg oidc_scopes: a list of OIDC scopes when MBS + is using OIDC for authentication. :return: a list of module build IDs that are being built from this - request + request. + :rtype: list[int] """ body = {'scmurl': scm_url, 'branch': branch} optional = optional if optional else [] @@ -3117,12 +3143,13 @@ class Commands(object): def module_watch_build(self, api_url, build_ids): """ - Watches the first MBS build in the list in a loop that updates every - 15 seconds. The loop ends when the build state is 'failed', 'done', or + Watches the first MBS build in the list in a loop that updates every 15 + seconds. The loop ends when the build state is 'failed', 'done', or 'ready'. - :param api_url: a string of the URL of the MBS API + + :param str api_url: a string of the URL of the MBS API :param build_ids: a list of module build IDs - :return: None + :type build_ids: list[int] """ build_id = build_ids[0] warning = None diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py index ac0d8fc..90df637 100644 --- a/pyrpkg/cli.py +++ b/pyrpkg/cli.py @@ -280,7 +280,9 @@ class cliClient(object): As a side effect method sets self.site with a loaded library. - site option can be used to specify which library to load + :param site: used to specify which library to load. + :type site: the module of downstream client that is built on top of + rpkg. """ # We do some imports here to be more flexible @@ -1705,11 +1707,9 @@ see API KEY section of copr-cli(1) man page. print(self.cmd.mock_config(self.args.target, self.args.arch)) def module_build(self): - """ - Builds a module using MBS - :return: None - """ + """Builds a module using MBS""" api_url = self.module_api_url + self.module_validate_config() scm_url, branch = self.cmd.module_get_scm_info( self.args.scm_url, self.args.branch) auth_method, oidc_id_provider, oidc_client_id, oidc_client_secret, \ @@ -1732,10 +1732,7 @@ see API KEY section of copr-cli(1) man page. print('The {0} submitted to the MBS' .format(ids_to_print)) def module_build_cancel(self): - """ - Cancel an MBS build - :return: None - """ + """Cancel an MBS build""" api_url = self.module_api_url build_id = self.args.build_id auth_method, oidc_id_provider, oidc_client_id, oidc_client_secret, \ @@ -1750,17 +1747,11 @@ see API KEY section of copr-cli(1) man page. print('The module build #{0} was cancelled'.format(build_id)) def module_build_info(self): - """ - Show information about an MBS build - :return: None - """ + """Show information about an MBS build""" self.cmd.module_build_info(self.module_api_url, self.args.build_id) def module_build_local(self): - """ - Build a module locally using mbs-manager - :return: None - """ + """Build a module locally using mbs-manager""" self.module_validate_config() if not self.args.stream: @@ -1791,12 +1782,13 @@ see API KEY section of copr-cli(1) man page. default_streams=self.args.default_streams) def module_get_auth_config(self): - """ - Get the authentication configuration for the MBS + """Get the authentication configuration for the MBS + :return: a tuple consisting of the authentication method, the OIDC ID - provider, the OIDC client ID, the OIDC client secret, and the OIDC - scopes. If the authentication method is not OIDC, the OIDC values in - the tuple are set to None. + provider, the OIDC client ID, the OIDC client secret, and the OIDC + scopes. If the authentication method is not OIDC, the OIDC values + in the tuple are set to None. + :rtype: tuple """ auth_method = self.config.get(self.config_section, 'auth_method') oidc_id_provider = None @@ -1866,25 +1858,19 @@ see API KEY section of copr-cli(1) man page. return self._module_api_url def module_build_watch(self): - """ - Watch an MBS build from the command-line - :return: None - """ + """Watch an MBS build from the command-line""" self.module_watch_build([self.args.build_id]) def module_overview(self): - """ - Show the overview of the latest builds in the MBS - :return: None - """ + """Show the overview of the latest builds in the MBS""" self.cmd.module_overview( self.module_api_url, self.args.limit, finished=(not self.args.unfinished)) def module_validate_config(self): - """ - Validates the configuration needed for MBS commands - :return: None or rpkgError + """Validates the configuration needed for MBS commands + + :raises: :py:exc:`pyrpkg.errors.rpkgError` """ self.config_section = '{0}.mbs'.format(self.name) # Verify that all necessary config options are set @@ -1929,8 +1915,9 @@ see API KEY section of copr-cli(1) man page. Watches the first MBS build in the list in a loop that updates every 15 seconds. The loop ends when the build state is 'failed', 'done', or 'ready'. + :param build_ids: a list of module build IDs - :return: None + :type build_ids: list[int] """ self.cmd.module_watch_build(self.module_api_url, build_ids) diff --git a/pyrpkg/gitignore.py b/pyrpkg/gitignore.py index db3f3a5..c81da3e 100644 --- a/pyrpkg/gitignore.py +++ b/pyrpkg/gitignore.py @@ -16,13 +16,13 @@ import os class GitIgnore(object): """A class to manage a .gitignore file""" + def __init__(self, path): """Constructor - Args: - path (str): The full path to the .gitignore file. If it does not - exist, the file will be created when running GitIgnore.write() - for the first time. + :param str path: The full path to the .gitignore file. If it does not + exist, the file will be created when running :py:meth:GitIgnore.write for + the first time. """ self.path = path @@ -45,9 +45,8 @@ class GitIgnore(object): def add(self, line): """Add a line - Args: - line (str): The line to add to the file. It will not be added if - it already matches an existing line. + :param str line: The line to add to the file. It will not be added if + it already matches an existing line. """ if self.match(line): return @@ -61,11 +60,9 @@ class GitIgnore(object): This uses fnmatch to match against wildcards. - Args: - line (str): The new line to match against existing ones. - - Returns: - True if the new line matches, False otherwise. + :param str line: The new line to match against existing ones. + :return: True if the new line matches, False otherwise. + :rtype: bool """ line = line.lstrip('/').rstrip('\n') diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py index 5ced6e3..9282618 100644 --- a/pyrpkg/lookaside.py +++ b/pyrpkg/lookaside.py @@ -33,18 +33,17 @@ class CGILookasideCache(object): client_cert=None, ca_cert=None): """Constructor - Args: - hashtype (str): The hash algorithm to use for uploads. (e.g 'md5') - download_url (str): The URL used to download source files. - upload_url (str): The URL of the CGI script called when uploading - source files. - client_cert (str, optional): The full path to the client-side - certificate to use for HTTPS authentication. It defaults to - None, in which case no client-side certificate is used. - ca_cert (str, optional): The full path to the CA certificate to - use for HTTPS connexions. (e.g if the server certificate is - self-signed. It defaults to None, in which case the system CA - bundle is used. + :param str hashtype: The hash algorithm to use for uploads. (e.g 'md5') + :param str download_url: The URL used to download source files. + :param str upload_url: The URL of the CGI script called when uploading + source files. + :param str client_cert: Optional. The full path to the client-side + certificate to use for HTTPS authentication. It defaults to None, + in which case no client-side certificate is used. + :param str ca_cert: Optional. The full path to the CA certificate to + use for HTTPS connexions. (e.g if the server certificate is + self-signed. It defaults to None, in which case the system CA + bundle is used. """ self.hashtype = hashtype self.download_url = download_url @@ -82,13 +81,10 @@ class CGILookasideCache(object): def hash_file(self, filename, hashtype=None): """Compute the hash of a file - Args: - filename (str): The full path to the file. It is assumed to exist. - hashtype (str, optional): The hash algorithm to use. (e.g 'md5') - This defaults to the hashtype passed to the constructor. - - Returns: - The hash digest. + :param str filename: The full path to the file. It is assumed to exist. + :param str hashtype: Optional. The hash algorithm to use. (e.g 'md5') + This defaults to the hashtype passed to the constructor. + :return: The hash digest. """ if hashtype is None: hashtype = self.hashtype @@ -111,14 +107,12 @@ class CGILookasideCache(object): def file_is_valid(self, filename, hash, hashtype=None): """Ensure the file is correct - Args: - filename (str): The full path to the file. It is assumed to exist. - hash (str): The known good hash of the file. - hashtype (str, optional): The hash algorithm to use. (e.g 'md5') - This defaults to the hashtype passed to the constructor. - - Returns: - True if the file is valid, False otherwise. + :param str filename: The full path to the file. It is assumed to exist. + :param str hash: The known good hash of the file. + :param str hashtype: Optional. The hash algorithm to use. (e.g 'md5') + This defaults to the hashtype passed to the constructor. + :return: True if the file is valid, False otherwise. + :rtype: bool """ sum = self.hash_file(filename, hashtype) return sum == hash @@ -142,17 +136,16 @@ class CGILookasideCache(object): def download(self, name, filename, hash, outfile, hashtype=None, **kwargs): """Download a source file - Args: - name (str): The name of the module. (usually the name of the SRPM). - This can include the namespace as well (depending on - what the server side expects). - filename (str): The name of the file to download. - hash (str): The known good hash of the file. - outfile (str): The full path where to save the downloaded file. - hashtype (str, optional): The hash algorithm. (e.g 'md5') - This defaults to the hashtype passed to the constructor. - **kwargs: Additional keyword arguments. They will be used when - constructing the full URL to the file to download. + :param str name: The name of the module. (usually the name of the + SRPM). This can include the namespace as well (depending on what + the server side expects). + :param str filename: The name of the file to download. + :param str hash: The known good hash of the file. + :param str outfile: The full path where to save the downloaded file. + :param str hashtype: Optional. The hash algorithm. (e.g 'md5') This + defaults to the hashtype passed to the constructor. + :param kwargs: Additional keyword arguments. They will be used when + constructing the full URL to the file to download. """ if hashtype is None: hashtype = self.hashtype @@ -207,12 +200,11 @@ class CGILookasideCache(object): def remote_file_exists(self, name, filename, hash): """Verify whether a file exists on the lookaside cache - Args: - name: The name of the module. (usually the name of the SRPM). - This can include the namespace as well (depending on - what the server side expects). - filename: The name of the file to check for. - hash: The known good hash of the file. + :param str name: The name of the module. (usually the name of the + SRPM). This can include the namespace as well (depending on what + the server side expects). + :param str filename: The name of the file to check for. + :param str hash: The known good hash of the file. """ # RHEL 7 ships pycurl that does not accept unicode. When given unicode @@ -279,12 +271,11 @@ class CGILookasideCache(object): def upload(self, name, filepath, hash): """Upload a source file - Args: - name (str): The name of the module. (usually the name of the SRPM) - This can include the namespace as well (depending on - what the server side expects). - filepath (str): The full path to the file to upload. - hash (str): The known good hash of the file. + :param str name: The name of the module. (usually the name of the SRPM) + This can include the namespace as well (depending on what the + server side expects). + :param str filepath: The full path to the file to upload. + :param str hash: The known good hash of the file. """ filename = os.path.basename(filepath) diff --git a/pyrpkg/utils.py b/pyrpkg/utils.py index ab78cf7..2ce469f 100644 --- a/pyrpkg/utils.py +++ b/pyrpkg/utils.py @@ -63,12 +63,11 @@ class cached_property(property): def warn_deprecated(clsname, oldname, newname): """Emit a deprecation warning - Args: - clsname (str): The name of the class which has its attribute - deprecated. - oldname (str): The name of the deprecated attribute. - newname (str): The name of the new attribute, which should be used - instead. + :param str clsname: The name of the class which has its attribute + deprecated. + :param str oldname: The name of the deprecated attribute. + :param str newname: The name of the new attribute, which should be used + instead. """ sys.stderr.write( "DeprecationWarning: %s.%s is deprecated and will be removed eventually.\n"