diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 6886e90..0000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -exclude = .venv/,.tox/,dist/,build/,doc/,.eggs/ -format = pylint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ef4d5bf..969e877 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,14 +8,20 @@ on: jobs: build: runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.6, 3.7] steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install tox tox-gh-actions - name: Test with tox run: | - pip install tox tox diff --git a/.gitignore b/.gitignore index be6d5ab..e1fc810 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ *.egg-info/ -*.py[cod] +*.py[rcod] .cache/ .coverage .eggs/ .tox/ +.venv/ +build/ +coverage.* +dist/ doc/build/ +htmlcov/ +pip-wheel-metadata/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..66460d4 --- /dev/null +++ b/.python-version @@ -0,0 +1,2 @@ +3.7.6 +3.6.7 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e72b929..60c0f65 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -36,7 +36,7 @@ Unit tests are invoked by `Tox`_. Formatting ========== -The formatting is done using `YAPF`_. +The formatting is done using `Black`_. From the root for the project, run: @@ -44,6 +44,6 @@ From the root for the project, run: $ tox -e format -.. _`YAPF`: https://github.com/google/yapf +.. _`Black`: https://github.com/psf/black .. _`Tox`: https://tox.readthedocs.io/en/latest .. _`Issue`: https://github.com/metacloud/gilt/issues diff --git a/doc/source/conf.py b/doc/source/conf.py index 71215d3..11c4923 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -24,42 +24,42 @@ # 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.coverage', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'alabaster', + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "alabaster", ] # 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 = u'gilt' -copyright = u' %s, Cisco Systems, Inc.' % datetime.date.today().year -author = u'AUTHORS.md' +project = u"gilt" +copyright = u" %s, Cisco Systems, Inc." % datetime.date.today().year +author = u"AUTHORS.md" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -79,9 +79,9 @@ # 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. @@ -89,122 +89,118 @@ # 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 = True # List of warnings to suppress. -suppress_warnings = ['image.nonlocal_uri'] +suppress_warnings = ["image.nonlocal_uri"] # -- 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 = { - 'logo': 'logo.png', - 'github_user': 'metacloud', - 'github_repo': 'gilt', - 'github_button': True, - 'travis_button': False, - 'show_powered_by': False, - 'extra_nav_links': { - 'View on github': 'https://github.com/metacloud/gilt', + "logo": "logo.png", + "github_user": "metacloud", + "github_repo": "gilt", + "github_button": True, + "travis_button": False, + "show_powered_by": False, + "extra_nav_links": { + "View on github": "https://github.com/metacloud/gilt", }, } # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] html_theme_path = [alabaster.get_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # 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 (within the static path) to use as 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 '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # 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 = {} html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'searchbox.html', - ], + "**": ["about.html", "navigation.html", "searchbox.html",], } # 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 = False # 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 = False @@ -212,84 +208,77 @@ # If true, an OpenSearch description file will be output, and all pages will # contain a 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', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#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 = 'giltdoc' +htmlhelp_basename = "giltdoc" # -- 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, 'gilt.tex', u'gilt Documentation', - u'AUTHORS.md', 'manual'), + (master_doc, "gilt.tex", u"gilt Documentation", u"AUTHORS.md", "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, 'gilt', u'gilt Documentation', - [author], 1) -] +man_pages = [(master_doc, "gilt", u"gilt Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -298,19 +287,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'gilt', u'gilt Documentation', - author, 'AUTHORS.md', 'A restful port reservation microservice', - 'Miscellaneous'), + ( + master_doc, + "gilt", + u"gilt Documentation", + author, + "AUTHORS.md", + "A restful port reservation microservice", + "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 diff --git a/doc/source/development.rst b/doc/source/development.rst index 2f58a13..4724c13 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -37,21 +37,12 @@ Tag the release and push to github.com Upload to `PyPI`_ ^^^^^^^^^^^^^^^^^ -* Install `Twine`_ using `pip`. - - .. code-block:: bash - - $ pip install twine - * Upload to `PyPI`_. .. code-block:: bash - $ cd /path/to/gilt - $ rm -rf build/ dist/ - $ python setup.py sdist bdist_wheel - $ twine upload dist/* - $ rm -rf build/ dist/ + $ tox -e build-dists + $ tox -e publish-dists Post-release ------------ @@ -65,4 +56,3 @@ Roadmap .. _`PyPI`: https://pypi.python.org/pypi/python-gilt .. _`ISSUES`: https://github.com/metacloud/gilt/issues -.. _`Twine`: https://pypi.python.org/pypi/twine diff --git a/gilt/__init__.py b/gilt/__init__.py index 6975223..2d393e2 100644 --- a/gilt/__init__.py +++ b/gilt/__init__.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -20,7 +18,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import pbr.version +try: + import pkg_resources -version_info = pbr.version.VersionInfo('python-gilt') # noqa -__version__ = version_info.release_string() + __version__ = pkg_resources.get_distribution("gilt").version +except Exception: # pragma: no cover + __version__ = "unknown" diff --git a/gilt/config.py b/gilt/config.py index a170df8..adf9d59 100644 --- a/gilt/config.py +++ b/gilt/config.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -31,45 +29,48 @@ class ParseError(Exception): - """ Error raised when a config can't be loaded properly. """ + """Error raised when a config can't be loaded properly. """ + pass -BASE_WORKING_DIR = os.environ.get('GILT_CACHE_DIRECTORY', '~/.gilt') +BASE_WORKING_DIR = os.environ.get("GILT_CACHE_DIRECTORY", "~/.gilt") def config(filename): - """ - Construct `Config` object and return a list. + """Construct `Config` object and return a list. :parse filename: A string containing the path to YAML file. :return: list """ - Config = collections.namedtuple('Config', [ - 'git', - 'lock_file', - 'version', - 'name', - 'src', - 'dst', - 'files', - 'post_commands', - ]) + Config = collections.namedtuple( + "Config", + [ + "git", + "lock_file", + "version", + "name", + "src", + "dst", + "files", + "post_commands", + ], + ) return [Config(**d) for d in _get_config_generator(filename)] def _get_files_config(src_dir, files_list): - """ - Construct `FileConfig` object and return a list. + """Construct `FileConfig` object and return a list. :param src_dir: A string containing the source directory. :param files_list: A list of dicts containing the src/dst mapping of files to overlay. :return: list """ - FilesConfig = collections.namedtuple('FilesConfig', - ['src', 'dst', 'post_commands']) + FilesConfig = collections.namedtuple( + "FilesConfig", ["src", "dst", "post_commands"] + ) return [ FilesConfig(**d) for d in _get_files_generator(src_dir, files_list) @@ -77,37 +78,35 @@ def _get_files_config(src_dir, files_list): def _get_config_generator(filename): - """ - A generator which populates and return a dict. + """A generator which populates and return a dict. :parse filename: A string containing the path to YAML file. :return: dict """ for d in _get_config(filename): - repo = d['git'] + repo = d["git"] parsedrepo = giturlparse.parse(repo) - name = '{}.{}'.format(parsedrepo.owner, parsedrepo.name) + name = "{}.{}".format(parsedrepo.owner, parsedrepo.name) src_dir = os.path.join(_get_clone_dir(), name) - files = d.get('files') - post_commands = d.get('post_commands', []) + files = d.get("files") + post_commands = d.get("post_commands", []) dst_dir = None if not files: - dst_dir = _get_dst_dir(d['dst']) + dst_dir = _get_dst_dir(d["dst"]) yield { - 'git': repo, - 'lock_file': _get_lock_file(name), - 'version': d['version'], - 'name': name, - 'src': src_dir, - 'dst': dst_dir, - 'files': _get_files_config(src_dir, files), - 'post_commands': post_commands, + "git": repo, + "lock_file": _get_lock_file(name), + "version": d["version"], + "name": name, + "src": src_dir, + "dst": dst_dir, + "files": _get_files_config(src_dir, files), + "post_commands": post_commands, } def _get_files_generator(src_dir, files_list): - """ - A generator which populates and return a dict. + """A generator which populates and return a dict. :param src_dir: A string containing the source directory. :param files_list: A list of dicts containing the src/dst mapping of files @@ -117,35 +116,33 @@ def _get_files_generator(src_dir, files_list): if files_list: for d in files_list: yield { - 'src': os.path.join(src_dir, d['src']), - 'dst': _get_dst_dir(d['dst']), - 'post_commands': d.get('post_commands', []), + "src": os.path.join(src_dir, d["src"]), + "dst": _get_dst_dir(d["dst"]), + "post_commands": d.get("post_commands", []), } def _get_config(filename): - """ - Parse the provided YAML file and return a dict. + """Parse the provided YAML file and return a dict. :parse filename: A string containing the path to YAML file. :return: dict """ - i = interpolation.Interpolator(interpolation.TemplateWithDefaults, - os.environ) + i = interpolation.Interpolator( + interpolation.TemplateWithDefaults, os.environ + ) - with open(filename, 'r') as stream: + with open(filename, "r") as stream: try: interpolated_config = i.interpolate(stream.read()) return yaml.safe_load(interpolated_config) except yaml.parser.ParserError as e: - msg = 'Error parsing gilt config: {0}'.format(e) + msg = "Error parsing gilt config: {0}".format(e) raise ParseError(msg) def _get_dst_dir(dst_dir): - """ - Prefix the provided string with working directory and return a - str. + """Prefix the provided string with working directory and return a str. :param dst_dir: A string to be prefixed with the working dir. :return: str @@ -157,42 +154,33 @@ def _get_dst_dir(dst_dir): def _get_lock_file(name): - """ Return the lock file for the given name. """ - return os.path.join( - _get_lock_dir(), - name, ) + """Return the lock file for the given name. """ + return os.path.join(_get_lock_dir(), name,) def _get_base_dir(): - """ Return gilt's base working directory. """ + """Return gilt's base working directory. """ return os.path.expanduser(BASE_WORKING_DIR) def _get_lock_dir(): - """ - Construct gilt's lock directory and return a str. + """Construct gilt's lock directory and return a str. :return: str """ - return os.path.join( - _get_base_dir(), - 'lock', ) + return os.path.join(_get_base_dir(), "lock",) def _get_clone_dir(): - """ - Construct gilt's clone directory and return a str. + """Construct gilt's clone directory and return a str. :return: str """ - return os.path.join( - _get_base_dir(), - 'clone', ) + return os.path.join(_get_base_dir(), "clone",) def _makedirs(path): - """ - Create a base directory of the provided path and return None. + """Create a base directory of the provided path and return None. :param path: A string containing a path to be deconstructed and basedir created. diff --git a/gilt/git.py b/gilt/git.py index a15530c..c309858 100644 --- a/gilt/git.py +++ b/gilt/git.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,8 +28,7 @@ def clone(name, repository, destination, debug=False): - """ - Clone the specified repository into a temporary directory and return None. + """Clone the specified repository into a temporary directory and return None. :param name: A string containing the name of the repository being cloned. :param repository: A string containing the repository to clone. @@ -40,16 +37,14 @@ def clone(name, repository, destination, debug=False): :param debug: An optional bool to toggle debug output. :return: None """ - msg = ' - cloning {} to {}'.format(name, destination) + msg = " - cloning {} to {}".format(name, destination) util.print_info(msg) - cmd = sh.git.bake('clone', repository, destination) + cmd = sh.git.bake("clone", repository, destination) util.run_command(cmd, debug=debug) def extract(repository, destination, version, debug=False): - """ - Extract the specified repository/version into the given directory and - return None. + """Extract the specified repository/version into the directory and return None. :param repository: A string containing the path to the repository to be extracted. @@ -67,17 +62,17 @@ def extract(repository, destination, version, debug=False): os.chdir(repository) _get_version(version, debug) cmd = sh.git.bake( - 'checkout-index', force=True, all=True, prefix=destination) + "checkout-index", force=True, all=True, prefix=destination + ) util.run_command(cmd, debug=debug) - msg = ' - extracting ({}) {} to {}'.format(version, repository, - destination) + msg = " - extracting ({}) {} to {}".format( + version, repository, destination + ) util.print_info(msg) def overlay(repository, files, version, debug=False): - """ - Overlay files from the specified repository/version into the given - directory and return None. + """Overlay files from repository/version into the directory and return None. :param repository: A string containing the path to the repository to be extracted. @@ -91,24 +86,25 @@ def overlay(repository, files, version, debug=False): _get_version(version, debug) for fc in files: - if '*' in fc.src: + if "*" in fc.src: for filename in glob.glob(fc.src): util.copy(filename, fc.dst) - msg = ' - copied ({}) {} to {}'.format( - version, filename, fc.dst) + msg = " - copied ({}) {} to {}".format( + version, filename, fc.dst + ) util.print_info(msg) else: if os.path.isdir(fc.dst) and os.path.isdir(fc.src): shutil.rmtree(fc.dst) util.copy(fc.src, fc.dst) - msg = ' - copied ({}) {} to {}'.format( - version, fc.src, fc.dst) + msg = " - copied ({}) {} to {}".format( + version, fc.src, fc.dst + ) util.print_info(msg) def _get_version(version, debug=False): - """ - Handle switching to the specified version and return None. + """Handle switching to the specified version and return None. 1. Fetch the origin. 2. Checkout the specified version. @@ -120,22 +116,25 @@ def _get_version(version, debug=False): :return: None """ if not any( - (_has_branch(version, debug), _has_tag(version, debug), _has_commit( - version, debug))): - cmd = sh.git.bake('fetch') + ( + _has_branch(version, debug), + _has_tag(version, debug), + _has_commit(version, debug), + ) + ): + cmd = sh.git.bake("fetch") util.run_command(cmd, debug=debug) - cmd = sh.git.bake('checkout', version) + cmd = sh.git.bake("checkout", version) util.run_command(cmd, debug=debug) - cmd = sh.git.bake('clean', '-d', '-x', '-f') + cmd = sh.git.bake("clean", "-d", "-x", "-f") util.run_command(cmd, debug=debug) if _has_branch(version, debug): - cmd = sh.git.bake('pull', rebase=True, ff_only=True) + cmd = sh.git.bake("pull", rebase=True, ff_only=True) util.run_command(cmd, debug=debug) def _has_commit(version, debug=False): - """ - Determine a version is a local git commit sha or not. + """Determine a version is a local git commit sha or not. :param version: A string containing the branch/tag/sha to be determined. :param debug: An optional bool to toggle debug output. @@ -143,7 +142,7 @@ def _has_commit(version, debug=False): """ if _has_tag(version, debug) or _has_branch(version, debug): return False - cmd = sh.git.bake('cat-file', '-e', version) + cmd = sh.git.bake("cat-file", "-e", version) try: util.run_command(cmd, debug=debug) return True @@ -152,15 +151,15 @@ def _has_commit(version, debug=False): def _has_tag(version, debug=False): - """ - Determine a version is a local git tag name or not. + """Determine a version is a local git tag name or not. :param version: A string containing the branch/tag/sha to be determined. :param debug: An optional bool to toggle debug output. :return: bool """ - cmd = sh.git.bake('show-ref', '--verify', '--quiet', - "refs/tags/{}".format(version)) + cmd = sh.git.bake( + "show-ref", "--verify", "--quiet", "refs/tags/{}".format(version) + ) try: util.run_command(cmd, debug=debug) return True @@ -169,15 +168,15 @@ def _has_tag(version, debug=False): def _has_branch(version, debug=False): - """ - Determine a version is a local git branch name or not. + """Determine a version is a local git branch name or not. :param version: A string containing the branch/tag/sha to be determined. :param debug: An optional bool to toggle debug output. :return: bool """ - cmd = sh.git.bake('show-ref', '--verify', '--quiet', - "refs/heads/{}".format(version)) + cmd = sh.git.bake( + "show-ref", "--verify", "--quiet", "refs/heads/{}".format(version) + ) try: util.run_command(cmd, debug=debug) return True diff --git a/gilt/interpolation.py b/gilt/interpolation.py index 2fa3723..fe4f1a3 100644 --- a/gilt/interpolation.py +++ b/gilt/interpolation.py @@ -56,26 +56,26 @@ def interpolate(self, string): class TemplateWithDefaults(string.Template): - idpattern = r'[_a-z][_a-z0-9]*(?::?-[^}]+)?' + idpattern = r"[_a-z][_a-z0-9]*(?::?-[^}]+)?" # Modified from python2.7/string.py def substitute(self, mapping): # Helper function for .sub() def convert(mo): # Check the most common path first. - named = mo.group('named') or mo.group('braced') + named = mo.group("named") or mo.group("braced") if named is not None: - if ':-' in named: - var, _, default = named.partition(':-') + if ":-" in named: + var, _, default = named.partition(":-") return mapping.get(var) or default - if '-' in named: - var, _, default = named.partition('-') + if "-" in named: + var, _, default = named.partition("-") return mapping.get(var, default) - val = mapping.get(named, '') - return '%s' % (val, ) - if mo.group('escaped') is not None: + val = mapping.get(named, "") + return "%s" % (val,) + if mo.group("escaped") is not None: return self.delimiter - if mo.group('invalid') is not None: + if mo.group("invalid") is not None: self._invalid(mo) return self.pattern.sub(convert, self.template) diff --git a/gilt/shell.py b/gilt/shell.py index ef5b589..72d3bb5 100644 --- a/gilt/shell.py +++ b/gilt/shell.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -32,41 +30,44 @@ class NotFoundError(Exception): - """ Error raised when a config can not be found. """ + """Error raised when a config can not be found. """ + pass @click.group() @click.option( - '--config', - default='gilt.yml', - help='Path to config file. Default gilt.yml') + "--config", + default="gilt.yml", + help="Path to config file. Default gilt.yml", +) @click.option( - '--debug/--no-debug', + "--debug/--no-debug", default=False, - help='Enable or disable debug mode. Default is disabled.') + help="Enable or disable debug mode. Default is disabled.", +) @click.version_option(version=gilt.__version__) @click.pass_context def main(ctx, config, debug): # pragma: no cover - """ gilt - A GIT layering tool. """ + """gilt - A GIT layering tool. """ ctx.obj = {} - ctx.obj['args'] = {} - ctx.obj['args']['debug'] = debug - ctx.obj['args']['config'] = config + ctx.obj["args"] = {} + ctx.obj["args"]["debug"] = debug + ctx.obj["args"]["config"] = config @click.command() @click.pass_context def overlay(ctx): # pragma: no cover - """ Install gilt dependencies """ - args = ctx.obj.get('args') - filename = args.get('config') - debug = args.get('debug') + """Install gilt dependencies """ + args = ctx.obj.get("args") + filename = args.get("config") + debug = args.get("debug") _setup(filename) for c in config.config(filename): with fasteners.InterProcessLock(c.lock_file): - util.print_info('{}:'.format(c.name)) + util.print_info("{}:".format(c.name)) if not os.path.exists(c.src): git.clone(c.name, c.git, c.src, debug=debug) if c.dst: @@ -75,13 +76,12 @@ def overlay(ctx): # pragma: no cover else: git.overlay(c.src, c.files, c.version, debug=debug) post_commands = { - conf.dst: conf.post_commands - for conf in c.files + conf.dst: conf.post_commands for conf in c.files } # Run post commands if any. for dst, commands in post_commands.items(): for command in commands: - msg = ' - running `{}` in {}'.format(command, dst) + msg = " - running `{}` in {}".format(command, dst) util.print_info(msg) cmd = util.build_sh_cmd(command, cwd=dst) util.run_command(cmd, debug=debug) @@ -89,7 +89,7 @@ def overlay(ctx): # pragma: no cover def _setup(filename): if not os.path.exists(filename): - msg = 'Unable to find {}. Exiting.'.format(filename) + msg = "Unable to find {}. Exiting.".format(filename) raise NotFoundError(msg) working_dirs = [config._get_lock_dir(), config._get_clone_dir()] diff --git a/gilt/util.py b/gilt/util.py index 5763cda..9f2354f 100644 --- a/gilt/util.py +++ b/gilt/util.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -34,27 +32,26 @@ def print_info(msg): - """ Print the given message to STDOUT. """ + """Print the given message to STDOUT. """ print(msg) def print_warn(msg): - """ Print the given message to STDOUT in YELLOW. """ - print('{}{}'.format(colorama.Fore.YELLOW, msg)) + """Print the given message to STDOUT in YELLOW. """ + print("{}{}".format(colorama.Fore.YELLOW, msg)) def run_command(cmd, debug=False): - """ - Execute the given command and return None. + """Execute the given command and return None. :param cmd: A `sh.Command` object to execute. :param debug: An optional bool to toggle debug output. :return: None """ if debug: - msg = ' PWD: {}'.format(os.getcwd()) + msg = " PWD: {}".format(os.getcwd()) print_warn(msg) - msg = ' COMMAND: {}'.format(cmd) + msg = " COMMAND: {}".format(cmd) print_warn(msg) cmd() @@ -72,7 +69,7 @@ def build_sh_cmd(cmd, cwd=None): @contextlib.contextmanager def saved_cwd(): - """ Context manager to restore previous working directory. """ + """Context manager to restore previous working directory. """ saved = os.getcwd() try: yield @@ -81,8 +78,7 @@ def saved_cwd(): def copy(src, dst): - """ - Handle the copying of a file or directory. + """Handle the copying of a file or directory. The destination basedir _must_ exist. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9e54c26 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = [ + 'setuptools >= 41.0.0', + 'setuptools_scm >= 1.15.0', + 'setuptools_scm_git_archive >= 1.0', + 'wheel', +] +build-backend = 'setuptools.build_meta' + +[tool.black] +line-length = 79 +include = '\.pyi?$' +skip-string-normalization = true diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9364580..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -click -colorama -fasteners -git-url-parse -pbr -PyYAML -sh diff --git a/setup.cfg b/setup.cfg index e91dce8..3ebe00b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,16 @@ +[aliases] +dists = clean --all sdist bdist_wheel + +[bdist_wheel] +universal = 1 + [metadata] name = python-gilt summary = gilt - A GIT layering tool. description-file = README.rst author = John Dewey -author-email = jodewey@cisco.com -home-page = https://github.com/metacloud/gilt +author-email = john@dewey.ws +home-page = https://github.com/retr0h/gilt classifier = Development Status :: 4 - Beta Environment :: Console @@ -15,29 +21,52 @@ classifier = License :: OSI Approved :: MIT License Natural Language :: English Operating System :: OS Independent - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 Topic :: System :: Systems Administration Topic :: Utilities -[pbr] -skip_authors = True -skip_changelog = True -warnerrors = True +[options] +use_scm_version = True +python_requires = >=3.6 +packages = find: +include_package_data = True +zip_safe = False + +# These are required during `setup.py` run: +setup_requires = + setuptools_scm >= 1.15.0 + setuptools_scm_git_archive >= 1.0 + +# These are required in actual runtime: +install_requires = + click + colorama + fasteners + git-url-parse + PyYAML + sh -[global] -setup-hooks = - pbr.hooks.setup_hook +[options.extras_require] +# These are required during test: +test = + black==19.10b0 + flake8 + hacking + pep517 + pytest + pytest-cov + pytest-helpers-namespace + pytest-mock + twine -[entry_points] +[options.entry_points] console_scripts = gilt = gilt.shell:main -[files] -packages = - gilt +[options.packages.find] +where = . [build_sphinx] all_files = 1 diff --git a/setup.py b/setup.py index 9793a62..ca3c2b5 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,4 +20,7 @@ import setuptools -setuptools.setup(setup_requires=['pbr'], pbr=True) +if __name__ == "__main__": + setuptools.setup( + use_scm_version=True, setup_requires=["setuptools_scm"], + ) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index cbe8719..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -flake8 -pytest -pytest-cov -pytest-helpers-namespace -pytest-mock -yapf==0.16.3 diff --git a/test/conftest.py b/test/conftest.py index 13065f6..53988ed 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,7 +25,7 @@ import pytest -pytest_plugins = ['helpers_namespace'] +pytest_plugins = ["helpers_namespace"] def pytest_addoption(parser): @@ -35,7 +33,7 @@ def pytest_addoption(parser): def random_string(len=5): - return ''.join(random.choice(string.ascii_uppercase) for _ in range(len)) + return "".join(random.choice(string.ascii_uppercase) for _ in range(len)) @pytest.fixture() @@ -55,7 +53,7 @@ def cleanup(): def gilt_config_file(temp_dir, request): fixture = request.param d = temp_dir - c = d.join(os.extsep.join(('gilt', 'yml'))) + c = d.join(os.extsep.join(("gilt", "yml"))) c.write(request.getfixturevalue(fixture)) return c.strpath @@ -63,24 +61,24 @@ def gilt_config_file(temp_dir, request): @pytest.fixture() def gilt_data(): - return [{ - 'git': 'https://github.com/retr0h/ansible-etcd.git', - 'version': 'master', - 'dst': 'roles/retr0h.ansible-etcd/' - }, { - 'git': 'https://github.com/lorin/openstack-ansible-modules.git', - 'version': 'master', - 'files': [{ - 'src': '*_manage', - 'dst': 'library/' - }] - }] + return [ + { + "git": "https://github.com/retr0h/ansible-etcd.git", + "version": "master", + "dst": "roles/retr0h.ansible-etcd/", + }, + { + "git": "https://github.com/lorin/openstack-ansible-modules.git", + "version": "master", + "files": [{"src": "*_manage", "dst": "library/"}], + }, + ] @pytest.helpers.register def os_split(s): rest, tail = os.path.split(s) - if rest in ('', os.path.sep): - return tail, + if rest in ("", os.path.sep): + return (tail,) - return os_split(rest) + (tail, ) + return os_split(rest) + (tail,) diff --git a/test/test_config.py b/test/test_config.py index 11c9b34..f29d268 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,43 +26,45 @@ @pytest.mark.parametrize( - 'gilt_config_file', ['gilt_data'], indirect=['gilt_config_file']) + "gilt_config_file", ["gilt_data"], indirect=["gilt_config_file"] +) def test_config(gilt_config_file): result = config.config(gilt_config_file) os_split = pytest.helpers.os_split gilt_root = os.path.basename(config.BASE_WORKING_DIR) r = result[0] - assert 'https://github.com/retr0h/ansible-etcd.git' == r.git - assert 'master' == r.version - assert 'retr0h.ansible-etcd' == r.name - assert (gilt_root, 'clone', 'retr0h.ansible-etcd') == os_split(r.src)[-3:] - assert (gilt_root, 'lock', - 'retr0h.ansible-etcd') == os_split(r.lock_file)[-3:] - assert ('roles', 'retr0h.ansible-etcd', '') == os_split(r.dst)[-3:] + assert "https://github.com/retr0h/ansible-etcd.git" == r.git + assert "master" == r.version + assert "retr0h.ansible-etcd" == r.name + assert (gilt_root, "clone", "retr0h.ansible-etcd") == os_split(r.src)[-3:] + assert (gilt_root, "lock", "retr0h.ansible-etcd") == os_split(r.lock_file)[ + -3: + ] + assert ("roles", "retr0h.ansible-etcd", "") == os_split(r.dst)[-3:] assert [] == r.files r = result[1] - assert 'https://github.com/lorin/openstack-ansible-modules.git' == r.git - assert 'master' == r.version - assert 'lorin.openstack-ansible-modules' == r.name - assert 'lorin.openstack-ansible-modules' == os_split(r.src)[-1] + assert "https://github.com/lorin/openstack-ansible-modules.git" == r.git + assert "master" == r.version + assert "lorin.openstack-ansible-modules" == r.name + assert "lorin.openstack-ansible-modules" == os_split(r.src)[-1] assert r.dst is None f = r.files[0] - x = (gilt_root, 'clone', 'lorin.openstack-ansible-modules', '*_manage') + x = (gilt_root, "clone", "lorin.openstack-ansible-modules", "*_manage") assert x == os_split(f.src)[-4:] - assert ('library', '') == os_split(f.dst)[-2:] + assert ("library", "") == os_split(f.dst)[-2:] @pytest.fixture() def missing_git_key_data(): - return [{'foo': 'https://github.com/retr0h/ansible-etcd.git'}] + return [{"foo": "https://github.com/retr0h/ansible-etcd.git"}] @pytest.mark.parametrize( - 'gilt_config_file', ['missing_git_key_data'], - indirect=['gilt_config_file']) + "gilt_config_file", ["missing_git_key_data"], indirect=["gilt_config_file"] +) def test_config_missing_git_key(gilt_config_file): with pytest.raises(KeyError): config.config(gilt_config_file) @@ -72,15 +72,16 @@ def test_config_missing_git_key(gilt_config_file): @pytest.fixture() def missing_version_key_data(): - return [{ - 'git': 'https://github.com/retr0h/ansible-etcd.git', - 'foo': 'master' - }] + return [ + {"git": "https://github.com/retr0h/ansible-etcd.git", "foo": "master"} + ] @pytest.mark.parametrize( - 'gilt_config_file', ['missing_version_key_data'], - indirect=['gilt_config_file']) + "gilt_config_file", + ["missing_version_key_data"], + indirect=["gilt_config_file"], +) def test_config_missing_version_key(gilt_config_file): with pytest.raises(KeyError): config.config(gilt_config_file) @@ -88,16 +89,18 @@ def test_config_missing_version_key(gilt_config_file): @pytest.fixture() def missing_dst_key_data(): - return [{ - 'git': 'https://github.com/retr0h/ansible-etcd.git', - 'version': 'master', - 'foo': 'roles/retr0h.ansible-etcd/' - }] + return [ + { + "git": "https://github.com/retr0h/ansible-etcd.git", + "version": "master", + "foo": "roles/retr0h.ansible-etcd/", + } + ] @pytest.mark.parametrize( - 'gilt_config_file', ['missing_dst_key_data'], - indirect=['gilt_config_file']) + "gilt_config_file", ["missing_dst_key_data"], indirect=["gilt_config_file"] +) def test_config_missing_dst_key(gilt_config_file): with pytest.raises(KeyError): config.config(gilt_config_file) @@ -105,19 +108,20 @@ def test_config_missing_dst_key(gilt_config_file): @pytest.fixture() def missing_files_src_key_data(): - return [{ - 'git': 'https://github.com/lorin/openstack-ansible-modules.git', - 'version': 'master', - 'files': [{ - 'foo': '*_manage', - 'dst': 'library/' - }] - }] + return [ + { + "git": "https://github.com/lorin/openstack-ansible-modules.git", + "version": "master", + "files": [{"foo": "*_manage", "dst": "library/"}], + } + ] @pytest.mark.parametrize( - 'gilt_config_file', ['missing_files_src_key_data'], - indirect=['gilt_config_file']) + "gilt_config_file", + ["missing_files_src_key_data"], + indirect=["gilt_config_file"], +) def test_config_missing_files_src_key(gilt_config_file): with pytest.raises(KeyError): config.config(gilt_config_file) @@ -125,26 +129,28 @@ def test_config_missing_files_src_key(gilt_config_file): @pytest.fixture() def missing_files_dst_key_data(): - return [{ - 'git': 'https://github.com/lorin/openstack-ansible-modules.git', - 'version': 'master', - 'files': [{ - 'src': '*_manage', - 'foo': 'library/' - }] - }] + return [ + { + "git": "https://github.com/lorin/openstack-ansible-modules.git", + "version": "master", + "files": [{"src": "*_manage", "foo": "library/"}], + } + ] @pytest.mark.parametrize( - 'gilt_config_file', ['missing_files_dst_key_data'], - indirect=['gilt_config_file']) + "gilt_config_file", + ["missing_files_dst_key_data"], + indirect=["gilt_config_file"], +) def test_config_missing_files_dst_key(gilt_config_file): with pytest.raises(KeyError): config.config(gilt_config_file) @pytest.mark.parametrize( - 'gilt_config_file', ['gilt_data'], indirect=['gilt_config_file']) + "gilt_config_file", ["gilt_data"], indirect=["gilt_config_file"] +) def test_get_config_generator(gilt_config_file): result = [i for i in config._get_config_generator(gilt_config_file)] @@ -153,15 +159,16 @@ def test_get_config_generator(gilt_config_file): def test_get_files_generator(temp_dir): - files_list = [{'src': 'foo', 'dst': 'bar/'}] - result = [i for i in config._get_files_generator('/tmp/dir', files_list)] + files_list = [{"src": "foo", "dst": "bar/"}] + result = [i for i in config._get_files_generator("/tmp/dir", files_list)] assert isinstance(result, list) assert isinstance(result[0], dict) @pytest.mark.parametrize( - 'gilt_config_file', ['gilt_data'], indirect=['gilt_config_file']) + "gilt_config_file", ["gilt_data"], indirect=["gilt_config_file"] +) def test_get_config(gilt_config_file): result = config._get_config(gilt_config_file) @@ -171,11 +178,12 @@ def test_get_config(gilt_config_file): @pytest.fixture() def invalid_gilt_data(): - return '{' + return "{" @pytest.mark.parametrize( - 'gilt_config_file', ['invalid_gilt_data'], indirect=['gilt_config_file']) + "gilt_config_file", ["invalid_gilt_data"], indirect=["gilt_config_file"] +) def test_get_config_handles_parse_error(gilt_config_file): with pytest.raises(config.ParseError): config._get_config(gilt_config_file) @@ -183,21 +191,21 @@ def test_get_config_handles_parse_error(gilt_config_file): def test_get_dst_dir(temp_dir): os.chdir(temp_dir.strpath) - result = config._get_dst_dir('roles/foo') + result = config._get_dst_dir("roles/foo") - assert os.path.join(temp_dir.strpath, 'roles', 'foo') == result + assert os.path.join(temp_dir.strpath, "roles", "foo") == result def test_get_clone_dir(): parts = pytest.helpers.os_split(config._get_clone_dir()) gilt_root = os.path.basename(config.BASE_WORKING_DIR) - assert (gilt_root, 'clone') == parts[-2:] + assert (gilt_root, "clone") == parts[-2:] def test_makedirs(temp_dir): - config._makedirs('foo/') + config._makedirs("foo/") - d = os.path.join(temp_dir.strpath, 'foo') + d = os.path.join(temp_dir.strpath, "foo") assert os.path.isdir(d) curmask = os.umask(0) @@ -208,33 +216,33 @@ def test_makedirs(temp_dir): def test_makedirs_nested_directory(temp_dir): - config._makedirs('foo/bar/') + config._makedirs("foo/bar/") - d = os.path.join(temp_dir.strpath, 'foo', 'bar') + d = os.path.join(temp_dir.strpath, "foo", "bar") assert os.path.isdir(d) def test_makedirs_basedir(temp_dir): - config._makedirs('foo/filename.py') + config._makedirs("foo/filename.py") - d = os.path.join(temp_dir.strpath, 'foo') + d = os.path.join(temp_dir.strpath, "foo") assert os.path.isdir(d) def test_makedirs_nested_basedir(temp_dir): - config._makedirs('foo/bar/filename.py') + config._makedirs("foo/bar/filename.py") - d = os.path.join(temp_dir.strpath, 'foo', 'bar') + d = os.path.join(temp_dir.strpath, "foo", "bar") assert os.path.isdir(d) def test_makedirs_passes_if_exists(temp_dir): - d = os.path.join(temp_dir.strpath, 'foo') + d = os.path.join(temp_dir.strpath, "foo") os.mkdir(d) - config._makedirs('foo/') + config._makedirs("foo/") def test_makedirs_raises(temp_dir): with pytest.raises(OSError): - config._makedirs('') + config._makedirs("") diff --git a/test/test_git.py b/test/test_git.py index cdbde74..07c56db 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -31,8 +29,8 @@ @pytest.mark.slow def test_clone(temp_dir): - name = 'retr0h.ansible-etcd' - repo = 'https://github.com/retr0h/ansible-etcd.git' + name = "retr0h.ansible-etcd" + repo = "https://github.com/retr0h/ansible-etcd.git" destination = os.path.join(temp_dir.strpath, name) git.clone(name, repo, destination) @@ -42,186 +40,180 @@ def test_clone(temp_dir): @pytest.mark.slow def test_extract(temp_dir): - name = 'retr0h.ansible-etcd' - repo = 'https://github.com/retr0h/ansible-etcd.git' - branch = 'gilt' - clone_dir = os.path.join(temp_dir.strpath, 'clone_dir') - extract_dir = os.path.join(temp_dir.strpath, 'extract_dir') + name = "retr0h.ansible-etcd" + repo = "https://github.com/retr0h/ansible-etcd.git" + branch = "gilt" + clone_dir = os.path.join(temp_dir.strpath, "clone_dir") + extract_dir = os.path.join(temp_dir.strpath, "extract_dir") os.mkdir(clone_dir) os.mkdir(extract_dir) clone_destination = os.path.join(clone_dir, name) - extract_destination = os.path.join(extract_dir, name, '') + extract_destination = os.path.join(extract_dir, name, "") git.clone(name, repo, clone_destination) git.extract(clone_destination, extract_destination, branch) assert os.path.exists(extract_destination) - git_dir = os.path.join(extract_destination, os.extsep.join(('', 'git'))) + git_dir = os.path.join(extract_destination, os.extsep.join(("", "git"))) assert not os.path.exists(git_dir) - giltfile = os.path.join(extract_destination, 'giltfile') + giltfile = os.path.join(extract_destination, "giltfile") assert os.path.exists(giltfile) @pytest.mark.slow def test_overlay(mocker, temp_dir): - name = 'lorin.openstack-ansible-modules' - repo = 'https://github.com/lorin/openstack-ansible-modules.git' - branch = 'master' + name = "lorin.openstack-ansible-modules" + repo = "https://github.com/lorin/openstack-ansible-modules.git" + branch = "master" clone_dir = os.path.join(temp_dir.strpath, name) - dst_dir = os.path.join(temp_dir.strpath, 'dst', '') + dst_dir = os.path.join(temp_dir.strpath, "dst", "") os.mkdir(clone_dir) os.mkdir(dst_dir) - # yapf: disable files = [ - mocker.Mock(src=os.path.join(clone_dir, '*_manage'), dst=dst_dir), - mocker.Mock(src=os.path.join(clone_dir, 'nova_quota'), dst=dst_dir), - mocker.Mock(src=os.path.join(clone_dir, 'neutron_router'), - dst=os.path.join(dst_dir, 'neutron_router.py')), + mocker.Mock(src=os.path.join(clone_dir, "*_manage"), dst=dst_dir), + mocker.Mock(src=os.path.join(clone_dir, "nova_quota"), dst=dst_dir), + mocker.Mock( + src=os.path.join(clone_dir, "neutron_router"), + dst=os.path.join(dst_dir, "neutron_router.py"), + ), mocker.Mock( - src=os.path.join(clone_dir, 'tests'), - dst=os.path.join(dst_dir, 'tests')) + src=os.path.join(clone_dir, "tests"), + dst=os.path.join(dst_dir, "tests"), + ), ] - # yapf: enable git.clone(name, repo, clone_dir) git.overlay(clone_dir, files, branch) - assert 5 == len(glob.glob('{}/*_manage'.format(dst_dir))) - assert 1 == len(glob.glob('{}/nova_quota'.format(dst_dir))) - assert 1 == len(glob.glob('{}/neutron_router.py'.format(dst_dir))) - assert 2 == len(glob.glob('{}/*'.format(os.path.join(dst_dir, 'tests')))) + assert 5 == len(glob.glob("{}/*_manage".format(dst_dir))) + assert 1 == len(glob.glob("{}/nova_quota".format(dst_dir))) + assert 1 == len(glob.glob("{}/neutron_router.py".format(dst_dir))) + assert 2 == len(glob.glob("{}/*".format(os.path.join(dst_dir, "tests")))) @pytest.mark.slow def test_overlay_existing_directory(mocker, temp_dir): - name = 'lorin.openstack-ansible-modules' - repo = 'https://github.com/lorin/openstack-ansible-modules.git' - branch = 'master' + name = "lorin.openstack-ansible-modules" + repo = "https://github.com/lorin/openstack-ansible-modules.git" + branch = "master" clone_dir = os.path.join(temp_dir.strpath, name) - dst_dir = os.path.join(temp_dir.strpath, 'dst', '') + dst_dir = os.path.join(temp_dir.strpath, "dst", "") os.mkdir(clone_dir) os.mkdir(dst_dir) - os.mkdir(os.path.join(dst_dir, 'tests')) + os.mkdir(os.path.join(dst_dir, "tests")) files = [ mocker.Mock( - src=os.path.join(clone_dir, 'tests'), - dst=os.path.join(dst_dir, 'tests')) + src=os.path.join(clone_dir, "tests"), + dst=os.path.join(dst_dir, "tests"), + ) ] git.clone(name, repo, clone_dir) git.overlay(clone_dir, files, branch) - assert 2 == len(glob.glob('{}/*'.format(os.path.join(dst_dir, 'tests')))) + assert 2 == len(glob.glob("{}/*".format(os.path.join(dst_dir, "tests")))) @pytest.fixture() def patched_run_command(mocker): - return mocker.patch('gilt.util.run_command') + return mocker.patch("gilt.util.run_command") def test_get_version_has_branch(mocker, patched_run_command): - mocker.patch('gilt.git._has_branch').return_value = True - mocker.patch('gilt.git._has_tag').return_value = False - mocker.patch('gilt.git._has_commit').return_value = False - git._get_version('branch') - # yapf: disable + mocker.patch("gilt.git._has_branch").return_value = True + mocker.patch("gilt.git._has_tag").return_value = False + mocker.patch("gilt.git._has_commit").return_value = False + git._get_version("branch") expected = [ - mocker.call(sh.git.bake('checkout', 'branch'), debug=False), - mocker.call(sh.git.bake('clean', '-d', '-x', '-f'), debug=False), - mocker.call(sh.git.bake('pull', rebase=True, ff_only=True), - debug=False) + mocker.call(sh.git.bake("checkout", "branch"), debug=False), + mocker.call(sh.git.bake("clean", "-d", "-x", "-f"), debug=False), + mocker.call( + sh.git.bake("pull", rebase=True, ff_only=True), debug=False + ), ] - # yapf: enable assert expected == patched_run_command.mock_calls def test_get_version_has_tag(mocker, patched_run_command): - mocker.patch('gilt.git._has_branch').return_value = False - mocker.patch('gilt.git._has_tag').return_value = True - mocker.patch('gilt.git._has_commit').return_value = False - git._get_version('tag_name') - # yapf: disable + mocker.patch("gilt.git._has_branch").return_value = False + mocker.patch("gilt.git._has_tag").return_value = True + mocker.patch("gilt.git._has_commit").return_value = False + git._get_version("tag_name") expected = [ - mocker.call(sh.git.bake('checkout', 'tag_name'), debug=False), - mocker.call(sh.git.bake('clean', '-d', '-x', '-f'), debug=False) + mocker.call(sh.git.bake("checkout", "tag_name"), debug=False), + mocker.call(sh.git.bake("clean", "-d", "-x", "-f"), debug=False), ] - # yapf: enable assert expected == patched_run_command.mock_calls def test_get_version_has_commit(mocker, patched_run_command): - mocker.patch('gilt.git._has_branch').return_value = False - mocker.patch('gilt.git._has_tag').return_value = False - mocker.patch('gilt.git._has_commit').return_value = True - git._get_version('commit_sha') - # yapf: disable + mocker.patch("gilt.git._has_branch").return_value = False + mocker.patch("gilt.git._has_tag").return_value = False + mocker.patch("gilt.git._has_commit").return_value = True + git._get_version("commit_sha") expected = [ - mocker.call(sh.git.bake('checkout', 'commit_sha'), debug=False), - mocker.call(sh.git.bake('clean', '-d', '-x', '-f'), debug=False) + mocker.call(sh.git.bake("checkout", "commit_sha"), debug=False), + mocker.call(sh.git.bake("clean", "-d", "-x", "-f"), debug=False), ] - # yapf: enable assert expected == patched_run_command.mock_calls def test_get_version_needs_fetch(mocker, patched_run_command): - mocker.patch('gilt.git._has_branch').return_value = False - mocker.patch('gilt.git._has_tag').return_value = False - mocker.patch('gilt.git._has_commit').return_value = False - git._get_version('remote_tag') - # yapf: disable + mocker.patch("gilt.git._has_branch").return_value = False + mocker.patch("gilt.git._has_tag").return_value = False + mocker.patch("gilt.git._has_commit").return_value = False + git._get_version("remote_tag") expected = [ - mocker.call(sh.git.bake('fetch'), debug=False), - mocker.call(sh.git.bake('checkout', 'remote_tag'), debug=False), - mocker.call(sh.git.bake('clean', '-d', '-x', '-f'), debug=False) + mocker.call(sh.git.bake("fetch"), debug=False), + mocker.call(sh.git.bake("checkout", "remote_tag"), debug=False), + mocker.call(sh.git.bake("clean", "-d", "-x", "-f"), debug=False), ] - # yapf: enable assert expected == patched_run_command.mock_calls def test_get_version_needs_pull(mocker, patched_run_command): - mocker.patch('gilt.git._has_branch').side_effect = [False, True] - mocker.patch('gilt.git._has_tag').return_value = False - mocker.patch('gilt.git._has_commit').return_value = False - git._get_version('remote_branch') - # yapf: disable + mocker.patch("gilt.git._has_branch").side_effect = [False, True] + mocker.patch("gilt.git._has_tag").return_value = False + mocker.patch("gilt.git._has_commit").return_value = False + git._get_version("remote_branch") expected = [ - mocker.call(sh.git.bake('fetch'), debug=False), - mocker.call(sh.git.bake('checkout', 'remote_branch'), debug=False), - mocker.call(sh.git.bake('clean', '-d', '-x', '-f'), debug=False), - mocker.call(sh.git.bake('pull', rebase=True, ff_only=True), - debug=False) + mocker.call(sh.git.bake("fetch"), debug=False), + mocker.call(sh.git.bake("checkout", "remote_branch"), debug=False), + mocker.call(sh.git.bake("clean", "-d", "-x", "-f"), debug=False), + mocker.call( + sh.git.bake("pull", rebase=True, ff_only=True), debug=False + ), ] - # yapf: enable assert expected == patched_run_command.mock_calls @pytest.mark.slow def test_has_version(temp_dir): - name = 'retr0h.ansible-etcd' - repo = 'https://github.com/retr0h/ansible-etcd.git' + name = "retr0h.ansible-etcd" + repo = "https://github.com/retr0h/ansible-etcd.git" destination = os.path.join(temp_dir.strpath, name) git.clone(name, repo, destination) os.chdir(destination) # _has_branch tests - assert git._has_branch('master') - assert not git._has_branch('1.1') - assert not git._has_branch('888ef7b') + assert git._has_branch("master") + assert not git._has_branch("1.1") + assert not git._has_branch("888ef7b") # _has_tag tests - assert not git._has_tag('master') - assert git._has_tag('1.1') - assert not git._has_tag('888ef7b') + assert not git._has_tag("master") + assert git._has_tag("1.1") + assert not git._has_tag("888ef7b") # _has_commit tests - assert not git._has_commit('master') - assert not git._has_commit('1.1') - assert git._has_commit('888ef7b') + assert not git._has_commit("master") + assert not git._has_commit("1.1") + assert git._has_commit("888ef7b") diff --git a/test/test_interpolation.py b/test/test_interpolation.py index da8988b..057eb43 100644 --- a/test/test_interpolation.py +++ b/test/test_interpolation.py @@ -19,56 +19,58 @@ @pytest.fixture def mock_env(): - return {'FOO': 'foo', 'BAR': '', 'ETCD_VERSION': 'master'} + return {"FOO": "foo", "BAR": "", "ETCD_VERSION": "master"} @pytest.fixture def interpolator_instance(mock_env): - return interpolation.Interpolator(interpolation.TemplateWithDefaults, - mock_env).interpolate + return interpolation.Interpolator( + interpolation.TemplateWithDefaults, mock_env + ).interpolate def test_escaped_interpolation(interpolator_instance): - assert '${foo}' == interpolator_instance('$${foo}') + assert "${foo}" == interpolator_instance("$${foo}") def test_invalid_interpolation(interpolator_instance): with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${') + interpolator_instance("${") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('$}') + interpolator_instance("$}") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${}') + interpolator_instance("${}") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${ }') + interpolator_instance("${ }") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${ foo}') + interpolator_instance("${ foo}") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${foo }') + interpolator_instance("${foo }") with pytest.raises(interpolation.InvalidInterpolation): - interpolator_instance('${foo!}') + interpolator_instance("${foo!}") def test_interpolate_missing_no_default(interpolator_instance): - assert 'This var' == interpolator_instance('This ${missing} var') - assert 'This var' == interpolator_instance('This ${BAR} var') + assert "This var" == interpolator_instance("This ${missing} var") + assert "This var" == interpolator_instance("This ${BAR} var") def test_interpolate_with_value(interpolator_instance): - assert 'This foo var' == interpolator_instance('This $FOO var') - assert 'This foo var' == interpolator_instance('This ${FOO} var') + assert "This foo var" == interpolator_instance("This $FOO var") + assert "This foo var" == interpolator_instance("This ${FOO} var") def test_interpolate_missing_with_default(interpolator_instance): - assert 'ok def' == interpolator_instance('ok ${missing:-def}') - assert 'ok def' == interpolator_instance('ok ${missing-def}') - assert 'ok /non:-alphanumeric' == interpolator_instance( - 'ok ${BAR:-/non:-alphanumeric}') + assert "ok def" == interpolator_instance("ok ${missing:-def}") + assert "ok def" == interpolator_instance("ok ${missing-def}") + assert "ok /non:-alphanumeric" == interpolator_instance( + "ok ${BAR:-/non:-alphanumeric}" + ) def test_interpolate_with_empty_and_default_value(interpolator_instance): - assert 'ok def' == interpolator_instance('ok ${BAR:-def}') - assert 'ok ' == interpolator_instance('ok ${BAR-def}') + assert "ok def" == interpolator_instance("ok ${BAR:-def}") + assert "ok " == interpolator_instance("ok ${BAR-def}") def test_interpolate_with_gilt_yaml(interpolator_instance): diff --git a/test/test_shell.py b/test/test_shell.py index f99817c..a4ee90d 100644 --- a/test/test_shell.py +++ b/test/test_shell.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/test/test_util.py b/test/test_util.py index 45930ae..7116752 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright (c) 2016 Cisco Systems, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -29,17 +27,17 @@ def test_print_info(capsys): - util.print_info('foo') + util.print_info("foo") result, _ = capsys.readouterr() - assert 'foo\n' == result + assert "foo\n" == result def test_print_warn(capsys): - util.print_warn('foo') + util.print_warn("foo") result, _ = capsys.readouterr() - assert 'foo' in result + assert "foo" in result def test_run_command(capsys): @@ -47,7 +45,7 @@ def test_run_command(capsys): util.run_command(cmd) result, _ = capsys.readouterr() - assert '' == result + assert "" == result def test_run_command_with_debug(temp_dir, capsys): @@ -55,14 +53,14 @@ def test_run_command_with_debug(temp_dir, capsys): util.run_command(cmd, debug=True) result, _ = capsys.readouterr() - x = 'COMMAND: {} --version'.format(sh.git) + x = "COMMAND: {} --version".format(sh.git) assert x in result - x = 'PWD: {}'.format(temp_dir) + x = "PWD: {}".format(temp_dir) assert x in result def test_saved_cwd_contextmanager(temp_dir): - workdir = os.path.join(temp_dir.strpath, 'workdir') + workdir = os.path.join(temp_dir.strpath, "workdir") os.mkdir(workdir) @@ -74,27 +72,27 @@ def test_saved_cwd_contextmanager(temp_dir): def test_copy_file(temp_dir): - dst_dir = os.path.join(temp_dir.strpath, 'dst') + dst_dir = os.path.join(temp_dir.strpath, "dst") os.mkdir(dst_dir) - src = os.path.join(temp_dir.strpath, 'foo') - open(src, 'a').close() + src = os.path.join(temp_dir.strpath, "foo") + open(src, "a").close() util.copy(src, dst_dir) - dst = os.path.join(dst_dir, 'foo') + dst = os.path.join(dst_dir, "foo") assert os.path.exists(dst) def test_copy_dir(temp_dir): - src_dir = os.path.join(temp_dir.strpath, 'src') - dst_dir = os.path.join(temp_dir.strpath, 'dst') + src_dir = os.path.join(temp_dir.strpath, "src") + dst_dir = os.path.join(temp_dir.strpath, "dst") os.mkdir(src_dir) os.mkdir(dst_dir) - d = os.path.join(dst_dir, 'src') + d = os.path.join(dst_dir, "src") util.copy(src_dir, d) assert os.path.exists(d) @@ -102,21 +100,21 @@ def test_copy_dir(temp_dir): def test_copy_raises(temp_dir): with pytest.raises(OSError): - util.copy('invalid-src', 'invalid-dst') + util.copy("invalid-src", "invalid-dst") def test_build_sh_cmd_simple_command(): - cmd = util.build_sh_cmd('ls') - assert b'/bin/ls' == cmd._path + cmd = util.build_sh_cmd("ls") + assert b"/bin/ls" == cmd._path def test_build_sh_cmd_command_with_args(): - cmd = util.build_sh_cmd('ls /tmp') - assert b'/bin/ls' == cmd._path - assert [b'/tmp'] == cmd._partial_baked_args + cmd = util.build_sh_cmd("ls /tmp") + assert b"/bin/ls" == cmd._path + assert [b"/tmp"] == cmd._partial_baked_args def test_build_sh_cmd_command_with_cwd(temp_dir): - cmd = util.build_sh_cmd('ls', cwd=temp_dir) - assert b'/bin/ls' == cmd._path - assert temp_dir == cmd._partial_call_args['cwd'] + cmd = util.build_sh_cmd("ls", cwd=temp_dir) + assert b"/bin/ls" == cmd._path + assert temp_dir == cmd._partial_call_args["cwd"] diff --git a/tox.ini b/tox.ini index b1f3018..c1cea73 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,31 @@ [tox] minversion = 1.8 envlist = - py{27,36}-unit - py{27,36}-lint + py{36,37}-unit + py{36,37}-lint + format-check doc +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + [testenv] passenv = * -deps = - -rrequirements.txt - -rtest-requirements.txt commands = unit: py.test --runslow -vv {posargs} lint: flake8 +extras = + test [testenv:format] commands = - yapf -i -r gilt/ test/ + python -m black {toxinidir} + +[testenv:format-check] +commands = + python -m black --diff --check {toxinidir} [testenv:doc] passenv = * @@ -24,3 +33,47 @@ deps= -rdoc-requirements.txt commands= python setup.py build_sphinx --builder=html + +[testenv:build-dists-local] +usedevelop = false +skip_install = true +commands = + python -m pep517.build \ + --source \ + --binary \ + --out-dir {toxinidir}/dist/ \ + {toxinidir} + +[testenv:build-dists] +commands = + rm -rfv {toxinidir}/dist/ + {[testenv:build-dists-local]commands} +whitelist_externals = + rm + +[testenv:publish-dists] +commands = + bash -c '\ + twine upload {toxinidir}/dist/*.whl \ + -u $TWINE_USERNAME \ + -p $TWINE_PASSWORD \ + --repository-url $TWINE_REPOSITORY \ + ' +whitelist_externals = + bash + +[flake8] +max-line-length = 79 +format = pylint +exclude = + .eggs/ + .tox/, + .venv*, + build/, + dist/, + doc/, + interpolation.py, +#- [H106] Don't put vim configuration in source files. +#- [H203] Use assertIs(Not)None to check for None. +#- [H904] Delay string interpolations at logging calls. +enable-extensions = H106,H203,H904