diff options
-rw-r--r-- | .gitlab-ci.yml | 9 | ||||
-rw-r--r-- | docs/conf.py | 164 | ||||
-rw-r--r-- | docs/ext/docstrings.py | 41 | ||||
-rw-r--r-- | gitlab/__init__.py | 310 | ||||
-rw-r--r-- | gitlab/__main__.py | 2 | ||||
-rw-r--r-- | gitlab/base.py | 70 | ||||
-rw-r--r-- | gitlab/cli.py | 109 | ||||
-rw-r--r-- | gitlab/config.py | 69 | ||||
-rw-r--r-- | gitlab/const.py | 12 | ||||
-rw-r--r-- | gitlab/exceptions.py | 6 | ||||
-rw-r--r-- | gitlab/mixins.py | 112 | ||||
-rw-r--r-- | gitlab/tests/test_base.py | 80 | ||||
-rw-r--r-- | gitlab/tests/test_cli.py | 65 | ||||
-rw-r--r-- | gitlab/tests/test_config.py | 46 | ||||
-rw-r--r-- | gitlab/tests/test_gitlab.py | 444 | ||||
-rw-r--r-- | gitlab/tests/test_mixins.py | 239 | ||||
-rw-r--r-- | gitlab/tests/test_types.py | 30 | ||||
-rw-r--r-- | gitlab/types.py | 4 | ||||
-rw-r--r-- | gitlab/utils.py | 4 | ||||
-rw-r--r-- | gitlab/v4/cli.py | 261 | ||||
-rw-r--r-- | gitlab/v4/objects.py | 2830 | ||||
-rw-r--r-- | setup.py | 68 | ||||
-rwxr-xr-x | tools/ee-test.py | 87 | ||||
-rwxr-xr-x | tools/generate_token.py | 10 | ||||
-rw-r--r-- | tools/python_test_v4.py | 851 |
25 files changed, 3301 insertions, 2622 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b8fa4f..c50f2aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,6 +17,15 @@ commitlint: except: - master +black_lint: + stage: lint + before_script: + - pip3 install black + script: + - black --check . + except: + - master + #build_test_image: # Currently hangs forever, because of GitLab Runner infrastructure issues # stage: build-test-image # image: diff --git a/docs/conf.py b/docs/conf.py index 4b4a760..a5e5406 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,44 +20,42 @@ import sys import sphinx -sys.path.append('../') +sys.path.append("../") sys.path.append(os.path.dirname(__file__)) import gitlab -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" # 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.autosummary', 'ext.docstrings' -] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosummary", "ext.docstrings"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -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 = 'python-gitlab' -copyright = '2013-2018, Gauvain Pocentek, Mika Mäenpää' +project = "python-gitlab" +copyright = "2013-2018, Gauvain Pocentek, Mika Mäenpää" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -70,175 +68,179 @@ release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # 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 # -- 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 = 'default' -if not on_rtd: # only import and set the theme if we're building docs locally +html_theme = "default" +if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' + + html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - except ImportError: # Theme not found, use default + except ImportError: # Theme not found, use default pass # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> 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 = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-gitlabdoc' +htmlhelp_basename = "python-gitlabdoc" # -- 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': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # 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 = [ - ('index', 'python-gitlab.tex', 'python-gitlab Documentation', - 'Gauvain Pocentek, Mika Mäenpää', 'manual'), + ( + "index", + "python-gitlab.tex", + "python-gitlab Documentation", + "Gauvain Pocentek, Mika Mäenpää", + "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 --------------------------------------- @@ -246,12 +248,17 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'python-gitlab', 'python-gitlab Documentation', - ['Gauvain Pocentek, Mika Mäenpää'], 1) + ( + "index", + "python-gitlab", + "python-gitlab Documentation", + ["Gauvain Pocentek, Mika Mäenpää"], + 1, + ) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -260,20 +267,25 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'python-gitlab', 'python-gitlab Documentation', - 'Gauvain Pocentek, Mika Mäenpää', 'python-gitlab', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "python-gitlab", + "python-gitlab Documentation", + "Gauvain Pocentek, Mika Mäenpää", + "python-gitlab", + "One line description of project.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - +# texinfo_no_detailmenu = False diff --git a/docs/ext/docstrings.py b/docs/ext/docstrings.py index 5035f4f..e42bb60 100644 --- a/docs/ext/docstrings.py +++ b/docs/ext/docstrings.py @@ -13,46 +13,47 @@ def classref(value, short=True): return value if not inspect.isclass(value): - return ':class:%s' % value - tilde = '~' if short else '' - string = '%s.%s' % (value.__module__, value.__name__) - return ':class:`%sgitlab.objects.%s`' % (tilde, value.__name__) + return ":class:%s" % value + tilde = "~" if short else "" + string = "%s.%s" % (value.__module__, value.__name__) + return ":class:`%sgitlab.objects.%s`" % (tilde, value.__name__) def setup(app): - app.connect('autodoc-process-docstring', _process_docstring) - app.connect('autodoc-skip-member', napoleon._skip_member) + app.connect("autodoc-process-docstring", _process_docstring) + app.connect("autodoc-skip-member", napoleon._skip_member) conf = napoleon.Config._config_values for name, (default, rebuild) in six.iteritems(conf): app.add_config_value(name, default, rebuild) - return {'version': sphinx.__display_version__, 'parallel_read_safe': True} + return {"version": sphinx.__display_version__, "parallel_read_safe": True} def _process_docstring(app, what, name, obj, options, lines): result_lines = lines - docstring = GitlabDocstring(result_lines, app.config, app, what, name, obj, - options) + docstring = GitlabDocstring(result_lines, app.config, app, what, name, obj, options) result_lines = docstring.lines() lines[:] = result_lines[:] class GitlabDocstring(GoogleDocstring): def _build_doc(self, tmpl, **kwargs): - env = jinja2.Environment(loader=jinja2.FileSystemLoader( - os.path.dirname(__file__)), trim_blocks=False) - env.filters['classref'] = classref + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), trim_blocks=False + ) + env.filters["classref"] = classref template = env.get_template(tmpl) output = template.render(**kwargs) - return output.split('\n') + return output.split("\n") - def __init__(self, docstring, config=None, app=None, what='', name='', - obj=None, options=None): - super(GitlabDocstring, self).__init__(docstring, config, app, what, - name, obj, options) + def __init__( + self, docstring, config=None, app=None, what="", name="", obj=None, options=None + ): + super(GitlabDocstring, self).__init__( + docstring, config, app, what, name, obj, options + ) - if name.startswith('gitlab.v4.objects') and name.endswith('Manager'): - self._parsed_lines.extend(self._build_doc('manager_tmpl.j2', - cls=self._obj)) + if name.startswith("gitlab.v4.objects") and name.endswith("Manager"): + self._parsed_lines.extend(self._build_doc("manager_tmpl.j2", cls=self._obj)) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 9532267..fb21985 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -30,26 +30,26 @@ from gitlab.const import * # noqa from gitlab.exceptions import * # noqa from gitlab import utils # noqa -__title__ = 'python-gitlab' -__version__ = '1.8.0' -__author__ = 'Gauvain Pocentek' -__email__ = 'gauvainpocentek@gmail.com' -__license__ = 'LGPL3' -__copyright__ = 'Copyright 2013-2019 Gauvain Pocentek' +__title__ = "python-gitlab" +__version__ = "1.8.0" +__author__ = "Gauvain Pocentek" +__email__ = "gauvainpocentek@gmail.com" +__license__ = "LGPL3" +__copyright__ = "Copyright 2013-2019 Gauvain Pocentek" -warnings.filterwarnings('default', category=DeprecationWarning, - module='^gitlab') +warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab") -REDIRECT_MSG = ('python-gitlab detected an http to https redirection. You ' - 'must update your GitLab URL to use https:// to avoid issues.') +REDIRECT_MSG = ( + "python-gitlab detected an http to https redirection. You " + "must update your GitLab URL to use https:// to avoid issues." +) def _sanitize(value): if isinstance(value, dict): - return dict((k, _sanitize(v)) - for k, v in six.iteritems(value)) + return dict((k, _sanitize(v)) for k, v in six.iteritems(value)) if isinstance(value, six.string_types): - return value.replace('/', '%2F') + return value.replace("/", "%2F") return value @@ -71,15 +71,26 @@ class Gitlab(object): api_version (str): Gitlab API version to use (support for 4 only) """ - def __init__(self, url, private_token=None, oauth_token=None, email=None, - password=None, ssl_verify=True, http_username=None, - http_password=None, timeout=None, api_version='4', - session=None, per_page=None): + def __init__( + self, + url, + private_token=None, + oauth_token=None, + email=None, + password=None, + ssl_verify=True, + http_username=None, + http_password=None, + timeout=None, + api_version="4", + session=None, + per_page=None, + ): self._api_version = str(api_version) self._server_version = self._server_revision = None self._base_url = url - self._url = '%s/api/v%s' % (url, api_version) + self._url = "%s/api/v%s" % (url, api_version) #: Timeout to use for requests to gitlab server self.timeout = timeout #: Headers that will be used in request to GitLab @@ -103,8 +114,7 @@ class Gitlab(object): self.per_page = per_page - objects = importlib.import_module('gitlab.v%s.objects' % - self._api_version) + objects = importlib.import_module("gitlab.v%s.objects" % self._api_version) self._objects = objects self.broadcastmessages = objects.BroadcastMessageManager(self) @@ -141,13 +151,12 @@ class Gitlab(object): def __getstate__(self): state = self.__dict__.copy() - state.pop('_objects') + state.pop("_objects") return state def __setstate__(self, state): self.__dict__.update(state) - objects = importlib.import_module('gitlab.v%s.objects' % - self._api_version) + objects = importlib.import_module("gitlab.v%s.objects" % self._api_version) self._objects = objects @property @@ -179,15 +188,20 @@ class Gitlab(object): Raises: gitlab.config.GitlabDataError: If the configuration is not correct. """ - config = gitlab.config.GitlabConfigParser(gitlab_id=gitlab_id, - config_files=config_files) - return cls(config.url, private_token=config.private_token, - oauth_token=config.oauth_token, - ssl_verify=config.ssl_verify, timeout=config.timeout, - http_username=config.http_username, - http_password=config.http_password, - api_version=config.api_version, - per_page=config.per_page) + config = gitlab.config.GitlabConfigParser( + gitlab_id=gitlab_id, config_files=config_files + ) + return cls( + config.url, + private_token=config.private_token, + oauth_token=config.oauth_token, + ssl_verify=config.ssl_verify, + timeout=config.timeout, + http_username=config.http_username, + http_password=config.http_password, + api_version=config.api_version, + per_page=config.per_page, + ) def auth(self): """Performs an authentication. @@ -203,8 +217,8 @@ class Gitlab(object): self._credentials_auth() def _credentials_auth(self): - data = {'email': self.email, 'password': self.password} - r = self.http_post('/session', data) + data = {"email": self.email, "password": self.password} + r = self.http_post("/session", data) manager = self._objects.CurrentUserManager(self) self.user = self._objects.CurrentUser(manager, r) self.private_token = self.user.private_token @@ -226,11 +240,11 @@ class Gitlab(object): """ if self._server_version is None: try: - data = self.http_get('/version') - self._server_version = data['version'] - self._server_revision = data['revision'] + data = self.http_get("/version") + self._server_version = data["version"] + self._server_revision = data["revision"] except Exception: - self._server_version = self._server_revision = 'unknown' + self._server_version = self._server_revision = "unknown" return self._server_version, self._server_revision @@ -250,9 +264,9 @@ class Gitlab(object): tuple: (True, []) if the file is valid, (False, errors(list)) otherwise """ - post_data = {'content': content} - data = self.http_post('/ci/lint', post_data=post_data, **kwargs) - return (data['status'] == 'valid', data['errors']) + post_data = {"content": content} + data = self.http_post("/ci/lint", post_data=post_data, **kwargs) + return (data["status"] == "valid", data["errors"]) @on_http_error(GitlabMarkdownError) def markdown(self, text, gfm=False, project=None, **kwargs): @@ -273,11 +287,11 @@ class Gitlab(object): Returns: str: The HTML rendering of the markdown text. """ - post_data = {'text': text, 'gfm': gfm} + post_data = {"text": text, "gfm": gfm} if project is not None: - post_data['project'] = project - data = self.http_post('/markdown', post_data=post_data, **kwargs) - return data['html'] + post_data["project"] = project + data = self.http_post("/markdown", post_data=post_data, **kwargs) + return data["html"] @on_http_error(GitlabLicenseError) def get_license(self, **kwargs): @@ -293,7 +307,7 @@ class Gitlab(object): Returns: dict: The current license information """ - return self.http_get('/license', **kwargs) + return self.http_get("/license", **kwargs) @on_http_error(GitlabLicenseError) def set_license(self, license, **kwargs): @@ -310,54 +324,61 @@ class Gitlab(object): Returns: dict: The new license information """ - data = {'license': license} - return self.http_post('/license', post_data=data, **kwargs) + data = {"license": license} + return self.http_post("/license", post_data=data, **kwargs) def _construct_url(self, id_, obj, parameters, action=None): - if 'next_url' in parameters: - return parameters['next_url'] + if "next_url" in parameters: + return parameters["next_url"] args = _sanitize(parameters) - url_attr = '_url' + url_attr = "_url" if action is not None: - attr = '_%s_url' % action + attr = "_%s_url" % action if hasattr(obj, attr): url_attr = attr obj_url = getattr(obj, url_attr) url = obj_url % args if id_ is not None: - return '%s/%s' % (url, str(id_)) + return "%s/%s" % (url, str(id_)) else: return url def _set_auth_info(self): if self.private_token and self.oauth_token: - raise ValueError("Only one of private_token or oauth_token should " - "be defined") - if ((self.http_username and not self.http_password) - or (not self.http_username and self.http_password)): - raise ValueError("Both http_username and http_password should " - "be defined") + raise ValueError( + "Only one of private_token or oauth_token should " "be defined" + ) + if (self.http_username and not self.http_password) or ( + not self.http_username and self.http_password + ): + raise ValueError( + "Both http_username and http_password should " "be defined" + ) if self.oauth_token and self.http_username: - raise ValueError("Only one of oauth authentication or http " - "authentication should be defined") + raise ValueError( + "Only one of oauth authentication or http " + "authentication should be defined" + ) self._http_auth = None if self.private_token: - self.headers['PRIVATE-TOKEN'] = self.private_token - self.headers.pop('Authorization', None) + self.headers["PRIVATE-TOKEN"] = self.private_token + self.headers.pop("Authorization", None) if self.oauth_token: - self.headers['Authorization'] = "Bearer %s" % self.oauth_token - self.headers.pop('PRIVATE-TOKEN', None) + self.headers["Authorization"] = "Bearer %s" % self.oauth_token + self.headers.pop("PRIVATE-TOKEN", None) if self.http_username: - self._http_auth = requests.auth.HTTPBasicAuth(self.http_username, - self.http_password) + self._http_auth = requests.auth.HTTPBasicAuth( + self.http_username, self.http_password + ) def enable_debug(self): import logging + try: from http.client import HTTPConnection # noqa except ImportError: @@ -373,15 +394,15 @@ class Gitlab(object): def _create_headers(self, content_type=None): request_headers = self.headers.copy() if content_type is not None: - request_headers['Content-type'] = content_type + request_headers["Content-type"] = content_type return request_headers def _get_session_opts(self, content_type): return { - 'headers': self._create_headers(content_type), - 'auth': self._http_auth, - 'timeout': self.timeout, - 'verify': self.ssl_verify + "headers": self._create_headers(content_type), + "auth": self._http_auth, + "timeout": self.timeout, + "verify": self.ssl_verify, } def _build_url(self, path): @@ -393,10 +414,10 @@ class Gitlab(object): Returns: str: The full URL """ - if path.startswith('http://') or path.startswith('https://'): + if path.startswith("http://") or path.startswith("https://"): return path else: - return '%s%s' % (self._url, path) + return "%s%s" % (self._url, path) def _check_redirects(self, result): # Check the requests history to detect http to https redirections. @@ -406,20 +427,28 @@ class Gitlab(object): # request. # If we detect a redirection to https with a POST or a PUT request, we # raise an exception with a useful error message. - if result.history and self._base_url.startswith('http:'): + if result.history and self._base_url.startswith("http:"): for item in result.history: if item.status_code not in (301, 302): continue # GET methods can be redirected without issue - if item.request.method == 'GET': + if item.request.method == "GET": continue # Did we end-up with an https:// URL? - location = item.headers.get('Location', None) - if location and location.startswith('https://'): + location = item.headers.get("Location", None) + if location and location.startswith("https://"): raise RedirectError(REDIRECT_MSG) - def http_request(self, verb, path, query_data={}, post_data=None, - streamed=False, files=None, **kwargs): + def http_request( + self, + verb, + path, + query_data={}, + post_data=None, + streamed=False, + files=None, + **kwargs + ): """Make an HTTP request to the Gitlab server. Args: @@ -452,18 +481,18 @@ class Gitlab(object): # So we provide a `query_parameters` key: if it's there we use its dict # value as arguments for the gitlab server, and ignore the other # arguments, except pagination ones (per_page and page) - if 'query_parameters' in kwargs: - utils.copy_dict(params, kwargs['query_parameters']) - for arg in ('per_page', 'page'): + if "query_parameters" in kwargs: + utils.copy_dict(params, kwargs["query_parameters"]) + for arg in ("per_page", "page"): if arg in kwargs: params[arg] = kwargs[arg] else: utils.copy_dict(params, kwargs) - opts = self._get_session_opts(content_type='application/json') + opts = self._get_session_opts(content_type="application/json") - verify = opts.pop('verify') - timeout = opts.pop('timeout') + verify = opts.pop("verify") + timeout = opts.pop("timeout") # We need to deal with json vs. data when uploading files if files: @@ -480,12 +509,14 @@ class Gitlab(object): # The Requests behavior is right but it seems that web servers don't # always agree with this decision (this is the case with a default # gitlab installation) - req = requests.Request(verb, url, json=json, data=data, params=params, - files=files, **opts) + req = requests.Request( + verb, url, json=json, data=data, params=params, files=files, **opts + ) prepped = self.session.prepare_request(req) prepped.url = utils.sanitized_url(prepped.url) settings = self.session.merge_environment_settings( - prepped.url, {}, streamed, verify, None) + prepped.url, {}, streamed, verify, None + ) # obey the rate limit by default obey_rate_limit = kwargs.get("obey_rate_limit", True) @@ -514,7 +545,7 @@ class Gitlab(object): error_message = result.content try: error_json = result.json() - for k in ('message', 'error'): + for k in ("message", "error"): if k in error_json: error_message = error_json[k] except (KeyError, ValueError, TypeError): @@ -524,14 +555,16 @@ class Gitlab(object): raise GitlabAuthenticationError( response_code=result.status_code, error_message=error_message, - response_body=result.content) + response_body=result.content, + ) - raise GitlabHttpError(response_code=result.status_code, - error_message=error_message, - response_body=result.content) + raise GitlabHttpError( + response_code=result.status_code, + error_message=error_message, + response_body=result.content, + ) - def http_get(self, path, query_data={}, streamed=False, raw=False, - **kwargs): + def http_get(self, path, query_data={}, streamed=False, raw=False, **kwargs): """Make a GET request to the Gitlab server. Args: @@ -551,17 +584,21 @@ class Gitlab(object): GitlabHttpError: When the return code is not 2xx GitlabParsingError: If the json data could not be parsed """ - result = self.http_request('get', path, query_data=query_data, - streamed=streamed, **kwargs) - - if (result.headers['Content-Type'] == 'application/json' - and not streamed - and not raw): + result = self.http_request( + "get", path, query_data=query_data, streamed=streamed, **kwargs + ) + + if ( + result.headers["Content-Type"] == "application/json" + and not streamed + and not raw + ): try: return result.json() except Exception: raise GitlabParsingError( - error_message="Failed to parse the server message") + error_message="Failed to parse the server message" + ) else: return result @@ -590,22 +627,20 @@ class Gitlab(object): # In case we want to change the default behavior at some point as_list = True if as_list is None else as_list - get_all = kwargs.pop('all', False) + get_all = kwargs.pop("all", False) url = self._build_url(path) if get_all is True: return list(GitlabList(self, url, query_data, **kwargs)) - if 'page' in kwargs or as_list is True: + if "page" in kwargs or as_list is True: # pagination requested, we return a list - return list(GitlabList(self, url, query_data, get_next=False, - **kwargs)) + return list(GitlabList(self, url, query_data, get_next=False, **kwargs)) # No pagination, generator requested return GitlabList(self, url, query_data, **kwargs) - def http_post(self, path, query_data={}, post_data={}, files=None, - **kwargs): + def http_post(self, path, query_data={}, post_data={}, files=None, **kwargs): """Make a POST request to the Gitlab server. Args: @@ -625,18 +660,22 @@ class Gitlab(object): GitlabHttpError: When the return code is not 2xx GitlabParsingError: If the json data could not be parsed """ - result = self.http_request('post', path, query_data=query_data, - post_data=post_data, files=files, **kwargs) + result = self.http_request( + "post", + path, + query_data=query_data, + post_data=post_data, + files=files, + **kwargs + ) try: - if result.headers.get('Content-Type', None) == 'application/json': + if result.headers.get("Content-Type", None) == "application/json": return result.json() except Exception: - raise GitlabParsingError( - error_message="Failed to parse the server message") + raise GitlabParsingError(error_message="Failed to parse the server message") return result - def http_put(self, path, query_data={}, post_data={}, files=None, - **kwargs): + def http_put(self, path, query_data={}, post_data={}, files=None, **kwargs): """Make a PUT request to the Gitlab server. Args: @@ -655,13 +694,18 @@ class Gitlab(object): GitlabHttpError: When the return code is not 2xx GitlabParsingError: If the json data could not be parsed """ - result = self.http_request('put', path, query_data=query_data, - post_data=post_data, files=files, **kwargs) + result = self.http_request( + "put", + path, + query_data=query_data, + post_data=post_data, + files=files, + **kwargs + ) try: return result.json() except Exception: - raise GitlabParsingError( - error_message="Failed to parse the server message") + raise GitlabParsingError(error_message="Failed to parse the server message") def http_delete(self, path, **kwargs): """Make a PUT request to the Gitlab server. @@ -677,7 +721,7 @@ class Gitlab(object): Raises: GitlabHttpError: When the return code is not 2xx """ - return self.http_request('delete', path, **kwargs) + return self.http_request("delete", path, **kwargs) @on_http_error(GitlabSearchError) def search(self, scope, search, **kwargs): @@ -695,8 +739,8 @@ class Gitlab(object): Returns: GitlabList: A list of dicts describing the resources found. """ - data = {'scope': scope, 'search': search} - return self.http_list('/search', query_data=data, **kwargs) + data = {"scope": scope, "search": search} + return self.http_list("/search", query_data=data, **kwargs) class GitlabList(object): @@ -712,24 +756,22 @@ class GitlabList(object): self._get_next = get_next def _query(self, url, query_data={}, **kwargs): - result = self._gl.http_request('get', url, query_data=query_data, - **kwargs) + result = self._gl.http_request("get", url, query_data=query_data, **kwargs) try: - self._next_url = result.links['next']['url'] + self._next_url = result.links["next"]["url"] except KeyError: self._next_url = None - self._current_page = result.headers.get('X-Page') - self._prev_page = result.headers.get('X-Prev-Page') - self._next_page = result.headers.get('X-Next-Page') - self._per_page = result.headers.get('X-Per-Page') - self._total_pages = result.headers.get('X-Total-Pages') - self._total = result.headers.get('X-Total') + self._current_page = result.headers.get("X-Page") + self._prev_page = result.headers.get("X-Prev-Page") + self._next_page = result.headers.get("X-Next-Page") + self._per_page = result.headers.get("X-Per-Page") + self._total_pages = result.headers.get("X-Total-Pages") + self._total = result.headers.get("X-Total") try: self._data = result.json() except Exception: - raise GitlabParsingError( - error_message="Failed to parse the server message") + raise GitlabParsingError(error_message="Failed to parse the server message") self._current = 0 diff --git a/gitlab/__main__.py b/gitlab/__main__.py index 7d8d087..14a1fa2 100644 --- a/gitlab/__main__.py +++ b/gitlab/__main__.py @@ -1,4 +1,4 @@ import gitlab.cli -__name__ == '__main__' and gitlab.cli.main() +__name__ == "__main__" and gitlab.cli.main() diff --git a/gitlab/base.py b/gitlab/base.py index 7a88881..d2e44b8 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -28,35 +28,38 @@ class RESTObject(object): must be used as uniq ID. ``None`` means that the object can be updated without ID in the url. """ - _id_attr = 'id' + + _id_attr = "id" def __init__(self, manager, attrs): - self.__dict__.update({ - 'manager': manager, - '_attrs': attrs, - '_updated_attrs': {}, - '_module': importlib.import_module(self.__module__) - }) - self.__dict__['_parent_attrs'] = self.manager.parent_attrs + self.__dict__.update( + { + "manager": manager, + "_attrs": attrs, + "_updated_attrs": {}, + "_module": importlib.import_module(self.__module__), + } + ) + self.__dict__["_parent_attrs"] = self.manager.parent_attrs self._create_managers() def __getstate__(self): state = self.__dict__.copy() - module = state.pop('_module') - state['_module_name'] = module.__name__ + module = state.pop("_module") + state["_module_name"] = module.__name__ return state def __setstate__(self, state): - module_name = state.pop('_module_name') + module_name = state.pop("_module_name") self.__dict__.update(state) self._module = importlib.import_module(module_name) def __getattr__(self, name): try: - return self.__dict__['_updated_attrs'][name] + return self.__dict__["_updated_attrs"][name] except KeyError: try: - value = self.__dict__['_attrs'][name] + value = self.__dict__["_attrs"][name] # If the value is a list, we copy it in the _updated_attrs dict # because we are not able to detect changes made on the object @@ -69,32 +72,34 @@ class RESTObject(object): # note: _parent_attrs will only store simple values (int) so we # don't make this check in the next except block. if isinstance(value, list): - self.__dict__['_updated_attrs'][name] = value[:] - return self.__dict__['_updated_attrs'][name] + self.__dict__["_updated_attrs"][name] = value[:] + return self.__dict__["_updated_attrs"][name] return value except KeyError: try: - return self.__dict__['_parent_attrs'][name] + return self.__dict__["_parent_attrs"][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): - self.__dict__['_updated_attrs'][name] = value + self.__dict__["_updated_attrs"][name] = value def __str__(self): data = self._attrs.copy() data.update(self._updated_attrs) - return '%s => %s' % (type(self), data) + return "%s => %s" % (type(self), data) def __repr__(self): if self._id_attr: - return '<%s %s:%s>' % (self.__class__.__name__, - self._id_attr, - self.get_id()) + return "<%s %s:%s>" % ( + self.__class__.__name__, + self._id_attr, + self.get_id(), + ) else: - return '<%s>' % self.__class__.__name__ + return "<%s>" % self.__class__.__name__ def __eq__(self, other): if self.get_id() and other.get_id(): @@ -112,7 +117,7 @@ class RESTObject(object): return hash(self.get_id()) def _create_managers(self): - managers = getattr(self, '_managers', None) + managers = getattr(self, "_managers", None) if managers is None: return @@ -122,8 +127,8 @@ class RESTObject(object): self.__dict__[attr] = manager def _update_attrs(self, new_attrs): - self.__dict__['_updated_attrs'] = {} - self.__dict__['_attrs'].update(new_attrs) + self.__dict__["_updated_attrs"] = {} + self.__dict__["_attrs"].update(new_attrs) def get_id(self): """Returns the id of the resource.""" @@ -133,9 +138,9 @@ class RESTObject(object): @property def attributes(self): - d = self.__dict__['_updated_attrs'].copy() - d.update(self.__dict__['_attrs']) - d.update(self.__dict__['_parent_attrs']) + d = self.__dict__["_updated_attrs"].copy() + d.update(self.__dict__["_attrs"]) + d.update(self.__dict__["_parent_attrs"]) return d @@ -153,6 +158,7 @@ class RESTObjectList(object): obj_cls: Type of objects to create from the json data _list: A GitlabList object """ + def __init__(self, manager, obj_cls, _list): """Creates an objects list from a GitlabList. @@ -250,11 +256,13 @@ class RESTManager(object): self._parent_attrs = {} if path is None: path = self._path - if self._parent is None or not hasattr(self, '_from_parent_attrs'): + if self._parent is None or not hasattr(self, "_from_parent_attrs"): return path - data = {self_attr: getattr(self._parent, parent_attr, None) - for self_attr, parent_attr in self._from_parent_attrs.items()} + data = { + self_attr: getattr(self._parent, parent_attr, None) + for self_attr, parent_attr in self._from_parent_attrs.items() + } self._parent_attrs = data return path % data diff --git a/gitlab/cli.py b/gitlab/cli.py index b573c7f..0433a81 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -26,7 +26,7 @@ import sys import gitlab.config -camel_re = re.compile('(.)([A-Z])') +camel_re = re.compile("(.)([A-Z])") # custom_actions = { # cls: { @@ -46,20 +46,21 @@ def register_custom_action(cls_names, mandatory=tuple(), optional=tuple()): in_obj = True classes = cls_names if type(cls_names) != tuple: - classes = (cls_names, ) + classes = (cls_names,) for cls_name in classes: final_name = cls_name - if cls_name.endswith('Manager'): - final_name = cls_name.replace('Manager', '') + if cls_name.endswith("Manager"): + final_name = cls_name.replace("Manager", "") in_obj = False if final_name not in custom_actions: custom_actions[final_name] = {} - action = f.__name__.replace('_', '-') + action = f.__name__.replace("_", "-") custom_actions[final_name][action] = (mandatory, optional, in_obj) return wrapped_f + return wrap @@ -75,38 +76,57 @@ def what_to_cls(what): def cls_to_what(cls): - return camel_re.sub(r'\1-\2', cls.__name__).lower() + return camel_re.sub(r"\1-\2", cls.__name__).lower() def _get_base_parser(add_help=True): parser = argparse.ArgumentParser( - add_help=add_help, - description="GitLab API Command Line Interface") - parser.add_argument("--version", help="Display the version.", - action="store_true") - parser.add_argument("-v", "--verbose", "--fancy", - help="Verbose mode (legacy format only)", - action="store_true") - parser.add_argument("-d", "--debug", - help="Debug mode (display HTTP requests)", - action="store_true") - parser.add_argument("-c", "--config-file", action='append', - help=("Configuration file to use. Can be used " - "multiple times.")) - parser.add_argument("-g", "--gitlab", - help=("Which configuration section should " - "be used. If not defined, the default selection " - "will be used."), - required=False) - parser.add_argument("-o", "--output", - help="Output format (v4 only): json|legacy|yaml", - required=False, - choices=['json', 'legacy', 'yaml'], - default="legacy") - parser.add_argument("-f", "--fields", - help=("Fields to display in the output (comma " - "separated). Not used with legacy output"), - required=False) + add_help=add_help, description="GitLab API Command Line Interface" + ) + parser.add_argument("--version", help="Display the version.", action="store_true") + parser.add_argument( + "-v", + "--verbose", + "--fancy", + help="Verbose mode (legacy format only)", + action="store_true", + ) + parser.add_argument( + "-d", "--debug", help="Debug mode (display HTTP requests)", action="store_true" + ) + parser.add_argument( + "-c", + "--config-file", + action="append", + help=("Configuration file to use. Can be used " "multiple times."), + ) + parser.add_argument( + "-g", + "--gitlab", + help=( + "Which configuration section should " + "be used. If not defined, the default selection " + "will be used." + ), + required=False, + ) + parser.add_argument( + "-o", + "--output", + help="Output format (v4 only): json|legacy|yaml", + required=False, + choices=["json", "legacy", "yaml"], + default="legacy", + ) + parser.add_argument( + "-f", + "--fields", + help=( + "Fields to display in the output (comma " + "separated). Not used with legacy output" + ), + required=False, + ) return parser @@ -117,7 +137,7 @@ def _get_parser(cli_module): def _parse_value(v): - if isinstance(v, str) and v.startswith('@'): + if isinstance(v, str) and v.startswith("@"): # If the user-provided value starts with @, we try to read the file # path provided after @ as the real value. Exit on any error. try: @@ -142,16 +162,13 @@ def main(): # any subparser setup (options, args) = parser.parse_known_args(sys.argv) try: - config = gitlab.config.GitlabConfigParser( - options.gitlab, - options.config_file - ) + config = gitlab.config.GitlabConfigParser(options.gitlab, options.config_file) except gitlab.config.ConfigError as e: if "--help" in sys.argv or "-h" in sys.argv: parser.print_help() sys.exit(0) sys.exit(e) - cli_module = importlib.import_module('gitlab.v%s.cli' % config.api_version) + cli_module = importlib.import_module("gitlab.v%s.cli" % config.api_version) # Now we build the entire set of subcommands and do the complete parsing parser = _get_parser(cli_module) @@ -163,15 +180,23 @@ def main(): output = args.output fields = [] if args.fields: - fields = [x.strip() for x in args.fields.split(',')] + fields = [x.strip() for x in args.fields.split(",")] debug = args.debug action = args.action what = args.what args = args.__dict__ # Remove CLI behavior-related args - for item in ('gitlab', 'config_file', 'verbose', 'debug', 'what', 'action', - 'version', 'output'): + for item in ( + "gitlab", + "config_file", + "verbose", + "debug", + "what", + "action", + "version", + "output", + ): args.pop(item) args = {k: _parse_value(v) for k, v in args.items() if v is not None} diff --git a/gitlab/config.py b/gitlab/config.py index 1c76594..0c3cff7 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -19,10 +19,7 @@ import os from six.moves import configparser -_DEFAULT_FILES = [ - '/etc/python-gitlab.cfg', - os.path.expanduser('~/.python-gitlab.cfg') -] +_DEFAULT_FILES = ["/etc/python-gitlab.cfg", os.path.expanduser("~/.python-gitlab.cfg")] class ConfigError(Exception): @@ -63,40 +60,41 @@ class GitlabConfigParser(object): if self.gitlab_id is None: try: - self.gitlab_id = self._config.get('global', 'default') + self.gitlab_id = self._config.get("global", "default") except Exception: - raise GitlabIDError("Impossible to get the gitlab id " - "(not specified in config file)") + raise GitlabIDError( + "Impossible to get the gitlab id " "(not specified in config file)" + ) try: - self.url = self._config.get(self.gitlab_id, 'url') + self.url = self._config.get(self.gitlab_id, "url") except Exception: - raise GitlabDataError("Impossible to get gitlab informations from " - "configuration (%s)" % self.gitlab_id) + raise GitlabDataError( + "Impossible to get gitlab informations from " + "configuration (%s)" % self.gitlab_id + ) self.ssl_verify = True try: - self.ssl_verify = self._config.getboolean('global', 'ssl_verify') + self.ssl_verify = self._config.getboolean("global", "ssl_verify") except ValueError: # Value Error means the option exists but isn't a boolean. # Get as a string instead as it should then be a local path to a # CA bundle. try: - self.ssl_verify = self._config.get('global', 'ssl_verify') + self.ssl_verify = self._config.get("global", "ssl_verify") except Exception: pass except Exception: pass try: - self.ssl_verify = self._config.getboolean(self.gitlab_id, - 'ssl_verify') + self.ssl_verify = self._config.getboolean(self.gitlab_id, "ssl_verify") except ValueError: # Value Error means the option exists but isn't a boolean. # Get as a string instead as it should then be a local path to a # CA bundle. try: - self.ssl_verify = self._config.get(self.gitlab_id, - 'ssl_verify') + self.ssl_verify = self._config.get(self.gitlab_id, "ssl_verify") except Exception: pass except Exception: @@ -104,66 +102,59 @@ class GitlabConfigParser(object): self.timeout = 60 try: - self.timeout = self._config.getint('global', 'timeout') + self.timeout = self._config.getint("global", "timeout") except Exception: pass try: - self.timeout = self._config.getint(self.gitlab_id, 'timeout') + self.timeout = self._config.getint(self.gitlab_id, "timeout") except Exception: pass self.private_token = None try: - self.private_token = self._config.get(self.gitlab_id, - 'private_token') + self.private_token = self._config.get(self.gitlab_id, "private_token") except Exception: pass self.oauth_token = None try: - self.oauth_token = self._config.get(self.gitlab_id, 'oauth_token') + self.oauth_token = self._config.get(self.gitlab_id, "oauth_token") except Exception: pass self.http_username = None self.http_password = None try: - self.http_username = self._config.get(self.gitlab_id, - 'http_username') - self.http_password = self._config.get(self.gitlab_id, - 'http_password') + self.http_username = self._config.get(self.gitlab_id, "http_username") + self.http_password = self._config.get(self.gitlab_id, "http_password") except Exception: pass self.http_username = None self.http_password = None try: - self.http_username = self._config.get(self.gitlab_id, - 'http_username') - self.http_password = self._config.get(self.gitlab_id, - 'http_password') + self.http_username = self._config.get(self.gitlab_id, "http_username") + self.http_password = self._config.get(self.gitlab_id, "http_password") except Exception: pass - self.api_version = '4' + self.api_version = "4" try: - self.api_version = self._config.get('global', 'api_version') + self.api_version = self._config.get("global", "api_version") except Exception: pass try: - self.api_version = self._config.get(self.gitlab_id, 'api_version') + self.api_version = self._config.get(self.gitlab_id, "api_version") except Exception: pass - if self.api_version not in ('4',): - raise GitlabDataError("Unsupported API version: %s" % - self.api_version) + if self.api_version not in ("4",): + raise GitlabDataError("Unsupported API version: %s" % self.api_version) self.per_page = None - for section in ['global', self.gitlab_id]: + for section in ["global", self.gitlab_id]: try: - self.per_page = self._config.getint(section, 'per_page') + self.per_page = self._config.getint(section, "per_page") except Exception: pass if self.per_page is not None and not 0 <= self.per_page <= 100: - raise GitlabDataError("Unsupported per_page number: %s" % - self.per_page) + raise GitlabDataError("Unsupported per_page number: %s" % self.per_page) diff --git a/gitlab/const.py b/gitlab/const.py index 62f2403..aef4a40 100644 --- a/gitlab/const.py +++ b/gitlab/const.py @@ -26,9 +26,9 @@ VISIBILITY_PRIVATE = 0 VISIBILITY_INTERNAL = 10 VISIBILITY_PUBLIC = 20 -NOTIFICATION_LEVEL_DISABLED = 'disabled' -NOTIFICATION_LEVEL_PARTICIPATING = 'participating' -NOTIFICATION_LEVEL_WATCH = 'watch' -NOTIFICATION_LEVEL_GLOBAL = 'global' -NOTIFICATION_LEVEL_MENTION = 'mention' -NOTIFICATION_LEVEL_CUSTOM = 'custom' +NOTIFICATION_LEVEL_DISABLED = "disabled" +NOTIFICATION_LEVEL_PARTICIPATING = "participating" +NOTIFICATION_LEVEL_WATCH = "watch" +NOTIFICATION_LEVEL_GLOBAL = "global" +NOTIFICATION_LEVEL_MENTION = "mention" +NOTIFICATION_LEVEL_CUSTOM = "custom" diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 5b7b75c..449b6f0 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -19,8 +19,7 @@ import functools class GitlabError(Exception): - def __init__(self, error_message="", response_code=None, - response_body=None): + def __init__(self, error_message="", response_code=None, response_body=None): Exception.__init__(self, error_message) # Http status code @@ -248,6 +247,7 @@ def on_http_error(error): error(Exception): The exception type to raise -- must inherit from GitlabError """ + def wrap(f): @functools.wraps(f) def wrapped_f(*args, **kwargs): @@ -255,5 +255,7 @@ def on_http_error(error): return f(*args, **kwargs) except GitlabHttpError as e: raise error(e.error_message, e.response_code, e.response_body) + return wrapped_f + return wrap diff --git a/gitlab/mixins.py b/gitlab/mixins.py index ca68658..70de992 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -42,8 +42,8 @@ class GetMixin(object): GitlabGetError: If the server cannot perform the request """ if not isinstance(id, int): - id = id.replace('/', '%2F') - path = '%s/%s' % (self.path, id) + id = id.replace("/", "%2F") + path = "%s/%s" % (self.path, id) if lazy is True: return self._obj_cls(self, {self._obj_cls._id_attr: id}) server_data = self.gitlab.http_get(path, **kwargs) @@ -86,7 +86,7 @@ class RefreshMixin(object): GitlabGetError: If the server cannot perform the request """ if self._id_attr: - path = '%s/%s' % (self.manager.path, self.id) + path = "%s/%s" % (self.manager.path, self.id) else: path = self.manager.path server_data = self.manager.gitlab.http_get(path, **kwargs) @@ -117,10 +117,10 @@ class ListMixin(object): # Duplicate data to avoid messing with what the user sent us data = kwargs.copy() if self.gitlab.per_page: - data.setdefault('per_page', self.gitlab.per_page) + data.setdefault("per_page", self.gitlab.per_page) # We get the attributes that need some special transformation - types = getattr(self, '_types', {}) + types = getattr(self, "_types", {}) if types: for attr_name, type_cls in types.items(): if attr_name in data.keys(): @@ -128,7 +128,7 @@ class ListMixin(object): data[attr_name] = type_obj.get_for_api() # Allow to overwrite the path, handy for custom listings - path = data.pop('path', self.path) + path = data.pop("path", self.path) obj = self.gitlab.http_list(path, **data) if isinstance(obj, list): @@ -159,7 +159,7 @@ class CreateMixin(object): tuple: 2 items: list of required arguments and list of optional arguments for creation (in that order) """ - return getattr(self, '_create_attrs', (tuple(), tuple())) + return getattr(self, "_create_attrs", (tuple(), tuple())) @exc.on_http_error(exc.GitlabCreateError) def create(self, data, **kwargs): @@ -182,7 +182,7 @@ class CreateMixin(object): files = {} # We get the attributes that need some special transformation - types = getattr(self, '_types', {}) + types = getattr(self, "_types", {}) if types: # Duplicate data to avoid messing with what the user sent us data = data.copy() @@ -199,9 +199,8 @@ class CreateMixin(object): data[attr_name] = type_obj.get_for_api() # Handle specific URL for creation - path = kwargs.pop('path', self.path) - server_data = self.gitlab.http_post(path, post_data=data, files=files, - **kwargs) + path = kwargs.pop("path", self.path) + server_data = self.gitlab.http_post(path, post_data=data, files=files, **kwargs) return self._obj_cls(self, server_data) @@ -223,7 +222,7 @@ class UpdateMixin(object): tuple: 2 items: list of required arguments and list of optional arguments for update (in that order) """ - return getattr(self, '_update_attrs', (tuple(), tuple())) + return getattr(self, "_update_attrs", (tuple(), tuple())) def _get_update_method(self): """Return the HTTP method to use. @@ -231,7 +230,7 @@ class UpdateMixin(object): Returns: object: http_put (default) or http_post """ - if getattr(self, '_update_uses_post', False): + if getattr(self, "_update_uses_post", False): http_method = self.gitlab.http_post else: http_method = self.gitlab.http_put @@ -257,13 +256,13 @@ class UpdateMixin(object): if id is None: path = self.path else: - path = '%s/%s' % (self.path, id) + path = "%s/%s" % (self.path, id) self._check_missing_update_attrs(new_data) files = {} # We get the attributes that need some special transformation - types = getattr(self, '_types', {}) + types = getattr(self, "_types", {}) if types: # Duplicate data to avoid messing with what the user sent us new_data = new_data.copy() @@ -300,8 +299,8 @@ class SetMixin(object): Returns: obj: The created/updated attribute """ - path = '%s/%s' % (self.path, key.replace('/', '%2F')) - data = {'value': value} + path = "%s/%s" % (self.path, key.replace("/", "%2F")) + data = {"value": value} server_data = self.gitlab.http_put(path, post_data=data, **kwargs) return self._obj_cls(self, server_data) @@ -323,8 +322,8 @@ class DeleteMixin(object): path = self.path else: if not isinstance(id, int): - id = id.replace('/', '%2F') - path = '%s/%s' % (self.path, id) + id = id.replace("/", "%2F") + path = "%s/%s" % (self.path, id) self.gitlab.http_delete(path, **kwargs) @@ -338,6 +337,7 @@ class NoUpdateMixin(GetMixin, ListMixin, CreateMixin, DeleteMixin): class SaveMixin(object): """Mixin for RESTObject's that can be updated.""" + def _get_updated_data(self): updated_data = {} required, optional = self.manager.get_update_attrs() @@ -375,6 +375,7 @@ class SaveMixin(object): class ObjectDeleteMixin(object): """Mixin for RESTObject's that can be deleted.""" + def delete(self, **kwargs): """Delete the object from the server. @@ -389,7 +390,7 @@ class ObjectDeleteMixin(object): class UserAgentDetailMixin(object): - @cli.register_custom_action(('Snippet', 'ProjectSnippet', 'ProjectIssue')) + @cli.register_custom_action(("Snippet", "ProjectSnippet", "ProjectIssue")) @exc.on_http_error(exc.GitlabGetError) def user_agent_detail(self, **kwargs): """Get the user agent detail. @@ -401,13 +402,14 @@ class UserAgentDetailMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabGetError: If the server cannot perform the request """ - path = '%s/%s/user_agent_detail' % (self.manager.path, self.get_id()) + path = "%s/%s/user_agent_detail" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) class AccessRequestMixin(object): - @cli.register_custom_action(('ProjectAccessRequest', 'GroupAccessRequest'), - tuple(), ('access_level', )) + @cli.register_custom_action( + ("ProjectAccessRequest", "GroupAccessRequest"), tuple(), ("access_level",) + ) @exc.on_http_error(exc.GitlabUpdateError) def approve(self, access_level=gitlab.DEVELOPER_ACCESS, **kwargs): """Approve an access request. @@ -421,16 +423,14 @@ class AccessRequestMixin(object): GitlabUpdateError: If the server fails to perform the request """ - path = '%s/%s/approve' % (self.manager.path, self.id) - data = {'access_level': access_level} - server_data = self.manager.gitlab.http_put(path, post_data=data, - **kwargs) + path = "%s/%s/approve" % (self.manager.path, self.id) + data = {"access_level": access_level} + server_data = self.manager.gitlab.http_put(path, post_data=data, **kwargs) self._update_attrs(server_data) class SubscribableMixin(object): - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest', - 'ProjectLabel')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest", "ProjectLabel")) @exc.on_http_error(exc.GitlabSubscribeError) def subscribe(self, **kwargs): """Subscribe to the object notifications. @@ -442,12 +442,11 @@ class SubscribableMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabSubscribeError: If the subscription cannot be done """ - path = '%s/%s/subscribe' % (self.manager.path, self.get_id()) + path = "%s/%s/subscribe" % (self.manager.path, self.get_id()) server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest', - 'ProjectLabel')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest", "ProjectLabel")) @exc.on_http_error(exc.GitlabUnsubscribeError) def unsubscribe(self, **kwargs): """Unsubscribe from the object notifications. @@ -459,13 +458,13 @@ class SubscribableMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabUnsubscribeError: If the unsubscription cannot be done """ - path = '%s/%s/unsubscribe' % (self.manager.path, self.get_id()) + path = "%s/%s/unsubscribe" % (self.manager.path, self.get_id()) server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) class TodoMixin(object): - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTodoError) def todo(self, **kwargs): """Create a todo associated to the object. @@ -477,12 +476,12 @@ class TodoMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTodoError: If the todo cannot be set """ - path = '%s/%s/todo' % (self.manager.path, self.get_id()) + path = "%s/%s/todo" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path, **kwargs) class TimeTrackingMixin(object): - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTimeTrackingError) def time_stats(self, **kwargs): """Get time stats for the object. @@ -496,14 +495,13 @@ class TimeTrackingMixin(object): """ # Use the existing time_stats attribute if it exist, otherwise make an # API call - if 'time_stats' in self.attributes: - return self.attributes['time_stats'] + if "time_stats" in self.attributes: + return self.attributes["time_stats"] - path = '%s/%s/time_stats' % (self.manager.path, self.get_id()) + path = "%s/%s/time_stats" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest'), - ('duration', )) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) @exc.on_http_error(exc.GitlabTimeTrackingError) def time_estimate(self, duration, **kwargs): """Set an estimated time of work for the object. @@ -516,11 +514,11 @@ class TimeTrackingMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTimeTrackingError: If the time tracking update cannot be done """ - path = '%s/%s/time_estimate' % (self.manager.path, self.get_id()) - data = {'duration': duration} + path = "%s/%s/time_estimate" % (self.manager.path, self.get_id()) + data = {"duration": duration} return self.manager.gitlab.http_post(path, post_data=data, **kwargs) - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTimeTrackingError) def reset_time_estimate(self, **kwargs): """Resets estimated time for the object to 0 seconds. @@ -532,11 +530,10 @@ class TimeTrackingMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTimeTrackingError: If the time tracking update cannot be done """ - path = '%s/%s/reset_time_estimate' % (self.manager.path, self.get_id()) + path = "%s/%s/reset_time_estimate" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_post(path, **kwargs) - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest'), - ('duration', )) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",)) @exc.on_http_error(exc.GitlabTimeTrackingError) def add_spent_time(self, duration, **kwargs): """Add time spent working on the object. @@ -549,11 +546,11 @@ class TimeTrackingMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTimeTrackingError: If the time tracking update cannot be done """ - path = '%s/%s/add_spent_time' % (self.manager.path, self.get_id()) - data = {'duration': duration} + path = "%s/%s/add_spent_time" % (self.manager.path, self.get_id()) + data = {"duration": duration} return self.manager.gitlab.http_post(path, post_data=data, **kwargs) - @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest')) + @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTimeTrackingError) def reset_spent_time(self, **kwargs): """Resets the time spent working on the object. @@ -565,12 +562,12 @@ class TimeTrackingMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTimeTrackingError: If the time tracking update cannot be done """ - path = '%s/%s/reset_spent_time' % (self.manager.path, self.get_id()) + path = "%s/%s/reset_spent_time" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_post(path, **kwargs) class ParticipantsMixin(object): - @cli.register_custom_action(('ProjectMergeRequest', 'ProjectIssue')) + @cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue")) @exc.on_http_error(exc.GitlabListError) def participants(self, **kwargs): """List the participants. @@ -591,13 +588,14 @@ class ParticipantsMixin(object): RESTObjectList: The list of participants """ - path = '%s/%s/participants' % (self.manager.path, self.get_id()) + path = "%s/%s/participants" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) class BadgeRenderMixin(object): - @cli.register_custom_action(('GroupBadgeManager', 'ProjectBadgeManager'), - ('link_url', 'image_url')) + @cli.register_custom_action( + ("GroupBadgeManager", "ProjectBadgeManager"), ("link_url", "image_url") + ) @exc.on_http_error(exc.GitlabRenderError) def render(self, link_url, image_url, **kwargs): """Preview link_url and image_url after interpolation. @@ -614,6 +612,6 @@ class BadgeRenderMixin(object): Returns: dict: The rendering properties """ - path = '%s/render' % self.path - data = {'link_url': link_url, 'image_url': image_url} + path = "%s/render" % self.path + data = {"link_url": link_url, "image_url": image_url} return self.gitlab.http_get(path, data, **kwargs) diff --git a/gitlab/tests/test_base.py b/gitlab/tests/test_base.py index d38c507..2526bee 100644 --- a/gitlab/tests/test_base.py +++ b/gitlab/tests/test_base.py @@ -16,6 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import pickle + try: import unittest except ImportError: @@ -34,23 +35,23 @@ class FakeObject(base.RESTObject): class FakeManager(base.RESTManager): _obj_cls = FakeObject - _path = '/tests' + _path = "/tests" class TestRESTManager(unittest.TestCase): def test_computed_path_simple(self): class MGR(base.RESTManager): - _path = '/tests' + _path = "/tests" _obj_cls = object mgr = MGR(FakeGitlab()) - self.assertEqual(mgr._computed_path, '/tests') + self.assertEqual(mgr._computed_path, "/tests") def test_computed_path_with_parent(self): class MGR(base.RESTManager): - _path = '/tests/%(test_id)s/cases' + _path = "/tests/%(test_id)s/cases" _obj_cls = object - _from_parent_attrs = {'test_id': 'id'} + _from_parent_attrs = {"test_id": "id"} class Parent(object): id = 42 @@ -59,15 +60,15 @@ class TestRESTManager(unittest.TestCase): no_id = 0 mgr = MGR(FakeGitlab(), parent=Parent()) - self.assertEqual(mgr._computed_path, '/tests/42/cases') + self.assertEqual(mgr._computed_path, "/tests/42/cases") def test_path_property(self): class MGR(base.RESTManager): - _path = '/tests' + _path = "/tests" _obj_cls = object mgr = MGR(FakeGitlab()) - self.assertEqual(mgr.path, '/tests') + self.assertEqual(mgr.path, "/tests") class TestRESTObject(unittest.TestCase): @@ -76,36 +77,36 @@ class TestRESTObject(unittest.TestCase): self.manager = FakeManager(self.gitlab) def test_instanciate(self): - obj = FakeObject(self.manager, {'foo': 'bar'}) + obj = FakeObject(self.manager, {"foo": "bar"}) - self.assertDictEqual({'foo': 'bar'}, obj._attrs) + self.assertDictEqual({"foo": "bar"}, obj._attrs) self.assertDictEqual({}, obj._updated_attrs) self.assertEqual(None, obj._create_managers()) self.assertEqual(self.manager, obj.manager) self.assertEqual(self.gitlab, obj.manager.gitlab) def test_pickability(self): - obj = FakeObject(self.manager, {'foo': 'bar'}) + obj = FakeObject(self.manager, {"foo": "bar"}) original_obj_module = obj._module pickled = pickle.dumps(obj) unpickled = pickle.loads(pickled) self.assertIsInstance(unpickled, FakeObject) - self.assertTrue(hasattr(unpickled, '_module')) + self.assertTrue(hasattr(unpickled, "_module")) self.assertEqual(unpickled._module, original_obj_module) def test_attrs(self): - obj = FakeObject(self.manager, {'foo': 'bar'}) + obj = FakeObject(self.manager, {"foo": "bar"}) - self.assertEqual('bar', obj.foo) - self.assertRaises(AttributeError, getattr, obj, 'bar') + self.assertEqual("bar", obj.foo) + self.assertRaises(AttributeError, getattr, obj, "bar") - obj.bar = 'baz' - self.assertEqual('baz', obj.bar) - self.assertDictEqual({'foo': 'bar'}, obj._attrs) - self.assertDictEqual({'bar': 'baz'}, obj._updated_attrs) + obj.bar = "baz" + self.assertEqual("baz", obj.bar) + self.assertDictEqual({"foo": "bar"}, obj._attrs) + self.assertDictEqual({"bar": "baz"}, obj._updated_attrs) def test_get_id(self): - obj = FakeObject(self.manager, {'foo': 'bar'}) + obj = FakeObject(self.manager, {"foo": "bar"}) obj.id = 42 self.assertEqual(42, obj.get_id()) @@ -114,50 +115,47 @@ class TestRESTObject(unittest.TestCase): def test_custom_id_attr(self): class OtherFakeObject(FakeObject): - _id_attr = 'foo' + _id_attr = "foo" - obj = OtherFakeObject(self.manager, {'foo': 'bar'}) - self.assertEqual('bar', obj.get_id()) + obj = OtherFakeObject(self.manager, {"foo": "bar"}) + self.assertEqual("bar", obj.get_id()) def test_update_attrs(self): - obj = FakeObject(self.manager, {'foo': 'bar'}) - obj.bar = 'baz' - obj._update_attrs({'foo': 'foo', 'bar': 'bar'}) - self.assertDictEqual({'foo': 'foo', 'bar': 'bar'}, obj._attrs) + obj = FakeObject(self.manager, {"foo": "bar"}) + obj.bar = "baz" + obj._update_attrs({"foo": "foo", "bar": "bar"}) + self.assertDictEqual({"foo": "foo", "bar": "bar"}, obj._attrs) self.assertDictEqual({}, obj._updated_attrs) def test_create_managers(self): class ObjectWithManager(FakeObject): - _managers = (('fakes', 'FakeManager'), ) + _managers = (("fakes", "FakeManager"),) - obj = ObjectWithManager(self.manager, {'foo': 'bar'}) + obj = ObjectWithManager(self.manager, {"foo": "bar"}) obj.id = 42 self.assertIsInstance(obj.fakes, FakeManager) self.assertEqual(obj.fakes.gitlab, self.gitlab) self.assertEqual(obj.fakes._parent, obj) def test_equality(self): - obj1 = FakeObject(self.manager, {'id': 'foo'}) - obj2 = FakeObject(self.manager, {'id': 'foo', 'other_attr': 'bar'}) + obj1 = FakeObject(self.manager, {"id": "foo"}) + obj2 = FakeObject(self.manager, {"id": "foo", "other_attr": "bar"}) self.assertEqual(obj1, obj2) def test_equality_custom_id(self): class OtherFakeObject(FakeObject): - _id_attr = 'foo' + _id_attr = "foo" - obj1 = OtherFakeObject(self.manager, {'foo': 'bar'}) - obj2 = OtherFakeObject( - self.manager, - {'foo': 'bar', 'other_attr': 'baz'} - ) + obj1 = OtherFakeObject(self.manager, {"foo": "bar"}) + obj2 = OtherFakeObject(self.manager, {"foo": "bar", "other_attr": "baz"}) self.assertEqual(obj1, obj2) def test_inequality(self): - obj1 = FakeObject(self.manager, {'id': 'foo'}) - obj2 = FakeObject(self.manager, {'id': 'bar'}) + obj1 = FakeObject(self.manager, {"id": "foo"}) + obj2 = FakeObject(self.manager, {"id": "bar"}) self.assertNotEqual(obj1, obj2) def test_inequality_no_id(self): - obj1 = FakeObject(self.manager, {'attr1': 'foo'}) - obj2 = FakeObject(self.manager, {'attr1': 'bar'}) + obj1 = FakeObject(self.manager, {"attr1": "foo"}) + obj2 = FakeObject(self.manager, {"attr1": "bar"}) self.assertNotEqual(obj1, obj2) diff --git a/gitlab/tests/test_cli.py b/gitlab/tests/test_cli.py index 3fe4a4e..bc49d8b 100644 --- a/gitlab/tests/test_cli.py +++ b/gitlab/tests/test_cli.py @@ -22,6 +22,7 @@ from __future__ import absolute_import import argparse import os import tempfile + try: from contextlib import redirect_stderr # noqa: H302 except ImportError: @@ -34,6 +35,7 @@ except ImportError: yield sys.stderr = old_target + try: import unittest except ImportError: @@ -69,8 +71,8 @@ class TestCLI(unittest.TestCase): self.assertEqual(test.exception.code, 1) def test_parse_value(self): - ret = cli._parse_value('foobar') - self.assertEqual(ret, 'foobar') + ret = cli._parse_value("foobar") + self.assertEqual(ret, "foobar") ret = cli._parse_value(True) self.assertEqual(ret, True) @@ -82,36 +84,39 @@ class TestCLI(unittest.TestCase): self.assertEqual(ret, None) fd, temp_path = tempfile.mkstemp() - os.write(fd, b'content') + os.write(fd, b"content") os.close(fd) - ret = cli._parse_value('@%s' % temp_path) - self.assertEqual(ret, 'content') + ret = cli._parse_value("@%s" % temp_path) + self.assertEqual(ret, "content") os.unlink(temp_path) fl = six.StringIO() with redirect_stderr(fl): with self.assertRaises(SystemExit) as exc: - cli._parse_value('@/thisfileprobablydoesntexist') - self.assertEqual(fl.getvalue(), - "[Errno 2] No such file or directory:" - " '/thisfileprobablydoesntexist'\n") + cli._parse_value("@/thisfileprobablydoesntexist") + self.assertEqual( + fl.getvalue(), + "[Errno 2] No such file or directory:" + " '/thisfileprobablydoesntexist'\n", + ) self.assertEqual(exc.exception.code, 1) def test_base_parser(self): parser = cli._get_base_parser() - args = parser.parse_args(['-v', '-g', 'gl_id', - '-c', 'foo.cfg', '-c', 'bar.cfg']) + args = parser.parse_args( + ["-v", "-g", "gl_id", "-c", "foo.cfg", "-c", "bar.cfg"] + ) self.assertTrue(args.verbose) - self.assertEqual(args.gitlab, 'gl_id') - self.assertEqual(args.config_file, ['foo.cfg', 'bar.cfg']) + self.assertEqual(args.gitlab, "gl_id") + self.assertEqual(args.config_file, ["foo.cfg", "bar.cfg"]) class TestV4CLI(unittest.TestCase): def test_parse_args(self): parser = cli._get_parser(gitlab.v4.cli) - args = parser.parse_args(['project', 'list']) - self.assertEqual(args.what, 'project') - self.assertEqual(args.action, 'list') + args = parser.parse_args(["project", "list"]) + self.assertEqual(args.what, "project") + self.assertEqual(args.action, "list") def test_parser(self): parser = cli._get_parser(gitlab.v4.cli) @@ -121,29 +126,29 @@ class TestV4CLI(unittest.TestCase): subparsers = action break self.assertIsNotNone(subparsers) - self.assertIn('project', subparsers.choices) + self.assertIn("project", subparsers.choices) user_subparsers = None - for action in subparsers.choices['project']._actions: + for action in subparsers.choices["project"]._actions: if type(action) == argparse._SubParsersAction: user_subparsers = action break self.assertIsNotNone(user_subparsers) - self.assertIn('list', user_subparsers.choices) - self.assertIn('get', user_subparsers.choices) - self.assertIn('delete', user_subparsers.choices) - self.assertIn('update', user_subparsers.choices) - self.assertIn('create', user_subparsers.choices) - self.assertIn('archive', user_subparsers.choices) - self.assertIn('unarchive', user_subparsers.choices) + self.assertIn("list", user_subparsers.choices) + self.assertIn("get", user_subparsers.choices) + self.assertIn("delete", user_subparsers.choices) + self.assertIn("update", user_subparsers.choices) + self.assertIn("create", user_subparsers.choices) + self.assertIn("archive", user_subparsers.choices) + self.assertIn("unarchive", user_subparsers.choices) - actions = user_subparsers.choices['create']._option_string_actions - self.assertFalse(actions['--description'].required) + actions = user_subparsers.choices["create"]._option_string_actions + self.assertFalse(actions["--description"].required) user_subparsers = None - for action in subparsers.choices['group']._actions: + for action in subparsers.choices["group"]._actions: if type(action) == argparse._SubParsersAction: user_subparsers = action break - actions = user_subparsers.choices['create']._option_string_actions - self.assertTrue(actions['--name'].required) + actions = user_subparsers.choices["create"]._option_string_actions + self.assertTrue(actions["--name"].required) diff --git a/gitlab/tests/test_config.py b/gitlab/tests/test_config.py index d1e668e..9e19ce8 100644 --- a/gitlab/tests/test_config.py +++ b/gitlab/tests/test_config.py @@ -76,51 +76,51 @@ per_page = 200 class TestConfigParser(unittest.TestCase): - @mock.patch('os.path.exists') + @mock.patch("os.path.exists") def test_missing_config(self, path_exists): path_exists.return_value = False with self.assertRaises(config.GitlabConfigMissingError): - config.GitlabConfigParser('test') + config.GitlabConfigParser("test") - @mock.patch('os.path.exists') - @mock.patch('six.moves.builtins.open') + @mock.patch("os.path.exists") + @mock.patch("six.moves.builtins.open") def test_invalid_id(self, m_open, path_exists): fd = six.StringIO(no_default_config) fd.close = mock.Mock(return_value=None) m_open.return_value = fd path_exists.return_value = True - config.GitlabConfigParser('there') + config.GitlabConfigParser("there") self.assertRaises(config.GitlabIDError, config.GitlabConfigParser) fd = six.StringIO(valid_config) fd.close = mock.Mock(return_value=None) m_open.return_value = fd - self.assertRaises(config.GitlabDataError, - config.GitlabConfigParser, - gitlab_id='not_there') + self.assertRaises( + config.GitlabDataError, config.GitlabConfigParser, gitlab_id="not_there" + ) - @mock.patch('os.path.exists') - @mock.patch('six.moves.builtins.open') + @mock.patch("os.path.exists") + @mock.patch("six.moves.builtins.open") def test_invalid_data(self, m_open, path_exists): fd = six.StringIO(missing_attr_config) - fd.close = mock.Mock(return_value=None, - side_effect=lambda: fd.seek(0)) + fd.close = mock.Mock(return_value=None, side_effect=lambda: fd.seek(0)) m_open.return_value = fd path_exists.return_value = True - config.GitlabConfigParser('one') - config.GitlabConfigParser('one') - self.assertRaises(config.GitlabDataError, config.GitlabConfigParser, - gitlab_id='two') - self.assertRaises(config.GitlabDataError, config.GitlabConfigParser, - gitlab_id='three') + config.GitlabConfigParser("one") + config.GitlabConfigParser("one") + self.assertRaises( + config.GitlabDataError, config.GitlabConfigParser, gitlab_id="two" + ) + self.assertRaises( + config.GitlabDataError, config.GitlabConfigParser, gitlab_id="three" + ) with self.assertRaises(config.GitlabDataError) as emgr: - config.GitlabConfigParser('four') - self.assertEqual('Unsupported per_page number: 200', - emgr.exception.args[0]) + config.GitlabConfigParser("four") + self.assertEqual("Unsupported per_page number: 200", emgr.exception.args[0]) - @mock.patch('os.path.exists') - @mock.patch('six.moves.builtins.open') + @mock.patch("os.path.exists") + @mock.patch("six.moves.builtins.open") def test_valid_data(self, m_open, path_exists): fd = six.StringIO(valid_config) fd.close = mock.Mock(return_value=None) diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index fddd5ed..c2b372a 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -21,6 +21,7 @@ from __future__ import print_function import os import pickle import tempfile + try: import unittest except ImportError: @@ -64,42 +65,52 @@ class TestSanitize(unittest.TestCase): class TestGitlabList(unittest.TestCase): def setUp(self): - self.gl = Gitlab("http://localhost", private_token="private_token", - api_version=4) + self.gl = Gitlab( + "http://localhost", private_token="private_token", api_version=4 + ) def test_build_list(self): - @urlmatch(scheme='http', netloc="localhost", path="/api/v4/tests", - method="get") + @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") def resp_1(url, request): - headers = {'content-type': 'application/json', - 'X-Page': 1, - 'X-Next-Page': 2, - 'X-Per-Page': 1, - 'X-Total-Pages': 2, - 'X-Total': 2, - 'Link': ( - '<http://localhost/api/v4/tests?per_page=1&page=2>;' - ' rel="next"')} + headers = { + "content-type": "application/json", + "X-Page": 1, + "X-Next-Page": 2, + "X-Per-Page": 1, + "X-Total-Pages": 2, + "X-Total": 2, + "Link": ( + "<http://localhost/api/v4/tests?per_page=1&page=2>;" ' rel="next"' + ), + } content = '[{"a": "b"}]' return response(200, content, headers, None, 5, request) - @urlmatch(scheme='http', netloc="localhost", path="/api/v4/tests", - method='get', query=r'.*page=2') + @urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/tests", + method="get", + query=r".*page=2", + ) def resp_2(url, request): - headers = {'content-type': 'application/json', - 'X-Page': 2, - 'X-Next-Page': 2, - 'X-Per-Page': 1, - 'X-Total-Pages': 2, - 'X-Total': 2} + headers = { + "content-type": "application/json", + "X-Page": 2, + "X-Next-Page": 2, + "X-Per-Page": 1, + "X-Total-Pages": 2, + "X-Total": 2, + } content = '[{"c": "d"}]' return response(200, content, headers, None, 5, request) with HTTMock(resp_1): - obj = self.gl.http_list('/tests', as_list=False) + obj = self.gl.http_list("/tests", as_list=False) self.assertEqual(len(obj), 2) - self.assertEqual(obj._next_url, - 'http://localhost/api/v4/tests?per_page=1&page=2') + self.assertEqual( + obj._next_url, "http://localhost/api/v4/tests?per_page=1&page=2" + ) self.assertEqual(obj.current_page, 1) self.assertEqual(obj.prev_page, None) self.assertEqual(obj.next_page, 2) @@ -110,306 +121,343 @@ class TestGitlabList(unittest.TestCase): with HTTMock(resp_2): l = list(obj) self.assertEqual(len(l), 2) - self.assertEqual(l[0]['a'], 'b') - self.assertEqual(l[1]['c'], 'd') + self.assertEqual(l[0]["a"], "b") + self.assertEqual(l[1]["c"], "d") class TestGitlabHttpMethods(unittest.TestCase): def setUp(self): - self.gl = Gitlab("http://localhost", private_token="private_token", - api_version=4) + self.gl = Gitlab( + "http://localhost", private_token="private_token", api_version=4 + ) def test_build_url(self): - r = self.gl._build_url('http://localhost/api/v4') - self.assertEqual(r, 'http://localhost/api/v4') - r = self.gl._build_url('https://localhost/api/v4') - self.assertEqual(r, 'https://localhost/api/v4') - r = self.gl._build_url('/projects') - self.assertEqual(r, 'http://localhost/api/v4/projects') + r = self.gl._build_url("http://localhost/api/v4") + self.assertEqual(r, "http://localhost/api/v4") + r = self.gl._build_url("https://localhost/api/v4") + self.assertEqual(r, "https://localhost/api/v4") + r = self.gl._build_url("/projects") + self.assertEqual(r, "http://localhost/api/v4/projects") def test_http_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '[{"name": "project1"}]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - http_r = self.gl.http_request('get', '/projects') + http_r = self.gl.http_request("get", "/projects") http_r.json() self.assertEqual(http_r.status_code, 200) def test_http_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="get" + ) def resp_cont(url, request): - content = {'Here is wh it failed'} + content = {"Here is wh it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, - self.gl.http_request, - 'get', '/not_there') + self.assertRaises( + GitlabHttpError, self.gl.http_request, "get", "/not_there" + ) def test_get_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"name": "project1"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_get('/projects') + result = self.gl.http_get("/projects") self.assertIsInstance(result, dict) - self.assertEqual(result['name'], 'project1') + self.assertEqual(result["name"], "project1") def test_get_request_raw(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/octet-stream'} - content = 'content' + headers = {"content-type": "application/octet-stream"} + content = "content" return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_get('/projects') - self.assertEqual(result.content.decode('utf-8'), 'content') + result = self.gl.http_get("/projects") + self.assertEqual(result.content.decode("utf-8"), "content") def test_get_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="get" + ) def resp_cont(url, request): - content = {'Here is wh it failed'} + content = {"Here is wh it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, self.gl.http_get, '/not_there') + self.assertRaises(GitlabHttpError, self.gl.http_get, "/not_there") def test_get_request_invalid_data(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '["name": "project1"]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabParsingError, self.gl.http_get, - '/projects') + self.assertRaises(GitlabParsingError, self.gl.http_get, "/projects") def test_list_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json', 'X-Total': 1} + headers = {"content-type": "application/json", "X-Total": 1} content = '[{"name": "project1"}]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_list('/projects', as_list=True) + result = self.gl.http_list("/projects", as_list=True) self.assertIsInstance(result, list) self.assertEqual(len(result), 1) with HTTMock(resp_cont): - result = self.gl.http_list('/projects', as_list=False) + result = self.gl.http_list("/projects", as_list=False) self.assertIsInstance(result, GitlabList) self.assertEqual(len(result), 1) with HTTMock(resp_cont): - result = self.gl.http_list('/projects', all=True) + result = self.gl.http_list("/projects", all=True) self.assertIsInstance(result, list) self.assertEqual(len(result), 1) def test_list_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="get" + ) def resp_cont(url, request): - content = {'Here is why it failed'} + content = {"Here is why it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, self.gl.http_list, '/not_there') + self.assertRaises(GitlabHttpError, self.gl.http_list, "/not_there") def test_list_request_invalid_data(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="get" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '["name": "project1"]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabParsingError, self.gl.http_list, - '/projects') + self.assertRaises(GitlabParsingError, self.gl.http_list, "/projects") def test_post_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="post" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"name": "project1"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_post('/projects') + result = self.gl.http_post("/projects") self.assertIsInstance(result, dict) - self.assertEqual(result['name'], 'project1') + self.assertEqual(result["name"], "project1") def test_post_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="post" + ) def resp_cont(url, request): - content = {'Here is wh it failed'} + content = {"Here is wh it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, self.gl.http_post, '/not_there') + self.assertRaises(GitlabHttpError, self.gl.http_post, "/not_there") def test_post_request_invalid_data(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="post" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '["name": "project1"]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabParsingError, self.gl.http_post, - '/projects') + self.assertRaises(GitlabParsingError, self.gl.http_post, "/projects") def test_put_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="put" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"name": "project1"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_put('/projects') + result = self.gl.http_put("/projects") self.assertIsInstance(result, dict) - self.assertEqual(result['name'], 'project1') + self.assertEqual(result["name"], "project1") def test_put_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="put" + ) def resp_cont(url, request): - content = {'Here is wh it failed'} + content = {"Here is wh it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, self.gl.http_put, '/not_there') + self.assertRaises(GitlabHttpError, self.gl.http_put, "/not_there") def test_put_request_invalid_data(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="put" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '["name": "project1"]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabParsingError, self.gl.http_put, - '/projects') + self.assertRaises(GitlabParsingError, self.gl.http_put, "/projects") def test_delete_request(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", - method="delete") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects", method="delete" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} - content = 'true' + headers = {"content-type": "application/json"} + content = "true" return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): - result = self.gl.http_delete('/projects') + result = self.gl.http_delete("/projects") self.assertIsInstance(result, requests.Response) self.assertEqual(result.json(), True) def test_delete_request_404(self): - @urlmatch(scheme="http", netloc="localhost", - path="/api/v4/not_there", method="delete") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/not_there", method="delete" + ) def resp_cont(url, request): - content = {'Here is wh it failed'} + content = {"Here is wh it failed"} return response(404, content, {}, None, 5, request) with HTTMock(resp_cont): - self.assertRaises(GitlabHttpError, self.gl.http_delete, - '/not_there') + self.assertRaises(GitlabHttpError, self.gl.http_delete, "/not_there") class TestGitlabAuth(unittest.TestCase): def test_invalid_auth_args(self): - self.assertRaises(ValueError, - Gitlab, - "http://localhost", api_version='4', - private_token='private_token', oauth_token='bearer') - self.assertRaises(ValueError, - Gitlab, - "http://localhost", api_version='4', - oauth_token='bearer', http_username='foo', - http_password='bar') - self.assertRaises(ValueError, - Gitlab, - "http://localhost", api_version='4', - private_token='private_token', http_password='bar') - self.assertRaises(ValueError, - Gitlab, - "http://localhost", api_version='4', - private_token='private_token', http_username='foo') + self.assertRaises( + ValueError, + Gitlab, + "http://localhost", + api_version="4", + private_token="private_token", + oauth_token="bearer", + ) + self.assertRaises( + ValueError, + Gitlab, + "http://localhost", + api_version="4", + oauth_token="bearer", + http_username="foo", + http_password="bar", + ) + self.assertRaises( + ValueError, + Gitlab, + "http://localhost", + api_version="4", + private_token="private_token", + http_password="bar", + ) + self.assertRaises( + ValueError, + Gitlab, + "http://localhost", + api_version="4", + private_token="private_token", + http_username="foo", + ) def test_private_token_auth(self): - gl = Gitlab('http://localhost', private_token='private_token', - api_version='4') - self.assertEqual(gl.private_token, 'private_token') + gl = Gitlab("http://localhost", private_token="private_token", api_version="4") + self.assertEqual(gl.private_token, "private_token") self.assertEqual(gl.oauth_token, None) self.assertEqual(gl._http_auth, None) - self.assertEqual(gl.headers['PRIVATE-TOKEN'], 'private_token') - self.assertNotIn('Authorization', gl.headers) + self.assertEqual(gl.headers["PRIVATE-TOKEN"], "private_token") + self.assertNotIn("Authorization", gl.headers) def test_oauth_token_auth(self): - gl = Gitlab('http://localhost', oauth_token='oauth_token', - api_version='4') + gl = Gitlab("http://localhost", oauth_token="oauth_token", api_version="4") self.assertEqual(gl.private_token, None) - self.assertEqual(gl.oauth_token, 'oauth_token') + self.assertEqual(gl.oauth_token, "oauth_token") self.assertEqual(gl._http_auth, None) - self.assertEqual(gl.headers['Authorization'], 'Bearer oauth_token') - self.assertNotIn('PRIVATE-TOKEN', gl.headers) + self.assertEqual(gl.headers["Authorization"], "Bearer oauth_token") + self.assertNotIn("PRIVATE-TOKEN", gl.headers) def test_http_auth(self): - gl = Gitlab('http://localhost', private_token='private_token', - http_username='foo', http_password='bar', api_version='4') - self.assertEqual(gl.private_token, 'private_token') + gl = Gitlab( + "http://localhost", + private_token="private_token", + http_username="foo", + http_password="bar", + api_version="4", + ) + self.assertEqual(gl.private_token, "private_token") self.assertEqual(gl.oauth_token, None) self.assertIsInstance(gl._http_auth, requests.auth.HTTPBasicAuth) - self.assertEqual(gl.headers['PRIVATE-TOKEN'], 'private_token') - self.assertNotIn('Authorization', gl.headers) + self.assertEqual(gl.headers["PRIVATE-TOKEN"], "private_token") + self.assertNotIn("Authorization", gl.headers) class TestGitlab(unittest.TestCase): - def setUp(self): - self.gl = Gitlab("http://localhost", private_token="private_token", - email="testuser@test.com", password="testpassword", - ssl_verify=True, api_version=4) + self.gl = Gitlab( + "http://localhost", + private_token="private_token", + email="testuser@test.com", + password="testpassword", + ssl_verify=True, + api_version=4, + ) def test_pickability(self): original_gl_objects = self.gl._objects pickled = pickle.dumps(self.gl) unpickled = pickle.loads(pickled) self.assertIsInstance(unpickled, Gitlab) - self.assertTrue(hasattr(unpickled, '_objects')) + self.assertTrue(hasattr(unpickled, "_objects")) self.assertEqual(unpickled._objects, original_gl_objects) def test_credentials_auth_nopassword(self): self.gl.email = None self.gl.password = None - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/session", - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/session", method="post" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"message": "message"}'.encode("utf-8") return response(404, content, headers, None, 5, request) @@ -417,10 +465,11 @@ class TestGitlab(unittest.TestCase): self.assertRaises(GitlabHttpError, self.gl._credentials_auth) def test_credentials_auth_notok(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/session", - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/session", method="post" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"message": "message"}'.encode("utf-8") return response(404, content, headers, None, 5, request) @@ -441,12 +490,14 @@ class TestGitlab(unittest.TestCase): id_ = 1 expected = {"PRIVATE-TOKEN": token} - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/session", - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/session", method="post" + ) def resp_cont(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{{"id": {0:d}, "private_token": "{1:s}"}}'.format( - id_, token).encode("utf-8") + id_, token + ).encode("utf-8") return response(201, content, headers, None, 5, request) with HTTMock(resp_cont): @@ -461,12 +512,12 @@ class TestGitlab(unittest.TestCase): name = "username" id_ = 1 - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/user", - method="get") + @urlmatch(scheme="http", netloc="localhost", path="/api/v4/user", method="get") def resp_cont(url, request): - headers = {'content-type': 'application/json'} - content = '{{"id": {0:d}, "username": "{1:s}"}}'.format( - id_, name).encode("utf-8") + headers = {"content-type": "application/json"} + content = '{{"id": {0:d}, "username": "{1:s}"}}'.format(id_, name).encode( + "utf-8" + ) return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): @@ -476,10 +527,11 @@ class TestGitlab(unittest.TestCase): self.assertEqual(type(self.gl.user), CurrentUser) def test_hooks(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/hooks/1", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/hooks/1", method="get" + ) def resp_get_hook(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"url": "testurl", "id": 1}'.encode("utf-8") return response(200, content, headers, None, 5, request) @@ -490,10 +542,11 @@ class TestGitlab(unittest.TestCase): self.assertEqual(data.id, 1) def test_projects(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/projects/1", method="get" + ) def resp_get_project(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"name": "name", "id": 1}'.encode("utf-8") return response(200, content, headers, None, 5, request) @@ -504,12 +557,13 @@ class TestGitlab(unittest.TestCase): self.assertEqual(data.id, 1) def test_groups(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/groups/1", method="get" + ) def resp_get_group(url, request): - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} content = '{"name": "name", "id": 1, "path": "path"}' - content = content.encode('utf-8') + content = content.encode("utf-8") return response(200, content, headers, None, 5, request) with HTTMock(resp_get_group): @@ -520,27 +574,30 @@ class TestGitlab(unittest.TestCase): self.assertEqual(data.id, 1) def test_issues(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/issues", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/issues", method="get" + ) def resp_get_issue(url, request): - headers = {'content-type': 'application/json'} - content = ('[{"name": "name", "id": 1}, ' - '{"name": "other_name", "id": 2}]') + headers = {"content-type": "application/json"} + content = '[{"name": "name", "id": 1}, ' '{"name": "other_name", "id": 2}]' content = content.encode("utf-8") return response(200, content, headers, None, 5, request) with HTTMock(resp_get_issue): data = self.gl.issues.list() self.assertEqual(data[1].id, 2) - self.assertEqual(data[1].name, 'other_name') + self.assertEqual(data[1].name, "other_name") def test_users(self): - @urlmatch(scheme="http", netloc="localhost", path="/api/v4/users/1", - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/users/1", method="get" + ) def resp_get_user(url, request): - headers = {'content-type': 'application/json'} - content = ('{"name": "name", "id": 1, "password": "password", ' - '"username": "username", "email": "email"}') + headers = {"content-type": "application/json"} + content = ( + '{"name": "name", "id": 1, "password": "password", ' + '"username": "username", "email": "email"}' + ) content = content.encode("utf-8") return response(200, content, headers, None, 5, request) @@ -558,13 +615,14 @@ class TestGitlab(unittest.TestCase): def test_from_config(self): config_path = self._default_config() - gitlab.Gitlab.from_config('one', [config_path]) + gitlab.Gitlab.from_config("one", [config_path]) os.unlink(config_path) def test_subclass_from_config(self): class MyGitlab(gitlab.Gitlab): pass + config_path = self._default_config() - gl = MyGitlab.from_config('one', [config_path]) - self.assertEqual(type(gl).__name__, 'MyGitlab') + gl = MyGitlab.from_config("one", [config_path]) + self.assertEqual(type(gl).__name__, "MyGitlab") os.unlink(config_path) diff --git a/gitlab/tests/test_mixins.py b/gitlab/tests/test_mixins.py index b3c2e81..56be8f3 100644 --- a/gitlab/tests/test_mixins.py +++ b/gitlab/tests/test_mixins.py @@ -38,47 +38,47 @@ class TestObjectMixinsAttributes(unittest.TestCase): pass obj = O() - self.assertTrue(hasattr(obj, 'approve')) + self.assertTrue(hasattr(obj, "approve")) def test_subscribable_mixin(self): class O(SubscribableMixin): pass obj = O() - self.assertTrue(hasattr(obj, 'subscribe')) - self.assertTrue(hasattr(obj, 'unsubscribe')) + self.assertTrue(hasattr(obj, "subscribe")) + self.assertTrue(hasattr(obj, "unsubscribe")) def test_todo_mixin(self): class O(TodoMixin): pass obj = O() - self.assertTrue(hasattr(obj, 'todo')) + self.assertTrue(hasattr(obj, "todo")) def test_time_tracking_mixin(self): class O(TimeTrackingMixin): pass obj = O() - self.assertTrue(hasattr(obj, 'time_stats')) - self.assertTrue(hasattr(obj, 'time_estimate')) - self.assertTrue(hasattr(obj, 'reset_time_estimate')) - self.assertTrue(hasattr(obj, 'add_spent_time')) - self.assertTrue(hasattr(obj, 'reset_spent_time')) + self.assertTrue(hasattr(obj, "time_stats")) + self.assertTrue(hasattr(obj, "time_estimate")) + self.assertTrue(hasattr(obj, "reset_time_estimate")) + self.assertTrue(hasattr(obj, "add_spent_time")) + self.assertTrue(hasattr(obj, "reset_spent_time")) def test_set_mixin(self): class O(SetMixin): pass obj = O() - self.assertTrue(hasattr(obj, 'set')) + self.assertTrue(hasattr(obj, "set")) def test_user_agent_detail_mixin(self): class O(UserAgentDetailMixin): pass obj = O() - self.assertTrue(hasattr(obj, 'user_agent_detail')) + self.assertTrue(hasattr(obj, "user_agent_detail")) class TestMetaMixins(unittest.TestCase): @@ -87,11 +87,11 @@ class TestMetaMixins(unittest.TestCase): pass obj = M() - self.assertTrue(hasattr(obj, 'list')) - self.assertTrue(hasattr(obj, 'get')) - self.assertFalse(hasattr(obj, 'create')) - self.assertFalse(hasattr(obj, 'update')) - self.assertFalse(hasattr(obj, 'delete')) + self.assertTrue(hasattr(obj, "list")) + self.assertTrue(hasattr(obj, "get")) + self.assertFalse(hasattr(obj, "create")) + self.assertFalse(hasattr(obj, "update")) + self.assertFalse(hasattr(obj, "delete")) self.assertIsInstance(obj, ListMixin) self.assertIsInstance(obj, GetMixin) @@ -100,11 +100,11 @@ class TestMetaMixins(unittest.TestCase): pass obj = M() - self.assertTrue(hasattr(obj, 'get')) - self.assertTrue(hasattr(obj, 'list')) - self.assertTrue(hasattr(obj, 'create')) - self.assertTrue(hasattr(obj, 'update')) - self.assertTrue(hasattr(obj, 'delete')) + self.assertTrue(hasattr(obj, "get")) + self.assertTrue(hasattr(obj, "list")) + self.assertTrue(hasattr(obj, "create")) + self.assertTrue(hasattr(obj, "update")) + self.assertTrue(hasattr(obj, "delete")) self.assertIsInstance(obj, ListMixin) self.assertIsInstance(obj, GetMixin) self.assertIsInstance(obj, CreateMixin) @@ -116,11 +116,11 @@ class TestMetaMixins(unittest.TestCase): pass obj = M() - self.assertTrue(hasattr(obj, 'get')) - self.assertTrue(hasattr(obj, 'list')) - self.assertTrue(hasattr(obj, 'create')) - self.assertFalse(hasattr(obj, 'update')) - self.assertTrue(hasattr(obj, 'delete')) + self.assertTrue(hasattr(obj, "get")) + self.assertTrue(hasattr(obj, "list")) + self.assertTrue(hasattr(obj, "create")) + self.assertFalse(hasattr(obj, "update")) + self.assertTrue(hasattr(obj, "delete")) self.assertIsInstance(obj, ListMixin) self.assertIsInstance(obj, GetMixin) self.assertIsInstance(obj, CreateMixin) @@ -133,23 +133,25 @@ class FakeObject(base.RESTObject): class FakeManager(base.RESTManager): - _path = '/tests' + _path = "/tests" _obj_cls = FakeObject class TestMixinMethods(unittest.TestCase): def setUp(self): - self.gl = Gitlab("http://localhost", private_token="private_token", - api_version=4) + self.gl = Gitlab( + "http://localhost", private_token="private_token", api_version=4 + ) def test_get_mixin(self): class M(GetMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/42', - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/42", method="get" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "bar"}' return response(200, content, headers, None, 5, request) @@ -157,36 +159,36 @@ class TestMixinMethods(unittest.TestCase): mgr = M(self.gl) obj = mgr.get(42) self.assertIsInstance(obj, FakeObject) - self.assertEqual(obj.foo, 'bar') + self.assertEqual(obj.foo, "bar") self.assertEqual(obj.id, 42) def test_refresh_mixin(self): class O(RefreshMixin, FakeObject): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/42', - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/42", method="get" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "bar"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = FakeManager(self.gl) - obj = O(mgr, {'id': 42}) + obj = O(mgr, {"id": 42}) res = obj.refresh() self.assertIsNone(res) - self.assertEqual(obj.foo, 'bar') + self.assertEqual(obj.foo, "bar") self.assertEqual(obj.id, 42) def test_get_without_id_mixin(self): class M(GetWithoutIdMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests', - method="get") + @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"foo": "bar"}' return response(200, content, headers, None, 5, request) @@ -194,17 +196,16 @@ class TestMixinMethods(unittest.TestCase): mgr = M(self.gl) obj = mgr.get() self.assertIsInstance(obj, FakeObject) - self.assertEqual(obj.foo, 'bar') - self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, "bar") + self.assertFalse(hasattr(obj, "id")) def test_list_mixin(self): class M(ListMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests', - method="get") + @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="get") def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '[{"id": 42, "foo": "bar"},{"id": 43, "foo": "baz"}]' return response(200, content, headers, None, 5, request) @@ -229,20 +230,21 @@ class TestMixinMethods(unittest.TestCase): class M(ListMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/others', - method="get") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/others", method="get" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '[{"id": 42, "foo": "bar"}]' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - obj_list = mgr.list(path='/others', as_list=False) + obj_list = mgr.list(path="/others", as_list=False) self.assertIsInstance(obj_list, base.RESTObjectList) obj = obj_list.next() self.assertEqual(obj.id, 42) - self.assertEqual(obj.foo, 'bar') + self.assertEqual(obj.foo, "bar") self.assertRaises(StopIteration, obj_list.next) def test_create_mixin_get_attrs(self): @@ -250,8 +252,8 @@ class TestMixinMethods(unittest.TestCase): pass class M2(CreateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) mgr = M1(self.gl) required, optional = mgr.get_create_attrs() @@ -260,69 +262,71 @@ class TestMixinMethods(unittest.TestCase): mgr = M2(self.gl) required, optional = mgr.get_create_attrs() - self.assertIn('foo', required) - self.assertIn('bar', optional) - self.assertIn('baz', optional) - self.assertNotIn('bam', optional) + self.assertIn("foo", required) + self.assertIn("bar", optional) + self.assertIn("baz", optional) + self.assertNotIn("bam", optional) def test_create_mixin_missing_attrs(self): class M(CreateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) + _create_attrs = (("foo",), ("bar", "baz")) mgr = M(self.gl) - data = {'foo': 'bar', 'baz': 'blah'} + data = {"foo": "bar", "baz": "blah"} mgr._check_missing_create_attrs(data) - data = {'baz': 'blah'} + data = {"baz": "blah"} with self.assertRaises(AttributeError) as error: mgr._check_missing_create_attrs(data) - self.assertIn('foo', str(error.exception)) + self.assertIn("foo", str(error.exception)) def test_create_mixin(self): class M(CreateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests', - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests", method="post" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "bar"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - obj = mgr.create({'foo': 'bar'}) + obj = mgr.create({"foo": "bar"}) self.assertIsInstance(obj, FakeObject) self.assertEqual(obj.id, 42) - self.assertEqual(obj.foo, 'bar') + self.assertEqual(obj.foo, "bar") def test_create_mixin_custom_path(self): class M(CreateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/others', - method="post") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/others", method="post" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "bar"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - obj = mgr.create({'foo': 'bar'}, path='/others') + obj = mgr.create({"foo": "bar"}, path="/others") self.assertIsInstance(obj, FakeObject) self.assertEqual(obj.id, 42) - self.assertEqual(obj.foo, 'bar') + self.assertEqual(obj.foo, "bar") def test_update_mixin_get_attrs(self): class M1(UpdateMixin, FakeManager): pass class M2(UpdateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) mgr = M1(self.gl) required, optional = mgr.get_update_attrs() @@ -331,70 +335,71 @@ class TestMixinMethods(unittest.TestCase): mgr = M2(self.gl) required, optional = mgr.get_update_attrs() - self.assertIn('foo', required) - self.assertIn('bam', optional) - self.assertNotIn('bar', optional) - self.assertNotIn('baz', optional) + self.assertIn("foo", required) + self.assertIn("bam", optional) + self.assertNotIn("bar", optional) + self.assertNotIn("baz", optional) def test_update_mixin_missing_attrs(self): class M(UpdateMixin, FakeManager): - _update_attrs = (('foo',), ('bar', 'baz')) + _update_attrs = (("foo",), ("bar", "baz")) mgr = M(self.gl) - data = {'foo': 'bar', 'baz': 'blah'} + data = {"foo": "bar", "baz": "blah"} mgr._check_missing_update_attrs(data) - data = {'baz': 'blah'} + data = {"baz": "blah"} with self.assertRaises(AttributeError) as error: mgr._check_missing_update_attrs(data) - self.assertIn('foo', str(error.exception)) + self.assertIn("foo", str(error.exception)) def test_update_mixin(self): class M(UpdateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/42', - method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/42", method="put" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "baz"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - server_data = mgr.update(42, {'foo': 'baz'}) + server_data = mgr.update(42, {"foo": "baz"}) self.assertIsInstance(server_data, dict) - self.assertEqual(server_data['id'], 42) - self.assertEqual(server_data['foo'], 'baz') + self.assertEqual(server_data["id"], 42) + self.assertEqual(server_data["foo"], "baz") def test_update_mixin_no_id(self): class M(UpdateMixin, FakeManager): - _create_attrs = (('foo',), ('bar', 'baz')) - _update_attrs = (('foo',), ('bam', )) + _create_attrs = (("foo",), ("bar", "baz")) + _update_attrs = (("foo",), ("bam",)) - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests', - method="put") + @urlmatch(scheme="http", netloc="localhost", path="/api/v4/tests", method="put") def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"foo": "baz"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - server_data = mgr.update(new_data={'foo': 'baz'}) + server_data = mgr.update(new_data={"foo": "baz"}) self.assertIsInstance(server_data, dict) - self.assertEqual(server_data['foo'], 'baz') + self.assertEqual(server_data["foo"], "baz") def test_delete_mixin(self): class M(DeleteMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/42', - method="delete") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/42", method="delete" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} - content = '' + headers = {"Content-Type": "application/json"} + content = "" return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): @@ -408,35 +413,37 @@ class TestMixinMethods(unittest.TestCase): class O(SaveMixin, RESTObject): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/42', - method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/42", method="put" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"id": 42, "foo": "baz"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - obj = O(mgr, {'id': 42, 'foo': 'bar'}) - obj.foo = 'baz' + obj = O(mgr, {"id": 42, "foo": "bar"}) + obj.foo = "baz" obj.save() - self.assertEqual(obj._attrs['foo'], 'baz') + self.assertEqual(obj._attrs["foo"], "baz") self.assertDictEqual(obj._updated_attrs, {}) def test_set_mixin(self): class M(SetMixin, FakeManager): pass - @urlmatch(scheme="http", netloc="localhost", path='/api/v4/tests/foo', - method="put") + @urlmatch( + scheme="http", netloc="localhost", path="/api/v4/tests/foo", method="put" + ) def resp_cont(url, request): - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} content = '{"key": "foo", "value": "bar"}' return response(200, content, headers, None, 5, request) with HTTMock(resp_cont): mgr = M(self.gl) - obj = mgr.set('foo', 'bar') + obj = mgr.set("foo", "bar") self.assertIsInstance(obj, FakeObject) - self.assertEqual(obj.key, 'foo') - self.assertEqual(obj.value, 'bar') + self.assertEqual(obj.key, "foo") + self.assertEqual(obj.value, "bar") diff --git a/gitlab/tests/test_types.py b/gitlab/tests/test_types.py index c04f68f..4ce065e 100644 --- a/gitlab/tests/test_types.py +++ b/gitlab/tests/test_types.py @@ -25,13 +25,13 @@ from gitlab import types class TestGitlabAttribute(unittest.TestCase): def test_all(self): - o = types.GitlabAttribute('whatever') - self.assertEqual('whatever', o.get()) + o = types.GitlabAttribute("whatever") + self.assertEqual("whatever", o.get()) - o.set_from_cli('whatever2') - self.assertEqual('whatever2', o.get()) + o.set_from_cli("whatever2") + self.assertEqual("whatever2", o.get()) - self.assertEqual('whatever2', o.get_for_api()) + self.assertEqual("whatever2", o.get_for_api()) o = types.GitlabAttribute() self.assertEqual(None, o._value) @@ -40,27 +40,27 @@ class TestGitlabAttribute(unittest.TestCase): class TestListAttribute(unittest.TestCase): def test_list_input(self): o = types.ListAttribute() - o.set_from_cli('foo,bar,baz') - self.assertEqual(['foo', 'bar', 'baz'], o.get()) + o.set_from_cli("foo,bar,baz") + self.assertEqual(["foo", "bar", "baz"], o.get()) - o.set_from_cli('foo') - self.assertEqual(['foo'], o.get()) + o.set_from_cli("foo") + self.assertEqual(["foo"], o.get()) def test_empty_input(self): o = types.ListAttribute() - o.set_from_cli('') + o.set_from_cli("") self.assertEqual([], o.get()) - o.set_from_cli(' ') + o.set_from_cli(" ") self.assertEqual([], o.get()) def test_get_for_api(self): o = types.ListAttribute() - o.set_from_cli('foo,bar,baz') - self.assertEqual('foo,bar,baz', o.get_for_api()) + o.set_from_cli("foo,bar,baz") + self.assertEqual("foo,bar,baz", o.get_for_api()) class TestLowercaseStringAttribute(unittest.TestCase): def test_get_for_api(self): - o = types.LowercaseStringAttribute('FOO') - self.assertEqual('foo', o.get_for_api()) + o = types.LowercaseStringAttribute("FOO") + self.assertEqual("foo", o.get_for_api()) diff --git a/gitlab/types.py b/gitlab/types.py index b32409f..525dc30 100644 --- a/gitlab/types.py +++ b/gitlab/types.py @@ -35,7 +35,7 @@ class ListAttribute(GitlabAttribute): if not cli_value.strip(): self._value = [] else: - self._value = [item.strip() for item in cli_value.split(',')] + self._value = [item.strip() for item in cli_value.split(",")] def get_for_api(self): return ",".join(self._value) @@ -53,4 +53,4 @@ class FileAttribute(GitlabAttribute): class ImageAttribute(FileAttribute): def get_file_name(self, attr_name=None): - return '%s.png' % attr_name if attr_name else 'image.png' + return "%s.png" % attr_name if attr_name else "image.png" diff --git a/gitlab/utils.py b/gitlab/utils.py index 49e2c88..6b43800 100644 --- a/gitlab/utils.py +++ b/gitlab/utils.py @@ -42,12 +42,12 @@ def copy_dict(dest, src): # custom_attributes: {'foo', 'bar'} => # "custom_attributes['foo']": "bar" for dict_k, dict_v in v.items(): - dest['%s[%s]' % (k, dict_k)] = dict_v + dest["%s[%s]" % (k, dict_k)] = dict_v else: dest[k] = v def sanitized_url(url): parsed = six.moves.urllib.parse.urlparse(url) - new_path = parsed.path.replace('.', '%2E') + new_path = parsed.path.replace(".", "%2E") return parsed._replace(path=new_path).geturl() diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 242874d..f0ed199 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -33,12 +33,11 @@ class GitlabCLI(object): def __init__(self, gl, what, action, args): self.cls_name = cli.what_to_cls(what) self.cls = gitlab.v4.objects.__dict__[self.cls_name] - self.what = what.replace('-', '_') + self.what = what.replace("-", "_") self.action = action.lower() self.gl = gl self.args = args - self.mgr_cls = getattr(gitlab.v4.objects, - self.cls.__name__ + 'Manager') + self.mgr_cls = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager") # We could do something smart, like splitting the manager name to find # parents, build the chain of managers to get to the final object. # Instead we do something ugly and efficient: interpolate variables in @@ -46,7 +45,7 @@ class GitlabCLI(object): self.mgr_cls._path = self.mgr_cls._path % self.args self.mgr = self.mgr_cls(gl) - types = getattr(self.mgr_cls, '_types', {}) + types = getattr(self.mgr_cls, "_types", {}) if types: for attr_name, type_cls in types.items(): if attr_name in self.args.keys(): @@ -56,12 +55,12 @@ class GitlabCLI(object): def __call__(self): # Check for a method that matches object + action - method = 'do_%s_%s' % (self.what, self.action) + method = "do_%s_%s" % (self.what, self.action) if hasattr(self, method): return getattr(self, method)() # Fallback to standard actions (get, list, create, ...) - method = 'do_%s' % self.action + method = "do_%s" % self.action if hasattr(self, method): return getattr(self, method)() @@ -74,23 +73,22 @@ class GitlabCLI(object): # Get the object (lazy), then act if in_obj: data = {} - if hasattr(self.mgr, '_from_parent_attrs'): + if hasattr(self.mgr, "_from_parent_attrs"): for k in self.mgr._from_parent_attrs: data[k] = self.args[k] if gitlab.mixins.GetWithoutIdMixin not in inspect.getmro(self.cls): data[self.cls._id_attr] = self.args.pop(self.cls._id_attr) o = self.cls(self.mgr, data) - method_name = self.action.replace('-', '_') + method_name = self.action.replace("-", "_") return getattr(o, method_name)(**self.args) else: return getattr(self.mgr, self.action)(**self.args) def do_project_export_download(self): try: - project = self.gl.projects.get(int(self.args['project_id']), - lazy=True) + project = self.gl.projects.get(int(self.args["project_id"]), lazy=True) data = project.exports.get().download() - if hasattr(sys.stdout, 'buffer'): + if hasattr(sys.stdout, "buffer"): # python3 sys.stdout.buffer.write(data) else: @@ -139,121 +137,163 @@ class GitlabCLI(object): def _populate_sub_parser_by_class(cls, sub_parser): - mgr_cls_name = cls.__name__ + 'Manager' + mgr_cls_name = cls.__name__ + "Manager" mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) - for action_name in ['list', 'get', 'create', 'update', 'delete']: + for action_name in ["list", "get", "create", "update", "delete"]: if not hasattr(mgr_cls, action_name): continue sub_parser_action = sub_parser.add_parser(action_name) sub_parser_action.add_argument("--sudo", required=False) - if hasattr(mgr_cls, '_from_parent_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in mgr_cls._from_parent_attrs] + if hasattr(mgr_cls, "_from_parent_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in mgr_cls._from_parent_attrs + ] if action_name == "list": - if hasattr(mgr_cls, '_list_filters'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in mgr_cls._list_filters] + if hasattr(mgr_cls, "_list_filters"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in mgr_cls._list_filters + ] sub_parser_action.add_argument("--page", required=False) sub_parser_action.add_argument("--per-page", required=False) - sub_parser_action.add_argument("--all", required=False, - action='store_true') + sub_parser_action.add_argument("--all", required=False, action="store_true") - if action_name == 'delete': + if action_name == "delete": if cls._id_attr is not None: - id_attr = cls._id_attr.replace('_', '-') + id_attr = cls._id_attr.replace("_", "-") sub_parser_action.add_argument("--%s" % id_attr, required=True) if action_name == "get": if gitlab.mixins.GetWithoutIdMixin not in inspect.getmro(cls): if cls._id_attr is not None: - id_attr = cls._id_attr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, - required=True) + id_attr = cls._id_attr.replace("_", "-") + sub_parser_action.add_argument("--%s" % id_attr, required=True) - if hasattr(mgr_cls, '_optional_get_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in mgr_cls._optional_get_attrs] + if hasattr(mgr_cls, "_optional_get_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in mgr_cls._optional_get_attrs + ] if action_name == "create": - if hasattr(mgr_cls, '_create_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in mgr_cls._create_attrs[0]] - - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in mgr_cls._create_attrs[1]] + if hasattr(mgr_cls, "_create_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in mgr_cls._create_attrs[0] + ] + + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in mgr_cls._create_attrs[1] + ] if action_name == "update": if cls._id_attr is not None: - id_attr = cls._id_attr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, - required=True) - - if hasattr(mgr_cls, '_update_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in mgr_cls._update_attrs[0] if x != cls._id_attr] + id_attr = cls._id_attr.replace("_", "-") + sub_parser_action.add_argument("--%s" % id_attr, required=True) - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in mgr_cls._update_attrs[1] if x != cls._id_attr] + if hasattr(mgr_cls, "_update_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in mgr_cls._update_attrs[0] + if x != cls._id_attr + ] + + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in mgr_cls._update_attrs[1] + if x != cls._id_attr + ] if cls.__name__ in cli.custom_actions: name = cls.__name__ for action_name in cli.custom_actions[name]: sub_parser_action = sub_parser.add_parser(action_name) # Get the attributes for URL/path construction - if hasattr(mgr_cls, '_from_parent_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in mgr_cls._from_parent_attrs] + if hasattr(mgr_cls, "_from_parent_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in mgr_cls._from_parent_attrs + ] sub_parser_action.add_argument("--sudo", required=False) # We need to get the object somehow if gitlab.mixins.GetWithoutIdMixin not in inspect.getmro(cls): if cls._id_attr is not None: - id_attr = cls._id_attr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, - required=True) + id_attr = cls._id_attr.replace("_", "-") + sub_parser_action.add_argument("--%s" % id_attr, required=True) required, optional, dummy = cli.custom_actions[name][action_name] - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in required if x != cls._id_attr] - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in optional if x != cls._id_attr] + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in required + if x != cls._id_attr + ] + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in optional + if x != cls._id_attr + ] if mgr_cls.__name__ in cli.custom_actions: name = mgr_cls.__name__ for action_name in cli.custom_actions[name]: sub_parser_action = sub_parser.add_parser(action_name) - if hasattr(mgr_cls, '_from_parent_attrs'): - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in mgr_cls._from_parent_attrs] + if hasattr(mgr_cls, "_from_parent_attrs"): + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in mgr_cls._from_parent_attrs + ] sub_parser_action.add_argument("--sudo", required=False) required, optional, dummy = cli.custom_actions[name][action_name] - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in required if x != cls._id_attr] - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in optional if x != cls._id_attr] + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=True + ) + for x in required + if x != cls._id_attr + ] + [ + sub_parser_action.add_argument( + "--%s" % x.replace("_", "-"), required=False + ) + for x in optional + if x != cls._id_attr + ] def extend_parser(parser): - subparsers = parser.add_subparsers(title='object', dest='what', - help="Object to manipulate.") + subparsers = parser.add_subparsers( + title="object", dest="what", help="Object to manipulate." + ) subparsers.required = True # populate argparse for all Gitlab Object @@ -272,8 +312,8 @@ def extend_parser(parser): object_group = subparsers.add_parser(arg_name) object_subparsers = object_group.add_subparsers( - title='action', - dest='action', help="Action to execute.") + title="action", dest="action", help="Action to execute." + ) _populate_sub_parser_by_class(cls, object_subparsers) object_subparsers.required = True @@ -285,18 +325,19 @@ def get_dict(obj, fields): return obj if fields: - return {k: v for k, v in obj.attributes.items() - if k in fields} + return {k: v for k, v in obj.attributes.items() if k in fields} return obj.attributes class JSONPrinter(object): def display(self, d, **kwargs): import json # noqa + print(json.dumps(d)) def display_list(self, data, fields, **kwargs): import json # noqa + print(json.dumps([get_dict(obj, fields) for obj in data])) @@ -304,39 +345,47 @@ class YAMLPrinter(object): def display(self, d, **kwargs): try: import yaml # noqa + print(yaml.safe_dump(d, default_flow_style=False)) except ImportError: - exit("PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature") + exit( + "PyYaml is not installed.\n" + "Install it with `pip install PyYaml` " + "to use the yaml output feature" + ) def display_list(self, data, fields, **kwargs): try: import yaml # noqa - print(yaml.safe_dump( - [get_dict(obj, fields) for obj in data], - default_flow_style=False)) + + print( + yaml.safe_dump( + [get_dict(obj, fields) for obj in data], default_flow_style=False + ) + ) except ImportError: - exit("PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature") + exit( + "PyYaml is not installed.\n" + "Install it with `pip install PyYaml` " + "to use the yaml output feature" + ) class LegacyPrinter(object): def display(self, d, **kwargs): - verbose = kwargs.get('verbose', False) - padding = kwargs.get('padding', 0) - obj = kwargs.get('obj') + verbose = kwargs.get("verbose", False) + padding = kwargs.get("padding", 0) + obj = kwargs.get("obj") def display_dict(d, padding): for k in sorted(d.keys()): v = d[k] if isinstance(v, dict): - print('%s%s:' % (' ' * padding, k.replace('_', '-'))) + print("%s%s:" % (" " * padding, k.replace("_", "-"))) new_padding = padding + 2 self.display(v, verbose=True, padding=new_padding, obj=v) continue - print('%s%s: %s' % (' ' * padding, k.replace('_', '-'), v)) + print("%s%s: %s" % (" " * padding, k.replace("_", "-"), v)) if verbose: if isinstance(obj, dict): @@ -346,7 +395,7 @@ class LegacyPrinter(object): # not a dict, we assume it's a RESTObject if obj._id_attr: id = getattr(obj, obj._id_attr, None) - print('%s: %s' % (obj._id_attr, id)) + print("%s: %s" % (obj._id_attr, id)) attrs = obj.attributes if obj._id_attr: attrs.pop(obj._id_attr) @@ -355,33 +404,29 @@ class LegacyPrinter(object): else: if obj._id_attr: id = getattr(obj, obj._id_attr) - print('%s: %s' % (obj._id_attr.replace('_', '-'), id)) - if hasattr(obj, '_short_print_attr'): + print("%s: %s" % (obj._id_attr.replace("_", "-"), id)) + if hasattr(obj, "_short_print_attr"): value = getattr(obj, obj._short_print_attr) - value = value.replace('\r', '').replace('\n', ' ') + value = value.replace("\r", "").replace("\n", " ") # If the attribute is a note (ProjectCommitComment) then we do # some modifications to fit everything on one line - line = '%s: %s' % (obj._short_print_attr, value) + line = "%s: %s" % (obj._short_print_attr, value) # ellipsize long lines (comments) if len(line) > 79: - line = line[:76] + '...' + line = line[:76] + "..." print(line) def display_list(self, data, fields, **kwargs): - verbose = kwargs.get('verbose', False) + verbose = kwargs.get("verbose", False) for obj in data: if isinstance(obj, gitlab.base.RESTObject): self.display(get_dict(obj, fields), verbose=verbose, obj=obj) else: print(obj) - print('') + print("") -PRINTERS = { - 'json': JSONPrinter, - 'legacy': LegacyPrinter, - 'yaml': YAMLPrinter, -} +PRINTERS = {"json": JSONPrinter, "legacy": LegacyPrinter, "yaml": YAMLPrinter} def run(gl, what, action, args, verbose, output, fields): @@ -398,5 +443,5 @@ def run(gl, what, action, args, verbose, output, fields): printer.display(get_dict(data, fields), verbose=verbose, obj=data) elif isinstance(data, six.string_types): print(data) - elif hasattr(data, 'decode'): + elif hasattr(data, "decode"): print(data.decode()) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index ed559cf..16a3da8 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -26,9 +26,9 @@ from gitlab.mixins import * # noqa from gitlab import types from gitlab import utils -VISIBILITY_PRIVATE = 'private' -VISIBILITY_INTERNAL = 'internal' -VISIBILITY_PUBLIC = 'public' +VISIBILITY_PRIVATE = "private" +VISIBILITY_INTERNAL = "internal" +VISIBILITY_PUBLIC = "public" ACCESS_GUEST = 10 ACCESS_REPORTER = 20 @@ -44,7 +44,7 @@ class SidekiqManager(RESTManager): for the sidekiq metrics API. """ - @cli.register_custom_action('SidekiqManager') + @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) def queue_metrics(self, **kwargs): """Return the registred queues information. @@ -59,9 +59,9 @@ class SidekiqManager(RESTManager): Returns: dict: Information about the Sidekiq queues """ - return self.gitlab.http_get('/sidekiq/queue_metrics', **kwargs) + return self.gitlab.http_get("/sidekiq/queue_metrics", **kwargs) - @cli.register_custom_action('SidekiqManager') + @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) def process_metrics(self, **kwargs): """Return the registred sidekiq workers. @@ -76,9 +76,9 @@ class SidekiqManager(RESTManager): Returns: dict: Information about the register Sidekiq worker """ - return self.gitlab.http_get('/sidekiq/process_metrics', **kwargs) + return self.gitlab.http_get("/sidekiq/process_metrics", **kwargs) - @cli.register_custom_action('SidekiqManager') + @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) def job_stats(self, **kwargs): """Return statistics about the jobs performed. @@ -93,9 +93,9 @@ class SidekiqManager(RESTManager): Returns: dict: Statistics about the Sidekiq jobs performed """ - return self.gitlab.http_get('/sidekiq/job_stats', **kwargs) + return self.gitlab.http_get("/sidekiq/job_stats", **kwargs) - @cli.register_custom_action('SidekiqManager') + @cli.register_custom_action("SidekiqManager") @exc.on_http_error(exc.GitlabGetError) def compound_metrics(self, **kwargs): """Return all available metrics and statistics. @@ -110,49 +110,48 @@ class SidekiqManager(RESTManager): Returns: dict: All available Sidekiq metrics and statistics """ - return self.gitlab.http_get('/sidekiq/compound_metrics', **kwargs) + return self.gitlab.http_get("/sidekiq/compound_metrics", **kwargs) class Event(RESTObject): _id_attr = None - _short_print_attr = 'target_title' + _short_print_attr = "target_title" class EventManager(ListMixin, RESTManager): - _path = '/events' + _path = "/events" _obj_cls = Event - _list_filters = ('action', 'target_type', 'before', 'after', 'sort') + _list_filters = ("action", "target_type", "before", "after", "sort") class UserActivities(RESTObject): - _id_attr = 'username' + _id_attr = "username" class UserActivitiesManager(ListMixin, RESTManager): - _path = '/user/activities' + _path = "/user/activities" _obj_cls = UserActivities class UserCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = 'key' + _id_attr = "key" -class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, - RESTManager): - _path = '/users/%(user_id)s/custom_attributes' +class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): + _path = "/users/%(user_id)s/custom_attributes" _obj_cls = UserCustomAttribute - _from_parent_attrs = {'user_id': 'id'} + _from_parent_attrs = {"user_id": "id"} class UserEmail(ObjectDeleteMixin, RESTObject): - _short_print_attr = 'email' + _short_print_attr = "email" class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = '/users/%(user_id)s/emails' + _path = "/users/%(user_id)s/emails" _obj_cls = UserEmail - _from_parent_attrs = {'user_id': 'id'} - _create_attrs = (('email', ), tuple()) + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("email",), tuple()) class UserEvent(Event): @@ -160,9 +159,9 @@ class UserEvent(Event): class UserEventManager(EventManager): - _path = '/users/%(user_id)s/events' + _path = "/users/%(user_id)s/events" _obj_cls = UserEvent - _from_parent_attrs = {'user_id': 'id'} + _from_parent_attrs = {"user_id": "id"} class UserGPGKey(ObjectDeleteMixin, RESTObject): @@ -170,10 +169,10 @@ class UserGPGKey(ObjectDeleteMixin, RESTObject): class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): - _path = '/users/%(user_id)s/gpg_keys' + _path = "/users/%(user_id)s/gpg_keys" _obj_cls = UserGPGKey - _from_parent_attrs = {'user_id': 'id'} - _create_attrs = (('key',), tuple()) + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("key",), tuple()) class UserKey(ObjectDeleteMixin, RESTObject): @@ -181,10 +180,10 @@ class UserKey(ObjectDeleteMixin, RESTObject): class UserKeyManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): - _path = '/users/%(user_id)s/keys' + _path = "/users/%(user_id)s/keys" _obj_cls = UserKey - _from_parent_attrs = {'user_id': 'id'} - _create_attrs = (('title', 'key'), tuple()) + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("title", "key"), tuple()) class UserImpersonationToken(ObjectDeleteMixin, RESTObject): @@ -192,11 +191,11 @@ class UserImpersonationToken(ObjectDeleteMixin, RESTObject): class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): - _path = '/users/%(user_id)s/impersonation_tokens' + _path = "/users/%(user_id)s/impersonation_tokens" _obj_cls = UserImpersonationToken - _from_parent_attrs = {'user_id': 'id'} - _create_attrs = (('name', 'scopes'), ('expires_at',)) - _list_filters = ('state',) + _from_parent_attrs = {"user_id": "id"} + _create_attrs = (("name", "scopes"), ("expires_at",)) + _list_filters = ("state",) class UserProject(RESTObject): @@ -204,19 +203,41 @@ class UserProject(RESTObject): class UserProjectManager(ListMixin, CreateMixin, RESTManager): - _path = '/projects/user/%(user_id)s' + _path = "/projects/user/%(user_id)s" _obj_cls = UserProject - _from_parent_attrs = {'user_id': 'id'} + _from_parent_attrs = {"user_id": "id"} _create_attrs = ( - ('name', ), - ('default_branch', 'issues_enabled', 'wall_enabled', - 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', - 'public', 'visibility', 'description', 'builds_enabled', - 'public_builds', 'import_url', 'only_allow_merge_if_build_succeeds') + ("name",), + ( + "default_branch", + "issues_enabled", + "wall_enabled", + "merge_requests_enabled", + "wiki_enabled", + "snippets_enabled", + "public", + "visibility", + "description", + "builds_enabled", + "public_builds", + "import_url", + "only_allow_merge_if_build_succeeds", + ), + ) + _list_filters = ( + "archived", + "visibility", + "order_by", + "sort", + "search", + "simple", + "owned", + "membership", + "starred", + "statistics", + "with_issues_enabled", + "with_merge_requests_enabled", ) - _list_filters = ('archived', 'visibility', 'order_by', 'sort', 'search', - 'simple', 'owned', 'membership', 'starred', 'statistics', - 'with_issues_enabled', 'with_merge_requests_enabled') def list(self, **kwargs): """Retrieve a list of objects. @@ -237,23 +258,23 @@ class UserProjectManager(ListMixin, CreateMixin, RESTManager): GitlabListError: If the server cannot perform the request """ - path = '/users/%s/projects' % self._parent.id + path = "/users/%s/projects" % self._parent.id return ListMixin.list(self, path=path, **kwargs) class User(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'username' + _short_print_attr = "username" _managers = ( - ('customattributes', 'UserCustomAttributeManager'), - ('emails', 'UserEmailManager'), - ('events', 'UserEventManager'), - ('gpgkeys', 'UserGPGKeyManager'), - ('impersonationtokens', 'UserImpersonationTokenManager'), - ('keys', 'UserKeyManager'), - ('projects', 'UserProjectManager'), + ("customattributes", "UserCustomAttributeManager"), + ("emails", "UserEmailManager"), + ("events", "UserEventManager"), + ("gpgkeys", "UserGPGKeyManager"), + ("impersonationtokens", "UserImpersonationTokenManager"), + ("keys", "UserKeyManager"), + ("projects", "UserProjectManager"), ) - @cli.register_custom_action('User') + @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabBlockError) def block(self, **kwargs): """Block the user. @@ -268,13 +289,13 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: bool: Whether the user status has been changed """ - path = '/users/%s/block' % self.id + path = "/users/%s/block" % self.id server_data = self.manager.gitlab.http_post(path, **kwargs) if server_data is True: - self._attrs['state'] = 'blocked' + self._attrs["state"] = "blocked" return server_data - @cli.register_custom_action('User') + @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabUnblockError) def unblock(self, **kwargs): """Unblock the user. @@ -289,84 +310,118 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: bool: Whether the user status has been changed """ - path = '/users/%s/unblock' % self.id + path = "/users/%s/unblock" % self.id server_data = self.manager.gitlab.http_post(path, **kwargs) if server_data is True: - self._attrs['state'] = 'active' + self._attrs["state"] = "active" return server_data class UserManager(CRUDMixin, RESTManager): - _path = '/users' + _path = "/users" _obj_cls = User - _list_filters = ('active', 'blocked', 'username', 'extern_uid', 'provider', - 'external', 'search', 'custom_attributes') + _list_filters = ( + "active", + "blocked", + "username", + "extern_uid", + "provider", + "external", + "search", + "custom_attributes", + ) _create_attrs = ( tuple(), - ('email', 'username', 'name', 'password', 'reset_password', 'skype', - 'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider', - 'bio', 'admin', 'can_create_group', 'website_url', - 'skip_confirmation', 'external', 'organization', 'location', 'avatar') + ( + "email", + "username", + "name", + "password", + "reset_password", + "skype", + "linkedin", + "twitter", + "projects_limit", + "extern_uid", + "provider", + "bio", + "admin", + "can_create_group", + "website_url", + "skip_confirmation", + "external", + "organization", + "location", + "avatar", + ), ) _update_attrs = ( - ('email', 'username', 'name'), - ('password', 'skype', 'linkedin', 'twitter', 'projects_limit', - 'extern_uid', 'provider', 'bio', 'admin', 'can_create_group', - 'website_url', 'skip_confirmation', 'external', 'organization', - 'location', 'avatar') + ("email", "username", "name"), + ( + "password", + "skype", + "linkedin", + "twitter", + "projects_limit", + "extern_uid", + "provider", + "bio", + "admin", + "can_create_group", + "website_url", + "skip_confirmation", + "external", + "organization", + "location", + "avatar", + ), ) - _types = { - 'confirm': types.LowercaseStringAttribute, - 'avatar': types.ImageAttribute, - } + _types = {"confirm": types.LowercaseStringAttribute, "avatar": types.ImageAttribute} class CurrentUserEmail(ObjectDeleteMixin, RESTObject): - _short_print_attr = 'email' + _short_print_attr = "email" -class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/user/emails' +class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/emails" _obj_cls = CurrentUserEmail - _create_attrs = (('email', ), tuple()) + _create_attrs = (("email",), tuple()) class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): pass -class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/user/gpg_keys' +class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/gpg_keys" _obj_cls = CurrentUserGPGKey - _create_attrs = (('key',), tuple()) + _create_attrs = (("key",), tuple()) class CurrentUserKey(ObjectDeleteMixin, RESTObject): - _short_print_attr = 'title' + _short_print_attr = "title" -class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/user/keys' +class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/user/keys" _obj_cls = CurrentUserKey - _create_attrs = (('title', 'key'), tuple()) + _create_attrs = (("title", "key"), tuple()) class CurrentUser(RESTObject): _id_attr = None - _short_print_attr = 'username' + _short_print_attr = "username" _managers = ( - ('emails', 'CurrentUserEmailManager'), - ('gpgkeys', 'CurrentUserGPGKeyManager'), - ('keys', 'CurrentUserKeyManager'), + ("emails", "CurrentUserEmailManager"), + ("gpgkeys", "CurrentUserGPGKeyManager"), + ("keys", "CurrentUserKeyManager"), ) class CurrentUserManager(GetWithoutIdMixin, RESTManager): - _path = '/user' + _path = "/user" _obj_cls = CurrentUser @@ -375,52 +430,103 @@ class ApplicationSettings(SaveMixin, RESTObject): class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = '/application/settings' + _path = "/application/settings" _obj_cls = ApplicationSettings _update_attrs = ( tuple(), - ('admin_notification_email', 'after_sign_out_path', - 'after_sign_up_text', 'akismet_api_key', 'akismet_enabled', - 'circuitbreaker_access_retries', 'circuitbreaker_check_interval', - 'circuitbreaker_failure_count_threshold', - 'circuitbreaker_failure_reset_time', 'circuitbreaker_storage_timeout', - 'clientside_sentry_dsn', 'clientside_sentry_enabled', - 'container_registry_token_expire_delay', - 'default_artifacts_expire_in', 'default_branch_protection', - 'default_group_visibility', 'default_project_visibility', - 'default_projects_limit', 'default_snippet_visibility', - 'disabled_oauth_sign_in_sources', 'domain_blacklist_enabled', - 'domain_blacklist', 'domain_whitelist', 'dsa_key_restriction', - 'ecdsa_key_restriction', 'ed25519_key_restriction', - 'email_author_in_body', 'enabled_git_access_protocol', - 'gravatar_enabled', 'help_page_hide_commercial_content', - 'help_page_support_url', 'home_page_url', - 'housekeeping_bitmaps_enabled', 'housekeeping_enabled', - 'housekeeping_full_repack_period', 'housekeeping_gc_period', - 'housekeeping_incremental_repack_period', 'html_emails_enabled', - 'import_sources', 'koding_enabled', 'koding_url', - 'max_artifacts_size', 'max_attachment_size', 'max_pages_size', - 'metrics_enabled', 'metrics_host', 'metrics_method_call_threshold', - 'metrics_packet_size', 'metrics_pool_size', 'metrics_port', - 'metrics_sample_interval', 'metrics_timeout', - 'password_authentication_enabled_for_web', - 'password_authentication_enabled_for_git', - 'performance_bar_allowed_group_id', 'performance_bar_enabled', - 'plantuml_enabled', 'plantuml_url', 'polling_interval_multiplier', - 'project_export_enabled', 'prometheus_metrics_enabled', - 'recaptcha_enabled', 'recaptcha_private_key', 'recaptcha_site_key', - 'repository_checks_enabled', 'repository_storages', - 'require_two_factor_authentication', 'restricted_visibility_levels', - 'rsa_key_restriction', 'send_user_confirmation_email', 'sentry_dsn', - 'sentry_enabled', 'session_expire_delay', 'shared_runners_enabled', - 'shared_runners_text', 'sidekiq_throttling_enabled', - 'sidekiq_throttling_factor', 'sidekiq_throttling_queues', - 'sign_in_text', 'signup_enabled', 'terminal_max_session_time', - 'two_factor_grace_period', 'unique_ips_limit_enabled', - 'unique_ips_limit_per_user', 'unique_ips_limit_time_window', - 'usage_ping_enabled', 'user_default_external', - 'user_oauth_applications', 'version_check_enabled', 'enforce_terms', - 'terms') + ( + "admin_notification_email", + "after_sign_out_path", + "after_sign_up_text", + "akismet_api_key", + "akismet_enabled", + "circuitbreaker_access_retries", + "circuitbreaker_check_interval", + "circuitbreaker_failure_count_threshold", + "circuitbreaker_failure_reset_time", + "circuitbreaker_storage_timeout", + "clientside_sentry_dsn", + "clientside_sentry_enabled", + "container_registry_token_expire_delay", + "default_artifacts_expire_in", + "default_branch_protection", + "default_group_visibility", + "default_project_visibility", + "default_projects_limit", + "default_snippet_visibility", + "disabled_oauth_sign_in_sources", + "domain_blacklist_enabled", + "domain_blacklist", + "domain_whitelist", + "dsa_key_restriction", + "ecdsa_key_restriction", + "ed25519_key_restriction", + "email_author_in_body", + "enabled_git_access_protocol", + "gravatar_enabled", + "help_page_hide_commercial_content", + "help_page_support_url", + "home_page_url", + "housekeeping_bitmaps_enabled", + "housekeeping_enabled", + "housekeeping_full_repack_period", + "housekeeping_gc_period", + "housekeeping_incremental_repack_period", + "html_emails_enabled", + "import_sources", + "koding_enabled", + "koding_url", + "max_artifacts_size", + "max_attachment_size", + "max_pages_size", + "metrics_enabled", + "metrics_host", + "metrics_method_call_threshold", + "metrics_packet_size", + "metrics_pool_size", + "metrics_port", + "metrics_sample_interval", + "metrics_timeout", + "password_authentication_enabled_for_web", + "password_authentication_enabled_for_git", + "performance_bar_allowed_group_id", + "performance_bar_enabled", + "plantuml_enabled", + "plantuml_url", + "polling_interval_multiplier", + "project_export_enabled", + "prometheus_metrics_enabled", + "recaptcha_enabled", + "recaptcha_private_key", + "recaptcha_site_key", + "repository_checks_enabled", + "repository_storages", + "require_two_factor_authentication", + "restricted_visibility_levels", + "rsa_key_restriction", + "send_user_confirmation_email", + "sentry_dsn", + "sentry_enabled", + "session_expire_delay", + "shared_runners_enabled", + "shared_runners_text", + "sidekiq_throttling_enabled", + "sidekiq_throttling_factor", + "sidekiq_throttling_queues", + "sign_in_text", + "signup_enabled", + "terminal_max_session_time", + "two_factor_grace_period", + "unique_ips_limit_enabled", + "unique_ips_limit_per_user", + "unique_ips_limit_time_window", + "usage_ping_enabled", + "user_default_external", + "user_oauth_applications", + "version_check_enabled", + "enforce_terms", + "terms", + ), ) @exc.on_http_error(exc.GitlabUpdateError) @@ -441,8 +547,8 @@ class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): """ data = new_data.copy() - if 'domain_whitelist' in data and data['domain_whitelist'] is None: - data.pop('domain_whitelist') + if "domain_whitelist" in data and data["domain_whitelist"] is None: + data.pop("domain_whitelist") super(ApplicationSettingsManager, self).update(id, data, **kwargs) @@ -451,12 +557,11 @@ class BroadcastMessage(SaveMixin, ObjectDeleteMixin, RESTObject): class BroadcastMessageManager(CRUDMixin, RESTManager): - _path = '/broadcast_messages' + _path = "/broadcast_messages" _obj_cls = BroadcastMessage - _create_attrs = (('message', ), ('starts_at', 'ends_at', 'color', 'font')) - _update_attrs = (tuple(), ('message', 'starts_at', 'ends_at', 'color', - 'font')) + _create_attrs = (("message",), ("starts_at", "ends_at", "color", "font")) + _update_attrs = (tuple(), ("message", "starts_at", "ends_at", "color", "font")) class DeployKey(RESTObject): @@ -464,7 +569,7 @@ class DeployKey(RESTObject): class DeployKeyManager(ListMixin, RESTManager): - _path = '/deploy_keys' + _path = "/deploy_keys" _obj_cls = DeployKey @@ -473,33 +578,43 @@ class NotificationSettings(SaveMixin, RESTObject): class NotificationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = '/notification_settings' + _path = "/notification_settings" _obj_cls = NotificationSettings _update_attrs = ( tuple(), - ('level', 'notification_email', 'new_note', 'new_issue', - 'reopen_issue', 'close_issue', 'reassign_issue', 'new_merge_request', - 'reopen_merge_request', 'close_merge_request', - 'reassign_merge_request', 'merge_merge_request') + ( + "level", + "notification_email", + "new_note", + "new_issue", + "reopen_issue", + "close_issue", + "reassign_issue", + "new_merge_request", + "reopen_merge_request", + "close_merge_request", + "reassign_merge_request", + "merge_merge_request", + ), ) class Dockerfile(RESTObject): - _id_attr = 'name' + _id_attr = "name" class DockerfileManager(RetrieveMixin, RESTManager): - _path = '/templates/dockerfiles' + _path = "/templates/dockerfiles" _obj_cls = Dockerfile class Feature(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' + _id_attr = "name" class FeatureManager(ListMixin, DeleteMixin, RESTManager): - _path = '/features/' + _path = "/features/" _obj_cls = Feature @exc.on_http_error(exc.GitlabSetError) @@ -520,27 +635,27 @@ class FeatureManager(ListMixin, DeleteMixin, RESTManager): Returns: obj: The created/updated attribute """ - path = '%s/%s' % (self.path, name.replace('/', '%2F')) - data = {'value': value, 'feature_group': feature_group, 'user': user} + path = "%s/%s" % (self.path, name.replace("/", "%2F")) + data = {"value": value, "feature_group": feature_group, "user": user} server_data = self.gitlab.http_post(path, post_data=data, **kwargs) return self._obj_cls(self, server_data) class Gitignore(RESTObject): - _id_attr = 'name' + _id_attr = "name" class GitignoreManager(RetrieveMixin, RESTManager): - _path = '/templates/gitignores' + _path = "/templates/gitignores" _obj_cls = Gitignore class Gitlabciyml(RESTObject): - _id_attr = 'name' + _id_attr = "name" class GitlabciymlManager(RetrieveMixin, RESTManager): - _path = '/templates/gitlab_ci_ymls' + _path = "/templates/gitlab_ci_ymls" _obj_cls = Gitlabciyml @@ -548,11 +663,10 @@ class GroupAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): pass -class GroupAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/groups/%(group_id)s/access_requests' +class GroupAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/access_requests" _obj_cls = GroupAccessRequest - _from_parent_attrs = {'group_id': 'id'} + _from_parent_attrs = {"group_id": "id"} class GroupBadge(SaveMixin, ObjectDeleteMixin, RESTObject): @@ -560,11 +674,11 @@ class GroupBadge(SaveMixin, ObjectDeleteMixin, RESTObject): class GroupBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/badges' + _path = "/groups/%(group_id)s/badges" _obj_cls = GroupBadge - _from_parent_attrs = {'group_id': 'id'} - _create_attrs = (('link_url', 'image_url'), tuple()) - _update_attrs = (tuple(), ('link_url', 'image_url')) + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("link_url", "image_url"), tuple()) + _update_attrs = (tuple(), ("link_url", "image_url")) class GroupBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): @@ -572,38 +686,36 @@ class GroupBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): class GroupBoardListManager(CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/boards/%(board_id)s/lists' + _path = "/groups/%(group_id)s/boards/%(board_id)s/lists" _obj_cls = GroupBoardList - _from_parent_attrs = {'group_id': 'group_id', - 'board_id': 'id'} - _create_attrs = (('label_id', ), tuple()) - _update_attrs = (('position', ), tuple()) + _from_parent_attrs = {"group_id": "group_id", "board_id": "id"} + _create_attrs = (("label_id",), tuple()) + _update_attrs = (("position",), tuple()) class GroupBoard(ObjectDeleteMixin, RESTObject): - _managers = (('lists', 'GroupBoardListManager'), ) + _managers = (("lists", "GroupBoardListManager"),) class GroupBoardManager(NoUpdateMixin, RESTManager): - _path = '/groups/%(group_id)s/boards' + _path = "/groups/%(group_id)s/boards" _obj_cls = GroupBoard - _from_parent_attrs = {'group_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("name",), tuple()) class GroupCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = 'key' + _id_attr = "key" -class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, - RESTManager): - _path = '/groups/%(group_id)s/custom_attributes' +class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/custom_attributes" _obj_cls = GroupCustomAttribute - _from_parent_attrs = {'group_id': 'id'} + _from_parent_attrs = {"group_id": "id"} class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject): - _id_attr = 'epic_issue_id' + _id_attr = "epic_issue_id" def save(self, **kwargs): """Save the changes made to the object to the server. @@ -627,13 +739,14 @@ class GroupEpicIssue(ObjectDeleteMixin, SaveMixin, RESTObject): self.manager.update(obj_id, updated_data, **kwargs) -class GroupEpicIssueManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, - RESTManager): - _path = '/groups/%(group_id)s/epics/%(epic_iid)s/issues' +class GroupEpicIssueManager( + ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = "/groups/%(group_id)s/epics/%(epic_iid)s/issues" _obj_cls = GroupEpicIssue - _from_parent_attrs = {'group_id': 'group_id', 'epic_iid': 'iid'} - _create_attrs = (('issue_id',), tuple()) - _update_attrs = (tuple(), ('move_before_id', 'move_after_id')) + _from_parent_attrs = {"group_id": "group_id", "epic_iid": "iid"} + _create_attrs = (("issue_id",), tuple()) + _update_attrs = (tuple(), ("move_before_id", "move_after_id")) @exc.on_http_error(exc.GitlabCreateError) def create(self, data, **kwargs): @@ -653,12 +766,12 @@ class GroupEpicIssueManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, the data sent by the server """ CreateMixin._check_missing_create_attrs(self, data) - path = '%s/%s' % (self.path, data.pop('issue_id')) + path = "%s/%s" % (self.path, data.pop("issue_id")) server_data = self.gitlab.http_post(path, **kwargs) # The epic_issue_id attribute doesn't exist when creating the resource, # but is used everywhere elese. Let's create it to be consistent client # side - server_data['epic_issue_id'] = server_data['id'] + server_data["epic_issue_id"] = server_data["id"] return self._obj_cls(self, server_data) @@ -667,29 +780,30 @@ class GroupEpicResourceLabelEvent(RESTObject): class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = ('/groups/%(group_id)s/epics/%(epic_id)s/resource_label_events') + _path = "/groups/%(group_id)s/epics/%(epic_id)s/resource_label_events" _obj_cls = GroupEpicResourceLabelEvent - _from_parent_attrs = {'group_id': 'group_id', 'epic_id': 'id'} + _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): - _id_attr = 'iid' + _id_attr = "iid" _managers = ( - ('issues', 'GroupEpicIssueManager'), - ('resourcelabelevents', 'GroupEpicResourceLabelEventManager'), + ("issues", "GroupEpicIssueManager"), + ("resourcelabelevents", "GroupEpicResourceLabelEventManager"), ) class GroupEpicManager(CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/epics' + _path = "/groups/%(group_id)s/epics" _obj_cls = GroupEpic - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('author_id', 'labels', 'order_by', 'sort', 'search') - _create_attrs = (('title',), - ('labels', 'description', 'start_date', 'end_date')) - _update_attrs = (tuple(), ('title', 'labels', 'description', 'start_date', - 'end_date')) - _types = {'labels': types.ListAttribute} + _from_parent_attrs = {"group_id": "id"} + _list_filters = ("author_id", "labels", "order_by", "sort", "search") + _create_attrs = (("title",), ("labels", "description", "start_date", "end_date")) + _update_attrs = ( + tuple(), + ("title", "labels", "description", "start_date", "end_date"), + ) + _types = {"labels": types.ListAttribute} class GroupIssue(RESTObject): @@ -697,28 +811,40 @@ class GroupIssue(RESTObject): class GroupIssueManager(ListMixin, RESTManager): - _path = '/groups/%(group_id)s/issues' + _path = "/groups/%(group_id)s/issues" _obj_cls = GroupIssue - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('state', 'labels', 'milestone', 'order_by', 'sort', - 'iids', 'author_id', 'assignee_id', 'my_reaction_emoji', - 'search', 'created_after', 'created_before', - 'updated_after', 'updated_before') - _types = {'labels': types.ListAttribute} + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "labels", + "milestone", + "order_by", + "sort", + "iids", + "author_id", + "assignee_id", + "my_reaction_emoji", + "search", + "created_after", + "created_before", + "updated_after", + "updated_before", + ) + _types = {"labels": types.ListAttribute} class GroupMember(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'username' + _short_print_attr = "username" class GroupMemberManager(CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/members' + _path = "/groups/%(group_id)s/members" _obj_cls = GroupMember - _from_parent_attrs = {'group_id': 'id'} - _create_attrs = (('access_level', 'user_id'), ('expires_at', )) - _update_attrs = (('access_level', ), ('expires_at', )) + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("access_level", "user_id"), ("expires_at",)) + _update_attrs = (("access_level",), ("expires_at",)) - @cli.register_custom_action('GroupMemberManager') + @cli.register_custom_action("GroupMemberManager") @exc.on_http_error(exc.GitlabListError) def all(self, **kwargs): """List all the members, included inherited ones. @@ -739,7 +865,7 @@ class GroupMemberManager(CRUDMixin, RESTManager): RESTObjectList: The list of members """ - path = '%s/all' % self.path + path = "%s/all" % self.path obj = self.gitlab.http_list(path, **kwargs) return [self._obj_cls(self, item) for item in obj] @@ -749,21 +875,35 @@ class GroupMergeRequest(RESTObject): class GroupMergeRequestManager(ListMixin, RESTManager): - _path = '/groups/%(group_id)s/merge_requests' + _path = "/groups/%(group_id)s/merge_requests" _obj_cls = GroupMergeRequest - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', - 'labels', 'created_after', 'created_before', - 'updated_after', 'updated_before', 'scope', 'author_id', - 'assignee_id', 'my_reaction_emoji', 'source_branch', - 'target_branch', 'search') - _types = {'labels': types.ListAttribute} + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "order_by", + "sort", + "milestone", + "view", + "labels", + "created_after", + "created_before", + "updated_after", + "updated_before", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "source_branch", + "target_branch", + "search", + ) + _types = {"labels": types.ListAttribute} class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'title' + _short_print_attr = "title" - @cli.register_custom_action('GroupMilestone') + @cli.register_custom_action("GroupMilestone") @exc.on_http_error(exc.GitlabListError) def issues(self, **kwargs): """List issues related to this milestone. @@ -784,15 +924,13 @@ class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): RESTObjectList: The list of issues """ - path = '%s/%s/issues' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = GroupIssueManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/issues" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) # FIXME(gpocentek): the computed manager path is not correct return RESTObjectList(manager, GroupIssue, data_list) - @cli.register_custom_action('GroupMilestone') + @cli.register_custom_action("GroupMilestone") @exc.on_http_error(exc.GitlabListError) def merge_requests(self, **kwargs): """List the merge requests related to this milestone. @@ -812,23 +950,23 @@ class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: RESTObjectList: The list of merge requests """ - path = '%s/%s/merge_requests' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = GroupIssueManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = GroupIssueManager(self.manager.gitlab, parent=self.manager._parent) # FIXME(gpocentek): the computed manager path is not correct return RESTObjectList(manager, GroupMergeRequest, data_list) class GroupMilestoneManager(CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/milestones' + _path = "/groups/%(group_id)s/milestones" _obj_cls = GroupMilestone - _from_parent_attrs = {'group_id': 'id'} - _create_attrs = (('title', ), ('description', 'due_date', 'start_date')) - _update_attrs = (tuple(), ('title', 'description', 'due_date', - 'start_date', 'state_event')) - _list_filters = ('iids', 'state', 'search') + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("title",), ("description", "due_date", "start_date")) + _update_attrs = ( + tuple(), + ("title", "description", "due_date", "start_date", "state_event"), + ) + _list_filters = ("iids", "state", "search") class GroupNotificationSettings(NotificationSettings): @@ -836,9 +974,9 @@ class GroupNotificationSettings(NotificationSettings): class GroupNotificationSettingsManager(NotificationSettingsManager): - _path = '/groups/%(group_id)s/notification_settings' + _path = "/groups/%(group_id)s/notification_settings" _obj_cls = GroupNotificationSettings - _from_parent_attrs = {'group_id': 'id'} + _from_parent_attrs = {"group_id": "id"} class GroupProject(RESTObject): @@ -846,12 +984,21 @@ class GroupProject(RESTObject): class GroupProjectManager(ListMixin, RESTManager): - _path = '/groups/%(group_id)s/projects' + _path = "/groups/%(group_id)s/projects" _obj_cls = GroupProject - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('archived', 'visibility', 'order_by', 'sort', 'search', - 'ci_enabled_first', 'simple', 'owned', 'starred', - 'with_custom_attributes') + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "archived", + "visibility", + "order_by", + "sort", + "search", + "ci_enabled_first", + "simple", + "owned", + "starred", + "with_custom_attributes", + ) class GroupSubgroup(RESTObject): @@ -859,44 +1006,52 @@ class GroupSubgroup(RESTObject): class GroupSubgroupManager(ListMixin, RESTManager): - _path = '/groups/%(group_id)s/subgroups' + _path = "/groups/%(group_id)s/subgroups" _obj_cls = GroupSubgroup - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('skip_groups', 'all_available', 'search', 'order_by', - 'sort', 'statistics', 'owned', 'with_custom_attributes') + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "skip_groups", + "all_available", + "search", + "order_by", + "sort", + "statistics", + "owned", + "with_custom_attributes", + ) class GroupVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = 'key' + _id_attr = "key" class GroupVariableManager(CRUDMixin, RESTManager): - _path = '/groups/%(group_id)s/variables' + _path = "/groups/%(group_id)s/variables" _obj_cls = GroupVariable - _from_parent_attrs = {'group_id': 'id'} - _create_attrs = (('key', 'value'), ('protected',)) - _update_attrs = (('key', 'value'), ('protected',)) + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("key", "value"), ("protected",)) + _update_attrs = (("key", "value"), ("protected",)) class Group(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'name' + _short_print_attr = "name" _managers = ( - ('accessrequests', 'GroupAccessRequestManager'), - ('badges', 'GroupBadgeManager'), - ('boards', 'GroupBoardManager'), - ('customattributes', 'GroupCustomAttributeManager'), - ('epics', 'GroupEpicManager'), - ('issues', 'GroupIssueManager'), - ('members', 'GroupMemberManager'), - ('mergerequests', 'GroupMergeRequestManager'), - ('milestones', 'GroupMilestoneManager'), - ('notificationsettings', 'GroupNotificationSettingsManager'), - ('projects', 'GroupProjectManager'), - ('subgroups', 'GroupSubgroupManager'), - ('variables', 'GroupVariableManager'), + ("accessrequests", "GroupAccessRequestManager"), + ("badges", "GroupBadgeManager"), + ("boards", "GroupBoardManager"), + ("customattributes", "GroupCustomAttributeManager"), + ("epics", "GroupEpicManager"), + ("issues", "GroupIssueManager"), + ("members", "GroupMemberManager"), + ("mergerequests", "GroupMergeRequestManager"), + ("milestones", "GroupMilestoneManager"), + ("notificationsettings", "GroupNotificationSettingsManager"), + ("projects", "GroupProjectManager"), + ("subgroups", "GroupSubgroupManager"), + ("variables", "GroupVariableManager"), ) - @cli.register_custom_action('Group', ('to_project_id', )) + @cli.register_custom_action("Group", ("to_project_id",)) @exc.on_http_error(exc.GitlabTransferProjectError) def transfer_project(self, to_project_id, **kwargs): """Transfer a project to this group. @@ -909,10 +1064,10 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabTransferProjectError: If the project could not be transfered """ - path = '/groups/%s/projects/%s' % (self.id, to_project_id) + path = "/groups/%s/projects/%s" % (self.id, to_project_id) self.manager.gitlab.http_post(path, **kwargs) - @cli.register_custom_action('Group', ('scope', 'search')) + @cli.register_custom_action("Group", ("scope", "search")) @exc.on_http_error(exc.GitlabSearchError) def search(self, scope, search, **kwargs): """Search the group resources matching the provided string.' @@ -929,11 +1084,11 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: GitlabList: A list of dicts describing the resources found. """ - data = {'scope': scope, 'search': search} - path = '/groups/%s/search' % self.get_id() + data = {"scope": scope, "search": search} + path = "/groups/%s/search" % self.get_id() return self.manager.gitlab.http_list(path, query_data=data, **kwargs) - @cli.register_custom_action('Group', ('cn', 'group_access', 'provider')) + @cli.register_custom_action("Group", ("cn", "group_access", "provider")) @exc.on_http_error(exc.GitlabCreateError) def add_ldap_group_link(self, cn, group_access, provider, **kwargs): """Add an LDAP group link. @@ -949,11 +1104,11 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server cannot perform the request """ - path = '/groups/%s/ldap_group_links' % self.get_id() - data = {'cn': cn, 'group_access': group_access, 'provider': provider} + path = "/groups/%s/ldap_group_links" % self.get_id() + data = {"cn": cn, "group_access": group_access, "provider": provider} self.manager.gitlab.http_post(path, post_data=data, **kwargs) - @cli.register_custom_action('Group', ('cn',), ('provider',)) + @cli.register_custom_action("Group", ("cn",), ("provider",)) @exc.on_http_error(exc.GitlabDeleteError) def delete_ldap_group_link(self, cn, provider=None, **kwargs): """Delete an LDAP group link. @@ -967,13 +1122,13 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - path = '/groups/%s/ldap_group_links' % self.get_id() + path = "/groups/%s/ldap_group_links" % self.get_id() if provider is not None: - path += '/%s' % provider - path += '/%s' % cn + path += "/%s" % provider + path += "/%s" % cn self.manager.gitlab.http_delete(path) - @cli.register_custom_action('Group') + @cli.register_custom_action("Group") @exc.on_http_error(exc.GitlabCreateError) def ldap_sync(self, **kwargs): """Sync LDAP groups. @@ -985,51 +1140,83 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server cannot perform the request """ - path = '/groups/%s/ldap_sync' % self.get_id() + path = "/groups/%s/ldap_sync" % self.get_id() self.manager.gitlab.http_post(path, **kwargs) class GroupManager(CRUDMixin, RESTManager): - _path = '/groups' + _path = "/groups" _obj_cls = Group - _list_filters = ('skip_groups', 'all_available', 'search', 'order_by', - 'sort', 'statistics', 'owned', 'with_custom_attributes') + _list_filters = ( + "skip_groups", + "all_available", + "search", + "order_by", + "sort", + "statistics", + "owned", + "with_custom_attributes", + ) _create_attrs = ( - ('name', 'path'), - ('description', 'visibility', 'parent_id', 'lfs_enabled', - 'request_access_enabled') + ("name", "path"), + ( + "description", + "visibility", + "parent_id", + "lfs_enabled", + "request_access_enabled", + ), ) _update_attrs = ( tuple(), - ('name', 'path', 'description', 'visibility', 'lfs_enabled', - 'request_access_enabled') + ( + "name", + "path", + "description", + "visibility", + "lfs_enabled", + "request_access_enabled", + ), ) class Hook(ObjectDeleteMixin, RESTObject): - _url = '/hooks' - _short_print_attr = 'url' + _url = "/hooks" + _short_print_attr = "url" class HookManager(NoUpdateMixin, RESTManager): - _path = '/hooks' + _path = "/hooks" _obj_cls = Hook - _create_attrs = (('url', ), tuple()) + _create_attrs = (("url",), tuple()) class Issue(RESTObject): - _url = '/issues' - _short_print_attr = 'title' + _url = "/issues" + _short_print_attr = "title" class IssueManager(ListMixin, RESTManager): - _path = '/issues' + _path = "/issues" _obj_cls = Issue - _list_filters = ('state', 'labels', 'milestone', 'scope', 'author_id', - 'assignee_id', 'my_reaction_emoji', 'iids', 'order_by', - 'sort', 'search', 'created_after', 'created_before', - 'updated_after', 'updated_before') - _types = {'labels': types.ListAttribute} + _list_filters = ( + "state", + "labels", + "milestone", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "iids", + "order_by", + "sort", + "search", + "created_after", + "created_before", + "updated_after", + "updated_before", + ) + _types = {"labels": types.ListAttribute} class LDAPGroup(RESTObject): @@ -1037,9 +1224,9 @@ class LDAPGroup(RESTObject): class LDAPGroupManager(RESTManager): - _path = '/ldap/groups' + _path = "/ldap/groups" _obj_cls = LDAPGroup - _list_filters = ('search', 'provider') + _list_filters = ("search", "provider") @exc.on_http_error(exc.GitlabListError) def list(self, **kwargs): @@ -1062,10 +1249,10 @@ class LDAPGroupManager(RESTManager): """ data = kwargs.copy() if self.gitlab.per_page: - data.setdefault('per_page', self.gitlab.per_page) + data.setdefault("per_page", self.gitlab.per_page) - if 'provider' in data: - path = '/ldap/%s/groups' % data['provider'] + if "provider" in data: + path = "/ldap/%s/groups" % data["provider"] else: path = self._path @@ -1077,14 +1264,14 @@ class LDAPGroupManager(RESTManager): class License(RESTObject): - _id_attr = 'key' + _id_attr = "key" class LicenseManager(RetrieveMixin, RESTManager): - _path = '/templates/licenses' + _path = "/templates/licenses" _obj_cls = License - _list_filters = ('popular', ) - _optional_get_attrs = ('project', 'fullname') + _list_filters = ("popular",) + _optional_get_attrs = ("project", "fullname") class MergeRequest(RESTObject): @@ -1092,21 +1279,35 @@ class MergeRequest(RESTObject): class MergeRequestManager(ListMixin, RESTManager): - _path = '/merge_requests' + _path = "/merge_requests" _obj_cls = MergeRequest - _from_parent_attrs = {'group_id': 'id'} - _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', - 'labels', 'created_after', 'created_before', - 'updated_after', 'updated_before', 'scope', 'author_id', - 'assignee_id', 'my_reaction_emoji', 'source_branch', - 'target_branch', 'search') - _types = {'labels': types.ListAttribute} + _from_parent_attrs = {"group_id": "id"} + _list_filters = ( + "state", + "order_by", + "sort", + "milestone", + "view", + "labels", + "created_after", + "created_before", + "updated_after", + "updated_before", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "source_branch", + "target_branch", + "search", + ) + _types = {"labels": types.ListAttribute} class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'title' + _short_print_attr = "title" - @cli.register_custom_action('Snippet') + @cli.register_custom_action("Snippet") @exc.on_http_error(exc.GitlabGetError) def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Return the content of a snippet. @@ -1127,21 +1328,20 @@ class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): Returns: str: The snippet content """ - path = '/snippets/%s/raw' % self.get_id() - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "/snippets/%s/raw" % self.get_id() + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) class SnippetManager(CRUDMixin, RESTManager): - _path = '/snippets' + _path = "/snippets" _obj_cls = Snippet - _create_attrs = (('title', 'file_name', 'content'), - ('lifetime', 'visibility')) - _update_attrs = (tuple(), - ('title', 'file_name', 'content', 'visibility')) + _create_attrs = (("title", "file_name", "content"), ("lifetime", "visibility")) + _update_attrs = (tuple(), ("title", "file_name", "content", "visibility")) - @cli.register_custom_action('SnippetManager') + @cli.register_custom_action("SnippetManager") def public(self, **kwargs): """List all the public snippets. @@ -1155,7 +1355,7 @@ class SnippetManager(CRUDMixin, RESTManager): Returns: RESTObjectList: A generator for the snippets list """ - return self.list(path='/snippets/public', **kwargs) + return self.list(path="/snippets/public", **kwargs) class Namespace(RESTObject): @@ -1163,44 +1363,44 @@ class Namespace(RESTObject): class NamespaceManager(RetrieveMixin, RESTManager): - _path = '/namespaces' + _path = "/namespaces" _obj_cls = Namespace - _list_filters = ('search', ) + _list_filters = ("search",) class PagesDomain(RESTObject): - _id_attr = 'domain' + _id_attr = "domain" class PagesDomainManager(ListMixin, RESTManager): - _path = '/pages/domains' + _path = "/pages/domains" _obj_cls = PagesDomain class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): - _managers = ( - ('tags', 'ProjectRegistryTagManager'), - ) + _managers = (("tags", "ProjectRegistryTagManager"),) class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager): - _path = '/projects/%(project_id)s/registry/repositories' + _path = "/projects/%(project_id)s/registry/repositories" _obj_cls = ProjectRegistryRepository - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectRegistryTag(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' + _id_attr = "name" class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager): _obj_cls = ProjectRegistryTag - _from_parent_attrs = {'project_id': 'project_id', 'repository_id': 'id'} - _path = '/projects/%(project_id)s/registry/repositories/%(repository_id)s/tags' + _from_parent_attrs = {"project_id": "project_id", "repository_id": "id"} + _path = "/projects/%(project_id)s/registry/repositories/%(repository_id)s/tags" - @cli.register_custom_action('ProjectRegistryTagManager', optional=('name_regex', 'keep_n', 'older_than')) + @cli.register_custom_action( + "ProjectRegistryTagManager", optional=("name_regex", "keep_n", "older_than") + ) @exc.on_http_error(exc.GitlabDeleteError) - def delete_in_bulk(self, name_regex='.*', **kwargs): + def delete_in_bulk(self, name_regex=".*", **kwargs): """Delete Tag in bulk Args: @@ -1214,8 +1414,8 @@ class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - valid_attrs = ['keep_n', 'older_than'] - data = {'name_regex': name_regex} + valid_attrs = ["keep_n", "older_than"] + data = {"name_regex": name_regex} data.update({k: v for k, v in kwargs.items() if k in valid_attrs}) self.gitlab.http_delete(self.path, query_data=data, **kwargs) @@ -1225,34 +1425,32 @@ class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectBoardListManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/boards/%(board_id)s/lists' + _path = "/projects/%(project_id)s/boards/%(board_id)s/lists" _obj_cls = ProjectBoardList - _from_parent_attrs = {'project_id': 'project_id', - 'board_id': 'id'} - _create_attrs = (('label_id', ), tuple()) - _update_attrs = (('position', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "board_id": "id"} + _create_attrs = (("label_id",), tuple()) + _update_attrs = (("position",), tuple()) class ProjectBoard(ObjectDeleteMixin, RESTObject): - _managers = (('lists', 'ProjectBoardListManager'), ) + _managers = (("lists", "ProjectBoardListManager"),) class ProjectBoardManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/boards' + _path = "/projects/%(project_id)s/boards" _obj_cls = ProjectBoard - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("name",), tuple()) class ProjectBranch(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' + _id_attr = "name" - @cli.register_custom_action('ProjectBranch', tuple(), - ('developers_can_push', - 'developers_can_merge')) + @cli.register_custom_action( + "ProjectBranch", tuple(), ("developers_can_push", "developers_can_merge") + ) @exc.on_http_error(exc.GitlabProtectError) - def protect(self, developers_can_push=False, developers_can_merge=False, - **kwargs): + def protect(self, developers_can_push=False, developers_can_merge=False, **kwargs): """Protect the branch. Args: @@ -1266,14 +1464,16 @@ class ProjectBranch(ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabProtectError: If the branch could not be protected """ - id = self.get_id().replace('/', '%2F') - path = '%s/%s/protect' % (self.manager.path, id) - post_data = {'developers_can_push': developers_can_push, - 'developers_can_merge': developers_can_merge} + id = self.get_id().replace("/", "%2F") + path = "%s/%s/protect" % (self.manager.path, id) + post_data = { + "developers_can_push": developers_can_push, + "developers_can_merge": developers_can_merge, + } self.manager.gitlab.http_put(path, post_data=post_data, **kwargs) - self._attrs['protected'] = True + self._attrs["protected"] = True - @cli.register_custom_action('ProjectBranch') + @cli.register_custom_action("ProjectBranch") @exc.on_http_error(exc.GitlabProtectError) def unprotect(self, **kwargs): """Unprotect the branch. @@ -1285,32 +1485,31 @@ class ProjectBranch(ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabProtectError: If the branch could not be unprotected """ - id = self.get_id().replace('/', '%2F') - path = '%s/%s/unprotect' % (self.manager.path, id) + id = self.get_id().replace("/", "%2F") + path = "%s/%s/unprotect" % (self.manager.path, id) self.manager.gitlab.http_put(path, **kwargs) - self._attrs['protected'] = False + self._attrs["protected"] = False class ProjectBranchManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/repository/branches' + _path = "/projects/%(project_id)s/repository/branches" _obj_cls = ProjectBranch - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('branch', 'ref'), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("branch", "ref"), tuple()) class ProjectCustomAttribute(ObjectDeleteMixin, RESTObject): - _id_attr = 'key' + _id_attr = "key" -class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/custom_attributes' +class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/custom_attributes" _obj_cls = ProjectCustomAttribute - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectJob(RESTObject, RefreshMixin): - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabJobCancelError) def cancel(self, **kwargs): """Cancel the job. @@ -1322,10 +1521,10 @@ class ProjectJob(RESTObject, RefreshMixin): GitlabAuthenticationError: If authentication is not correct GitlabJobCancelError: If the job could not be canceled """ - path = '%s/%s/cancel' % (self.manager.path, self.get_id()) + path = "%s/%s/cancel" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabJobRetryError) def retry(self, **kwargs): """Retry the job. @@ -1337,10 +1536,10 @@ class ProjectJob(RESTObject, RefreshMixin): GitlabAuthenticationError: If authentication is not correct GitlabJobRetryError: If the job could not be retried """ - path = '%s/%s/retry' % (self.manager.path, self.get_id()) + path = "%s/%s/retry" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabJobPlayError) def play(self, **kwargs): """Trigger a job explicitly. @@ -1352,10 +1551,10 @@ class ProjectJob(RESTObject, RefreshMixin): GitlabAuthenticationError: If authentication is not correct GitlabJobPlayError: If the job could not be triggered """ - path = '%s/%s/play' % (self.manager.path, self.get_id()) + path = "%s/%s/play" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabJobEraseError) def erase(self, **kwargs): """Erase the job (remove job artifacts and trace). @@ -1367,10 +1566,10 @@ class ProjectJob(RESTObject, RefreshMixin): GitlabAuthenticationError: If authentication is not correct GitlabJobEraseError: If the job could not be erased """ - path = '%s/%s/erase' % (self.manager.path, self.get_id()) + path = "%s/%s/erase" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabCreateError) def keep_artifacts(self, **kwargs): """Prevent artifacts from being deleted when expiration is set. @@ -1382,13 +1581,12 @@ class ProjectJob(RESTObject, RefreshMixin): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the request could not be performed """ - path = '%s/%s/artifacts/keep' % (self.manager.path, self.get_id()) + path = "%s/%s/artifacts/keep" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabGetError) - def artifacts(self, streamed=False, action=None, chunk_size=1024, - **kwargs): + def artifacts(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Get the job artifacts. Args: @@ -1407,15 +1605,15 @@ class ProjectJob(RESTObject, RefreshMixin): Returns: str: The artifacts if `streamed` is False, None otherwise. """ - path = '%s/%s/artifacts' % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "%s/%s/artifacts" % (self.manager.path, self.get_id()) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabGetError) - def artifact(self, path, streamed=False, action=None, chunk_size=1024, - **kwargs): + def artifact(self, path, streamed=False, action=None, chunk_size=1024, **kwargs): """Get a single artifact file from within the job's artifacts archive. Args: @@ -1435,12 +1633,13 @@ class ProjectJob(RESTObject, RefreshMixin): Returns: str: The artifacts if `streamed` is False, None otherwise. """ - path = '%s/%s/artifacts/%s' % (self.manager.path, self.get_id(), path) - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "%s/%s/artifacts/%s" % (self.manager.path, self.get_id(), path) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action('ProjectJob') + @cli.register_custom_action("ProjectJob") @exc.on_http_error(exc.GitlabGetError) def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Get the job trace. @@ -1461,16 +1660,17 @@ class ProjectJob(RESTObject, RefreshMixin): Returns: str: The trace """ - path = '%s/%s/trace' % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "%s/%s/trace" % (self.manager.path, self.get_id()) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) class ProjectJobManager(RetrieveMixin, RESTManager): - _path = '/projects/%(project_id)s/jobs' + _path = "/projects/%(project_id)s/jobs" _obj_cls = ProjectJob - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectCommitStatus(RESTObject, RefreshMixin): @@ -1478,13 +1678,13 @@ class ProjectCommitStatus(RESTObject, RefreshMixin): class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager): - _path = ('/projects/%(project_id)s/repository/commits/%(commit_id)s' - '/statuses') + _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/statuses" _obj_cls = ProjectCommitStatus - _from_parent_attrs = {'project_id': 'project_id', 'commit_id': 'id'} - _create_attrs = (('state', ), - ('description', 'name', 'context', 'ref', 'target_url', - 'coverage')) + _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} + _create_attrs = ( + ("state",), + ("description", "name", "context", "ref", "target_url", "coverage"), + ) @exc.on_http_error(exc.GitlabCreateError) def create(self, data, **kwargs): @@ -1507,8 +1707,8 @@ class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager): # project_id and commit_id are in the data dict when using the CLI, but # they are missing when using only the API # See #511 - base_path = '/projects/%(project_id)s/statuses/%(commit_id)s' - if 'project_id' in data and 'commit_id' in data: + base_path = "/projects/%(project_id)s/statuses/%(commit_id)s" + if "project_id" in data and "commit_id" in data: path = base_path % data else: path = self._compute_path(base_path) @@ -1517,54 +1717,57 @@ class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager): class ProjectCommitComment(RESTObject): _id_attr = None - _short_print_attr = 'note' + _short_print_attr = "note" class ProjectCommitCommentManager(ListMixin, CreateMixin, RESTManager): - _path = ('/projects/%(project_id)s/repository/commits/%(commit_id)s' - '/comments') + _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s" "/comments" _obj_cls = ProjectCommitComment - _from_parent_attrs = {'project_id': 'project_id', 'commit_id': 'id'} - _create_attrs = (('note', ), ('path', 'line', 'line_type')) + _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} + _create_attrs = (("note",), ("path", "line", "line_type")) class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectCommitDiscussionNoteManager(GetMixin, CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = ('/projects/%(project_id)s/repository/commits/%(commit_id)s/' - 'discussions/%(discussion_id)s/notes') +class ProjectCommitDiscussionNoteManager( + GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = ( + "/projects/%(project_id)s/repository/commits/%(commit_id)s/" + "discussions/%(discussion_id)s/notes" + ) _obj_cls = ProjectCommitDiscussionNote - _from_parent_attrs = {'project_id': 'project_id', - 'commit_id': 'commit_id', - 'discussion_id': 'id'} - _create_attrs = (('body',), ('created_at', 'position')) - _update_attrs = (('body',), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "commit_id": "commit_id", + "discussion_id": "id", + } + _create_attrs = (("body",), ("created_at", "position")) + _update_attrs = (("body",), tuple()) class ProjectCommitDiscussion(RESTObject): - _managers = (('notes', 'ProjectCommitDiscussionNoteManager'),) + _managers = (("notes", "ProjectCommitDiscussionNoteManager"),) class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = ('/projects/%(project_id)s/repository/commits/%(commit_id)s/' - 'discussions') + _path = "/projects/%(project_id)s/repository/commits/%(commit_id)s/" "discussions" _obj_cls = ProjectCommitDiscussion - _from_parent_attrs = {'project_id': 'project_id', 'commit_id': 'id'} - _create_attrs = (('body',), ('created_at',)) + _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} + _create_attrs = (("body",), ("created_at",)) class ProjectCommit(RESTObject): - _short_print_attr = 'title' + _short_print_attr = "title" _managers = ( - ('comments', 'ProjectCommitCommentManager'), - ('discussions', 'ProjectCommitDiscussionManager'), - ('statuses', 'ProjectCommitStatusManager'), + ("comments", "ProjectCommitCommentManager"), + ("discussions", "ProjectCommitDiscussionManager"), + ("statuses", "ProjectCommitStatusManager"), ) - @cli.register_custom_action('ProjectCommit') + @cli.register_custom_action("ProjectCommit") @exc.on_http_error(exc.GitlabGetError) def diff(self, **kwargs): """Generate the commit diff. @@ -1579,10 +1782,10 @@ class ProjectCommit(RESTObject): Returns: list: The changes done in this commit """ - path = '%s/%s/diff' % (self.manager.path, self.get_id()) + path = "%s/%s/diff" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action('ProjectCommit', ('branch',)) + @cli.register_custom_action("ProjectCommit", ("branch",)) @exc.on_http_error(exc.GitlabCherryPickError) def cherry_pick(self, branch, **kwargs): """Cherry-pick a commit into a branch. @@ -1595,13 +1798,13 @@ class ProjectCommit(RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCherryPickError: If the cherry-pick could not be performed """ - path = '%s/%s/cherry_pick' % (self.manager.path, self.get_id()) - post_data = {'branch': branch} + path = "%s/%s/cherry_pick" % (self.manager.path, self.get_id()) + post_data = {"branch": branch} self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) - @cli.register_custom_action('ProjectCommit', optional=('type',)) + @cli.register_custom_action("ProjectCommit", optional=("type",)) @exc.on_http_error(exc.GitlabGetError) - def refs(self, type='all', **kwargs): + def refs(self, type="all", **kwargs): """List the references the commit is pushed to. Args: @@ -1615,11 +1818,11 @@ class ProjectCommit(RESTObject): Returns: list: The references the commit is pushed to. """ - path = '%s/%s/refs' % (self.manager.path, self.get_id()) - data = {'type': type} + path = "%s/%s/refs" % (self.manager.path, self.get_id()) + data = {"type": type} return self.manager.gitlab.http_get(path, query_data=data, **kwargs) - @cli.register_custom_action('ProjectCommit') + @cli.register_custom_action("ProjectCommit") @exc.on_http_error(exc.GitlabGetError) def merge_requests(self, **kwargs): """List the merge requests related to the commit. @@ -1634,20 +1837,22 @@ class ProjectCommit(RESTObject): Returns: list: The merge requests related to the commit. """ - path = '%s/%s/merge_requests' % (self.manager.path, self.get_id()) + path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager): - _path = '/projects/%(project_id)s/repository/commits' + _path = "/projects/%(project_id)s/repository/commits" _obj_cls = ProjectCommit - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('branch', 'commit_message', 'actions'), - ('author_email', 'author_name')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = ( + ("branch", "commit_message", "actions"), + ("author_email", "author_name"), + ) class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action('ProjectEnvironment') + @cli.register_custom_action("ProjectEnvironment") @exc.on_http_error(exc.GitlabStopError) def stop(self, **kwargs): """Stop the environment. @@ -1659,17 +1864,18 @@ class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabStopError: If the operation failed """ - path = '%s/%s/stop' % (self.manager.path, self.get_id()) + path = "%s/%s/stop" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path, **kwargs) -class ProjectEnvironmentManager(ListMixin, CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = '/projects/%(project_id)s/environments' +class ProjectEnvironmentManager( + ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = "/projects/%(project_id)s/environments" _obj_cls = ProjectEnvironment - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', ), ('external_url', )) - _update_attrs = (tuple(), ('name', 'external_url')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("name",), ("external_url",)) + _update_attrs = (tuple(), ("name", "external_url")) class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject): @@ -1677,13 +1883,13 @@ class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectKeyManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/deploy_keys' + _path = "/projects/%(project_id)s/deploy_keys" _obj_cls = ProjectKey - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('title', 'key'), ('can_push',)) - _update_attrs = (tuple(), ('title', 'can_push')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("title", "key"), ("can_push",)) + _update_attrs = (tuple(), ("title", "can_push")) - @cli.register_custom_action('ProjectKeyManager', ('key_id',)) + @cli.register_custom_action("ProjectKeyManager", ("key_id",)) @exc.on_http_error(exc.GitlabProjectDeployKeyError) def enable(self, key_id, **kwargs): """Enable a deploy key for a project. @@ -1696,7 +1902,7 @@ class ProjectKeyManager(CRUDMixin, RESTManager): GitlabAuthenticationError: If authentication is not correct GitlabProjectDeployKeyError: If the key could not be enabled """ - path = '%s/%s/enable' % (self.path, key_id) + path = "%s/%s/enable" % (self.path, key_id) self.gitlab.http_post(path, **kwargs) @@ -1705,11 +1911,11 @@ class ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/badges' + _path = "/projects/%(project_id)s/badges" _obj_cls = ProjectBadge - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('link_url', 'image_url'), tuple()) - _update_attrs = (tuple(), ('link_url', 'image_url')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("link_url", "image_url"), tuple()) + _update_attrs = (tuple(), ("link_url", "image_url")) class ProjectEvent(Event): @@ -1717,9 +1923,9 @@ class ProjectEvent(Event): class ProjectEventManager(EventManager): - _path = '/projects/%(project_id)s/events' + _path = "/projects/%(project_id)s/events" _obj_cls = ProjectEvent - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectFork(RESTObject): @@ -1727,14 +1933,25 @@ class ProjectFork(RESTObject): class ProjectForkManager(CreateMixin, ListMixin, RESTManager): - _path = '/projects/%(project_id)s/fork' + _path = "/projects/%(project_id)s/fork" _obj_cls = ProjectFork - _from_parent_attrs = {'project_id': 'id'} - _list_filters = ('archived', 'visibility', 'order_by', 'sort', 'search', - 'simple', 'owned', 'membership', 'starred', 'statistics', - 'with_custom_attributes', 'with_issues_enabled', - 'with_merge_requests_enabled') - _create_attrs = (tuple(), ('namespace', )) + _from_parent_attrs = {"project_id": "id"} + _list_filters = ( + "archived", + "visibility", + "order_by", + "sort", + "search", + "simple", + "owned", + "membership", + "starred", + "statistics", + "with_custom_attributes", + "with_issues_enabled", + "with_merge_requests_enabled", + ) + _create_attrs = (tuple(), ("namespace",)) def list(self, **kwargs): """Retrieve a list of objects. @@ -1755,31 +1972,49 @@ class ProjectForkManager(CreateMixin, ListMixin, RESTManager): GitlabListError: If the server cannot perform the request """ - path = self._compute_path('/projects/%(project_id)s/forks') + path = self._compute_path("/projects/%(project_id)s/forks") return ListMixin.list(self, path=path, **kwargs) class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'url' + _short_print_attr = "url" class ProjectHookManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/hooks' + _path = "/projects/%(project_id)s/hooks" _obj_cls = ProjectHook - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} _create_attrs = ( - ('url', ), - ('push_events', 'issues_events', 'confidential_issues_events', - 'merge_requests_events', 'tag_push_events', 'note_events', - 'job_events', 'pipeline_events', 'wiki_page_events', - 'enable_ssl_verification', 'token') + ("url",), + ( + "push_events", + "issues_events", + "confidential_issues_events", + "merge_requests_events", + "tag_push_events", + "note_events", + "job_events", + "pipeline_events", + "wiki_page_events", + "enable_ssl_verification", + "token", + ), ) _update_attrs = ( - ('url', ), - ('push_events', 'issues_events', 'confidential_issues_events', - 'merge_requests_events', 'tag_push_events', 'note_events', - 'job_events', 'pipeline_events', 'wiki_events', - 'enable_ssl_verification', 'token') + ("url",), + ( + "push_events", + "issues_events", + "confidential_issues_events", + "merge_requests_events", + "tag_push_events", + "note_events", + "job_events", + "pipeline_events", + "wiki_events", + "enable_ssl_verification", + "token", + ), ) @@ -1788,10 +2023,10 @@ class ProjectIssueAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/issues/%(issue_iid)s/award_emoji' + _path = "/projects/%(project_id)s/issues/%(issue_iid)s/award_emoji" _obj_cls = ProjectIssueAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + _create_attrs = (("name",), tuple()) class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject): @@ -1799,64 +2034,71 @@ class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ('/projects/%(project_id)s/issues/%(issue_iid)s' - '/notes/%(note_id)s/award_emoji') + _path = ( + "/projects/%(project_id)s/issues/%(issue_iid)s" "/notes/%(note_id)s/award_emoji" + ) _obj_cls = ProjectIssueNoteAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', - 'issue_iid': 'issue_iid', - 'note_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "issue_iid": "issue_iid", + "note_id": "id", + } + _create_attrs = (("name",), tuple()) class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (('awardemojis', 'ProjectIssueNoteAwardEmojiManager'),) + _managers = (("awardemojis", "ProjectIssueNoteAwardEmojiManager"),) class ProjectIssueNoteManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/issues/%(issue_iid)s/notes' + _path = "/projects/%(project_id)s/issues/%(issue_iid)s/notes" _obj_cls = ProjectIssueNote - _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} - _create_attrs = (('body', ), ('created_at', )) - _update_attrs = (('body', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + _create_attrs = (("body",), ("created_at",)) + _update_attrs = (("body",), tuple()) class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectIssueDiscussionNoteManager(GetMixin, CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = ('/projects/%(project_id)s/issues/%(issue_iid)s/' - 'discussions/%(discussion_id)s/notes') +class ProjectIssueDiscussionNoteManager( + GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = ( + "/projects/%(project_id)s/issues/%(issue_iid)s/" + "discussions/%(discussion_id)s/notes" + ) _obj_cls = ProjectIssueDiscussionNote - _from_parent_attrs = {'project_id': 'project_id', - 'issue_iid': 'issue_iid', - 'discussion_id': 'id'} - _create_attrs = (('body',), ('created_at',)) - _update_attrs = (('body',), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "issue_iid": "issue_iid", + "discussion_id": "id", + } + _create_attrs = (("body",), ("created_at",)) + _update_attrs = (("body",), tuple()) class ProjectIssueDiscussion(RESTObject): - _managers = (('notes', 'ProjectIssueDiscussionNoteManager'),) + _managers = (("notes", "ProjectIssueDiscussionNoteManager"),) class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = '/projects/%(project_id)s/issues/%(issue_iid)s/discussions' + _path = "/projects/%(project_id)s/issues/%(issue_iid)s/discussions" _obj_cls = ProjectIssueDiscussion - _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} - _create_attrs = (('body',), ('created_at',)) + _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + _create_attrs = (("body",), ("created_at",)) class ProjectIssueLink(ObjectDeleteMixin, RESTObject): - _id_attr = 'issue_link_id' + _id_attr = "issue_link_id" -class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/issues/%(issue_iid)s/links' +class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/issues/%(issue_iid)s/links" _obj_cls = ProjectIssueLink - _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} - _create_attrs = (('target_project_id', 'target_issue_iid'), tuple()) + _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + _create_attrs = (("target_project_id", "target_issue_iid"), tuple()) @exc.on_http_error(exc.GitlabCreateError) def create(self, data, **kwargs): @@ -1875,12 +2117,9 @@ class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, GitlabCreateError: If the server cannot perform the request """ self._check_missing_create_attrs(data) - server_data = self.gitlab.http_post(self.path, post_data=data, - **kwargs) - source_issue = ProjectIssue(self._parent.manager, - server_data['source_issue']) - target_issue = ProjectIssue(self._parent.manager, - server_data['target_issue']) + server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs) + source_issue = ProjectIssue(self._parent.manager, server_data["source_issue"]) + target_issue = ProjectIssue(self._parent.manager, server_data["target_issue"]) return source_issue, target_issue @@ -1889,26 +2128,32 @@ class ProjectIssueResourceLabelEvent(RESTObject): class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = ('/projects/%(project_id)s/issues/%(issue_iid)s' - '/resource_label_events') + _path = "/projects/%(project_id)s/issues/%(issue_iid)s" "/resource_label_events" _obj_cls = ProjectIssueResourceLabelEvent - _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} - - -class ProjectIssue(UserAgentDetailMixin, SubscribableMixin, TodoMixin, - TimeTrackingMixin, ParticipantsMixin, SaveMixin, - ObjectDeleteMixin, RESTObject): - _short_print_attr = 'title' - _id_attr = 'iid' + _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + + +class ProjectIssue( + UserAgentDetailMixin, + SubscribableMixin, + TodoMixin, + TimeTrackingMixin, + ParticipantsMixin, + SaveMixin, + ObjectDeleteMixin, + RESTObject, +): + _short_print_attr = "title" + _id_attr = "iid" _managers = ( - ('awardemojis', 'ProjectIssueAwardEmojiManager'), - ('discussions', 'ProjectIssueDiscussionManager'), - ('links', 'ProjectIssueLinkManager'), - ('notes', 'ProjectIssueNoteManager'), - ('resourcelabelevents', 'ProjectIssueResourceLabelEventManager'), + ("awardemojis", "ProjectIssueAwardEmojiManager"), + ("discussions", "ProjectIssueDiscussionManager"), + ("links", "ProjectIssueLinkManager"), + ("notes", "ProjectIssueNoteManager"), + ("resourcelabelevents", "ProjectIssueResourceLabelEventManager"), ) - @cli.register_custom_action('ProjectIssue', ('to_project_id',)) + @cli.register_custom_action("ProjectIssue", ("to_project_id",)) @exc.on_http_error(exc.GitlabUpdateError) def move(self, to_project_id, **kwargs): """Move the issue to another project. @@ -1921,13 +2166,12 @@ class ProjectIssue(UserAgentDetailMixin, SubscribableMixin, TodoMixin, GitlabAuthenticationError: If authentication is not correct GitlabUpdateError: If the issue could not be moved """ - path = '%s/%s/move' % (self.manager.path, self.get_id()) - data = {'to_project_id': to_project_id} - server_data = self.manager.gitlab.http_post(path, post_data=data, - **kwargs) + path = "%s/%s/move" % (self.manager.path, self.get_id()) + data = {"to_project_id": to_project_id} + server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('ProjectIssue') + @cli.register_custom_action("ProjectIssue") @exc.on_http_error(exc.GitlabGetError) def closed_by(self, **kwargs): """List merge requests that will close the issue when merged. @@ -1942,42 +2186,77 @@ class ProjectIssue(UserAgentDetailMixin, SubscribableMixin, TodoMixin, Returns: list: The list of merge requests. """ - path = '%s/%s/closed_by' % (self.manager.path, self.get_id()) + path = "%s/%s/closed_by" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) class ProjectIssueManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/issues' + _path = "/projects/%(project_id)s/issues" _obj_cls = ProjectIssue - _from_parent_attrs = {'project_id': 'id'} - _list_filters = ('iids', 'state', 'labels', 'milestone', 'scope', - 'author_id', 'assignee_id', 'my_reaction_emoji', - 'order_by', 'sort', 'search', 'created_after', - 'created_before', 'updated_after', 'updated_before') - _create_attrs = (('title', ), - ('description', 'confidential', 'assignee_ids', - 'assignee_id', 'milestone_id', 'labels', 'created_at', - 'due_date', 'merge_request_to_resolve_discussions_of', - 'discussion_to_resolve')) - _update_attrs = (tuple(), ('title', 'description', 'confidential', - 'assignee_ids', 'assignee_id', 'milestone_id', - 'labels', 'state_event', 'updated_at', - 'due_date', 'discussion_locked')) - _types = {'labels': types.ListAttribute} + _from_parent_attrs = {"project_id": "id"} + _list_filters = ( + "iids", + "state", + "labels", + "milestone", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "order_by", + "sort", + "search", + "created_after", + "created_before", + "updated_after", + "updated_before", + ) + _create_attrs = ( + ("title",), + ( + "description", + "confidential", + "assignee_ids", + "assignee_id", + "milestone_id", + "labels", + "created_at", + "due_date", + "merge_request_to_resolve_discussions_of", + "discussion_to_resolve", + ), + ) + _update_attrs = ( + tuple(), + ( + "title", + "description", + "confidential", + "assignee_ids", + "assignee_id", + "milestone_id", + "labels", + "state_event", + "updated_at", + "due_date", + "discussion_locked", + ), + ) + _types = {"labels": types.ListAttribute} class ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'username' + _short_print_attr = "username" class ProjectMemberManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/members' + _path = "/projects/%(project_id)s/members" _obj_cls = ProjectMember - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('access_level', 'user_id'), ('expires_at', )) - _update_attrs = (('access_level', ), ('expires_at', )) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("access_level", "user_id"), ("expires_at",)) + _update_attrs = (("access_level",), ("expires_at",)) - @cli.register_custom_action('ProjectMemberManager') + @cli.register_custom_action("ProjectMemberManager") @exc.on_http_error(exc.GitlabListError) def all(self, **kwargs): """List all the members, included inherited ones. @@ -1998,7 +2277,7 @@ class ProjectMemberManager(CRUDMixin, RESTManager): RESTObjectList: The list of members """ - path = '%s/all' % self.path + path = "%s/all" % self.path obj = self.gitlab.http_list(path, **kwargs) return [self._obj_cls(self, item) for item in obj] @@ -2008,10 +2287,10 @@ class ProjectNote(RESTObject): class ProjectNoteManager(RetrieveMixin, RESTManager): - _path = '/projects/%(project_id)s/notes' + _path = "/projects/%(project_id)s/notes" _obj_cls = ProjectNote - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('body', ), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("body",), tuple()) class ProjectNotificationSettings(NotificationSettings): @@ -2019,40 +2298,39 @@ class ProjectNotificationSettings(NotificationSettings): class ProjectNotificationSettingsManager(NotificationSettingsManager): - _path = '/projects/%(project_id)s/notification_settings' + _path = "/projects/%(project_id)s/notification_settings" _obj_cls = ProjectNotificationSettings - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = 'domain' + _id_attr = "domain" class ProjectPagesDomainManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/pages/domains' + _path = "/projects/%(project_id)s/pages/domains" _obj_cls = ProjectPagesDomain - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('domain', ), ('certificate', 'key')) - _update_attrs = (tuple(), ('certificate', 'key')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("domain",), ("certificate", "key")) + _update_attrs = (tuple(), ("certificate", "key")) class ProjectRelease(RESTObject): - _id_attr = 'tag_name' + _id_attr = "tag_name" class ProjectReleaseManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/releases' + _path = "/projects/%(project_id)s/releases" _obj_cls = ProjectRelease - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', 'tag_name', 'description', ), - ('ref', 'assets', )) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("name", "tag_name", "description"), ("ref", "assets")) class ProjectTag(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' - _short_print_attr = 'name' + _id_attr = "name" + _short_print_attr = "name" - @cli.register_custom_action('ProjectTag', ('description', )) + @cli.register_custom_action("ProjectTag", ("description",)) def set_release_description(self, description, **kwargs): """Set the release notes on the tag. @@ -2068,55 +2346,54 @@ class ProjectTag(ObjectDeleteMixin, RESTObject): GitlabCreateError: If the server fails to create the release GitlabUpdateError: If the server fails to update the release """ - id = self.get_id().replace('/', '%2F') - path = '%s/%s/release' % (self.manager.path, id) - data = {'description': description} + id = self.get_id().replace("/", "%2F") + path = "%s/%s/release" % (self.manager.path, id) + data = {"description": description} if self.release is None: try: - server_data = self.manager.gitlab.http_post(path, - post_data=data, - **kwargs) + server_data = self.manager.gitlab.http_post( + path, post_data=data, **kwargs + ) except exc.GitlabHttpError as e: raise exc.GitlabCreateError(e.response_code, e.error_message) else: try: - server_data = self.manager.gitlab.http_put(path, - post_data=data, - **kwargs) + server_data = self.manager.gitlab.http_put( + path, post_data=data, **kwargs + ) except exc.GitlabHttpError as e: raise exc.GitlabUpdateError(e.response_code, e.error_message) self.release = server_data class ProjectTagManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/repository/tags' + _path = "/projects/%(project_id)s/repository/tags" _obj_cls = ProjectTag - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('tag_name', 'ref'), ('message',)) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("tag_name", "ref"), ("message",)) class ProjectProtectedTag(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' - _short_print_attr = 'name' + _id_attr = "name" + _short_print_attr = "name" class ProjectProtectedTagManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/protected_tags' + _path = "/projects/%(project_id)s/protected_tags" _obj_cls = ProjectProtectedTag - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name',), ('create_access_level',)) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("name",), ("create_access_level",)) class ProjectMergeRequestApproval(SaveMixin, RESTObject): _id_attr = None -class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, - RESTManager): - _path = '/projects/%(project_id)s/merge_requests/%(mr_iid)s/approvals' +class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): + _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/approvals" _obj_cls = ProjectMergeRequestApproval - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} - _update_attrs = (('approvals_required',), tuple()) + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + _update_attrs = (("approvals_required",), tuple()) _update_uses_post = True @exc.on_http_error(exc.GitlabUpdateError) @@ -2131,10 +2408,8 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, GitlabAuthenticationError: If authentication is not correct GitlabUpdateError: If the server failed to perform the request """ - path = '%s/%s/approvers' % (self._parent.manager.path, - self._parent.get_id()) - data = {'approver_ids': approver_ids, - 'approver_group_ids': approver_group_ids} + path = "%s/%s/approvers" % (self._parent.manager.path, self._parent.get_id()) + data = {"approver_ids": approver_ids, "approver_group_ids": approver_group_ids} self.gitlab.http_put(path, post_data=data, **kwargs) @@ -2143,10 +2418,10 @@ class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/merge_requests/%(mr_iid)s/award_emoji' + _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/award_emoji" _obj_cls = ProjectMergeRequestAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + _create_attrs = (("name",), tuple()) class ProjectMergeRequestDiff(RESTObject): @@ -2154,9 +2429,9 @@ class ProjectMergeRequestDiff(RESTObject): class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager): - _path = '/projects/%(project_id)s/merge_requests/%(mr_iid)s/versions' + _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/versions" _obj_cls = ProjectMergeRequestDiff - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject): @@ -2164,56 +2439,64 @@ class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ('/projects/%(project_id)s/merge_requests/%(mr_iid)s' - '/notes/%(note_id)s/award_emoji') + _path = ( + "/projects/%(project_id)s/merge_requests/%(mr_iid)s" + "/notes/%(note_id)s/award_emoji" + ) _obj_cls = ProjectMergeRequestNoteAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', - 'mr_iid': 'mr_iid', - 'note_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "mr_iid": "mr_iid", + "note_id": "id", + } + _create_attrs = (("name",), tuple()) class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (('awardemojis', 'ProjectMergeRequestNoteAwardEmojiManager'),) + _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),) class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/merge_requests/%(mr_iid)s/notes' + _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/notes" _obj_cls = ProjectMergeRequestNote - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} - _create_attrs = (('body', ), tuple()) - _update_attrs = (('body', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + _create_attrs = (("body",), tuple()) + _update_attrs = (("body",), tuple()) -class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, - RESTObject): +class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectMergeRequestDiscussionNoteManager(GetMixin, CreateMixin, - UpdateMixin, DeleteMixin, - RESTManager): - _path = ('/projects/%(project_id)s/merge_requests/%(mr_iid)s/' - 'discussions/%(discussion_id)s/notes') +class ProjectMergeRequestDiscussionNoteManager( + GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = ( + "/projects/%(project_id)s/merge_requests/%(mr_iid)s/" + "discussions/%(discussion_id)s/notes" + ) _obj_cls = ProjectMergeRequestDiscussionNote - _from_parent_attrs = {'project_id': 'project_id', - 'mr_iid': 'mr_iid', - 'discussion_id': 'id'} - _create_attrs = (('body',), ('created_at',)) - _update_attrs = (('body',), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "mr_iid": "mr_iid", + "discussion_id": "id", + } + _create_attrs = (("body",), ("created_at",)) + _update_attrs = (("body",), tuple()) class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): - _managers = (('notes', 'ProjectMergeRequestDiscussionNoteManager'),) + _managers = (("notes", "ProjectMergeRequestDiscussionNoteManager"),) -class ProjectMergeRequestDiscussionManager(RetrieveMixin, CreateMixin, - UpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/merge_requests/%(mr_iid)s/discussions' +class ProjectMergeRequestDiscussionManager( + RetrieveMixin, CreateMixin, UpdateMixin, RESTManager +): + _path = "/projects/%(project_id)s/merge_requests/%(mr_iid)s/discussions" _obj_cls = ProjectMergeRequestDiscussion - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} - _create_attrs = (('body',), ('created_at', 'position')) - _update_attrs = (('resolved',), tuple()) + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + _create_attrs = (("body",), ("created_at", "position")) + _update_attrs = (("resolved",), tuple()) class ProjectMergeRequestResourceLabelEvent(RESTObject): @@ -2221,28 +2504,34 @@ class ProjectMergeRequestResourceLabelEvent(RESTObject): class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager): - _path = ('/projects/%(project_id)s/merge_requests/%(mr_iid)s' - '/resource_label_events') + _path = ( + "/projects/%(project_id)s/merge_requests/%(mr_iid)s" "/resource_label_events" + ) _obj_cls = ProjectMergeRequestResourceLabelEvent - _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} + _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} -class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, - ParticipantsMixin, SaveMixin, ObjectDeleteMixin, - RESTObject): - _id_attr = 'iid' +class ProjectMergeRequest( + SubscribableMixin, + TodoMixin, + TimeTrackingMixin, + ParticipantsMixin, + SaveMixin, + ObjectDeleteMixin, + RESTObject, +): + _id_attr = "iid" _managers = ( - ('approvals', 'ProjectMergeRequestApprovalManager'), - ('awardemojis', 'ProjectMergeRequestAwardEmojiManager'), - ('diffs', 'ProjectMergeRequestDiffManager'), - ('discussions', 'ProjectMergeRequestDiscussionManager'), - ('notes', 'ProjectMergeRequestNoteManager'), - ('resourcelabelevents', - 'ProjectMergeRequestResourceLabelEventManager'), + ("approvals", "ProjectMergeRequestApprovalManager"), + ("awardemojis", "ProjectMergeRequestAwardEmojiManager"), + ("diffs", "ProjectMergeRequestDiffManager"), + ("discussions", "ProjectMergeRequestDiscussionManager"), + ("notes", "ProjectMergeRequestNoteManager"), + ("resourcelabelevents", "ProjectMergeRequestResourceLabelEventManager"), ) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabMROnBuildSuccessError) def cancel_merge_when_pipeline_succeeds(self, **kwargs): """Cancel merge when the pipeline succeeds. @@ -2256,12 +2545,14 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, request """ - path = ('%s/%s/cancel_merge_when_pipeline_succeeds' % - (self.manager.path, self.get_id())) + path = "%s/%s/cancel_merge_when_pipeline_succeeds" % ( + self.manager.path, + self.get_id(), + ) server_data = self.manager.gitlab.http_put(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def closes_issues(self, **kwargs): """List issues that will close on merge." @@ -2281,14 +2572,12 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, Returns: RESTObjectList: List of issues """ - path = '%s/%s/closes_issues' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = ProjectIssueManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/closes_issues" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) return RESTObjectList(manager, ProjectIssue, data_list) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def commits(self, **kwargs): """List the merge request commits. @@ -2309,14 +2598,12 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, RESTObjectList: The list of commits """ - path = '%s/%s/commits' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = ProjectCommitManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/commits" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = ProjectCommitManager(self.manager.gitlab, parent=self.manager._parent) return RESTObjectList(manager, ProjectCommit, data_list) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def changes(self, **kwargs): """List the merge request changes. @@ -2331,10 +2618,10 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, Returns: RESTObjectList: List of changes """ - path = '%s/%s/changes' % (self.manager.path, self.get_id()) + path = "%s/%s/changes" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabListError) def pipelines(self, **kwargs): """List the merge request pipelines. @@ -2350,10 +2637,10 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, RESTObjectList: List of changes """ - path = '%s/%s/pipelines' % (self.manager.path, self.get_id()) + path = "%s/%s/pipelines" % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action('ProjectMergeRequest', tuple(), ('sha')) + @cli.register_custom_action("ProjectMergeRequest", tuple(), ("sha")) @exc.on_http_error(exc.GitlabMRApprovalError) def approve(self, sha=None, **kwargs): """Approve the merge request. @@ -2366,16 +2653,15 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, GitlabAuthenticationError: If authentication is not correct GitlabMRApprovalError: If the approval failed """ - path = '%s/%s/approve' % (self.manager.path, self.get_id()) + path = "%s/%s/approve" % (self.manager.path, self.get_id()) data = {} if sha: - data['sha'] = sha + data["sha"] = sha - server_data = self.manager.gitlab.http_post(path, post_data=data, - **kwargs) + server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('ProjectMergeRequest') + @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabMRApprovalError) def unapprove(self, **kwargs): """Unapprove the merge request. @@ -2387,22 +2673,29 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, GitlabAuthenticationError: If authentication is not correct GitlabMRApprovalError: If the unapproval failed """ - path = '%s/%s/unapprove' % (self.manager.path, self.get_id()) + path = "%s/%s/unapprove" % (self.manager.path, self.get_id()) data = {} - server_data = self.manager.gitlab.http_post(path, post_data=data, - **kwargs) + server_data = self.manager.gitlab.http_post(path, post_data=data, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('ProjectMergeRequest', tuple(), - ('merge_commit_message', - 'should_remove_source_branch', - 'merge_when_pipeline_succeeds')) + @cli.register_custom_action( + "ProjectMergeRequest", + tuple(), + ( + "merge_commit_message", + "should_remove_source_branch", + "merge_when_pipeline_succeeds", + ), + ) @exc.on_http_error(exc.GitlabMRClosedError) - def merge(self, merge_commit_message=None, - should_remove_source_branch=False, - merge_when_pipeline_succeeds=False, - **kwargs): + def merge( + self, + merge_commit_message=None, + should_remove_source_branch=False, + merge_when_pipeline_succeeds=False, + **kwargs + ): """Accept the merge request. Args: @@ -2417,47 +2710,78 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, GitlabAuthenticationError: If authentication is not correct GitlabMRClosedError: If the merge failed """ - path = '%s/%s/merge' % (self.manager.path, self.get_id()) + path = "%s/%s/merge" % (self.manager.path, self.get_id()) data = {} if merge_commit_message: - data['merge_commit_message'] = merge_commit_message + data["merge_commit_message"] = merge_commit_message if should_remove_source_branch: - data['should_remove_source_branch'] = True + data["should_remove_source_branch"] = True if merge_when_pipeline_succeeds: - data['merge_when_pipeline_succeeds'] = True + data["merge_when_pipeline_succeeds"] = True - server_data = self.manager.gitlab.http_put(path, post_data=data, - **kwargs) + server_data = self.manager.gitlab.http_put(path, post_data=data, **kwargs) self._update_attrs(server_data) class ProjectMergeRequestManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/merge_requests' + _path = "/projects/%(project_id)s/merge_requests" _obj_cls = ProjectMergeRequest - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} _create_attrs = ( - ('source_branch', 'target_branch', 'title'), - ('assignee_id', 'description', 'target_project_id', 'labels', - 'milestone_id', 'remove_source_branch', 'allow_maintainer_to_push', - 'squash') + ("source_branch", "target_branch", "title"), + ( + "assignee_id", + "description", + "target_project_id", + "labels", + "milestone_id", + "remove_source_branch", + "allow_maintainer_to_push", + "squash", + ), ) _update_attrs = ( tuple(), - ('target_branch', 'assignee_id', 'title', 'description', 'state_event', - 'labels', 'milestone_id', 'remove_source_branch', 'discussion_locked', - 'allow_maintainer_to_push', 'squash')) - _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', - 'labels', 'created_after', 'created_before', - 'updated_after', 'updated_before', 'scope', 'author_id', - 'assignee_id', 'my_reaction_emoji', 'source_branch', - 'target_branch', 'search') - _types = {'labels': types.ListAttribute} + ( + "target_branch", + "assignee_id", + "title", + "description", + "state_event", + "labels", + "milestone_id", + "remove_source_branch", + "discussion_locked", + "allow_maintainer_to_push", + "squash", + ), + ) + _list_filters = ( + "state", + "order_by", + "sort", + "milestone", + "view", + "labels", + "created_after", + "created_before", + "updated_after", + "updated_before", + "scope", + "author_id", + "assignee_id", + "my_reaction_emoji", + "source_branch", + "target_branch", + "search", + ) + _types = {"labels": types.ListAttribute} class ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'title' + _short_print_attr = "title" - @cli.register_custom_action('ProjectMilestone') + @cli.register_custom_action("ProjectMilestone") @exc.on_http_error(exc.GitlabListError) def issues(self, **kwargs): """List issues related to this milestone. @@ -2478,15 +2802,13 @@ class ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): RESTObjectList: The list of issues """ - path = '%s/%s/issues' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = ProjectIssueManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/issues" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = ProjectIssueManager(self.manager.gitlab, parent=self.manager._parent) # FIXME(gpocentek): the computed manager path is not correct return RESTObjectList(manager, ProjectIssue, data_list) - @cli.register_custom_action('ProjectMilestone') + @cli.register_custom_action("ProjectMilestone") @exc.on_http_error(exc.GitlabListError) def merge_requests(self, **kwargs): """List the merge requests related to this milestone. @@ -2506,29 +2828,32 @@ class ProjectMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: RESTObjectList: The list of merge requests """ - path = '%s/%s/merge_requests' % (self.manager.path, self.get_id()) - data_list = self.manager.gitlab.http_list(path, as_list=False, - **kwargs) - manager = ProjectMergeRequestManager(self.manager.gitlab, - parent=self.manager._parent) + path = "%s/%s/merge_requests" % (self.manager.path, self.get_id()) + data_list = self.manager.gitlab.http_list(path, as_list=False, **kwargs) + manager = ProjectMergeRequestManager( + self.manager.gitlab, parent=self.manager._parent + ) # FIXME(gpocentek): the computed manager path is not correct return RESTObjectList(manager, ProjectMergeRequest, data_list) class ProjectMilestoneManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/milestones' + _path = "/projects/%(project_id)s/milestones" _obj_cls = ProjectMilestone - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('title', ), ('description', 'due_date', 'start_date', - 'state_event')) - _update_attrs = (tuple(), ('title', 'description', 'due_date', - 'start_date', 'state_event')) - _list_filters = ('iids', 'state', 'search') + _from_parent_attrs = {"project_id": "id"} + _create_attrs = ( + ("title",), + ("description", "due_date", "start_date", "state_event"), + ) + _update_attrs = ( + tuple(), + ("title", "description", "due_date", "start_date", "state_event"), + ) + _list_filters = ("iids", "state", "search") -class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, - RESTObject): - _id_attr = 'name' +class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = "name" # Update without ID, but we need an ID to get from list. @exc.on_http_error(exc.GitlabUpdateError) @@ -2551,14 +2876,14 @@ class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin, self._update_attrs(server_data) -class ProjectLabelManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/labels' +class ProjectLabelManager( + ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = "/projects/%(project_id)s/labels" _obj_cls = ProjectLabel - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', 'color'), ('description', 'priority')) - _update_attrs = (('name', ), - ('new_name', 'color', 'description', 'priority')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("name", "color"), ("description", "priority")) + _update_attrs = (("name",), ("new_name", "color", "description", "priority")) # Delete without ID. @exc.on_http_error(exc.GitlabDeleteError) @@ -2573,12 +2898,12 @@ class ProjectLabelManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - self.gitlab.http_delete(self.path, query_data={'name': name}, **kwargs) + self.gitlab.http_delete(self.path, query_data={"name": name}, **kwargs) class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = 'file_path' - _short_print_attr = 'file_path' + _id_attr = "file_path" + _short_print_attr = "file_path" def decode(self): """Returns the decoded content of the file. @@ -2604,7 +2929,7 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): """ self.branch = branch self.commit_message = commit_message - self.file_path = self.file_path.replace('/', '%2F') + self.file_path = self.file_path.replace("/", "%2F") super(ProjectFile, self).save(**kwargs) def delete(self, branch, commit_message, **kwargs): @@ -2619,21 +2944,24 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - file_path = self.get_id().replace('/', '%2F') + file_path = self.get_id().replace("/", "%2F") self.manager.delete(file_path, branch, commit_message, **kwargs) -class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/repository/files' +class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/repository/files" _obj_cls = ProjectFile - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('file_path', 'branch', 'content', 'commit_message'), - ('encoding', 'author_email', 'author_name')) - _update_attrs = (('file_path', 'branch', 'content', 'commit_message'), - ('encoding', 'author_email', 'author_name')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = ( + ("file_path", "branch", "content", "commit_message"), + ("encoding", "author_email", "author_name"), + ) + _update_attrs = ( + ("file_path", "branch", "content", "commit_message"), + ("encoding", "author_email", "author_name"), + ) - @cli.register_custom_action('ProjectFileManager', ('file_path', 'ref')) + @cli.register_custom_action("ProjectFileManager", ("file_path", "ref")) def get(self, file_path, ref, **kwargs): """Retrieve a single file. @@ -2649,13 +2977,14 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, Returns: object: The generated RESTObject """ - file_path = file_path.replace('/', '%2F') + file_path = file_path.replace("/", "%2F") return GetMixin.get(self, file_path, ref=ref, **kwargs) - @cli.register_custom_action('ProjectFileManager', - ('file_path', 'branch', 'content', - 'commit_message'), - ('encoding', 'author_email', 'author_name')) + @cli.register_custom_action( + "ProjectFileManager", + ("file_path", "branch", "content", "commit_message"), + ("encoding", "author_email", "author_name"), + ) @exc.on_http_error(exc.GitlabCreateError) def create(self, data, **kwargs): """Create a new object. @@ -2676,8 +3005,8 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, self._check_missing_create_attrs(data) new_data = data.copy() - file_path = new_data.pop('file_path').replace('/', '%2F') - path = '%s/%s' % (self.path, file_path) + file_path = new_data.pop("file_path").replace("/", "%2F") + path = "%s/%s" % (self.path, file_path) server_data = self.gitlab.http_post(path, post_data=new_data, **kwargs) return self._obj_cls(self, server_data) @@ -2699,14 +3028,15 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, """ data = new_data.copy() - file_path = file_path.replace('/', '%2F') - data['file_path'] = file_path - path = '%s/%s' % (self.path, file_path) + file_path = file_path.replace("/", "%2F") + data["file_path"] = file_path + path = "%s/%s" % (self.path, file_path) self._check_missing_update_attrs(data) return self.gitlab.http_put(path, post_data=data, **kwargs) - @cli.register_custom_action('ProjectFileManager', ('file_path', 'branch', - 'commit_message')) + @cli.register_custom_action( + "ProjectFileManager", ("file_path", "branch", "commit_message") + ) @exc.on_http_error(exc.GitlabDeleteError) def delete(self, file_path, branch, commit_message, **kwargs): """Delete a file on the server. @@ -2721,14 +3051,15 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - path = '%s/%s' % (self.path, file_path.replace('/', '%2F')) - data = {'branch': branch, 'commit_message': commit_message} + path = "%s/%s" % (self.path, file_path.replace("/", "%2F")) + data = {"branch": branch, "commit_message": commit_message} self.gitlab.http_delete(path, query_data=data, **kwargs) - @cli.register_custom_action('ProjectFileManager', ('file_path', 'ref')) + @cli.register_custom_action("ProjectFileManager", ("file_path", "ref")) @exc.on_http_error(exc.GitlabGetError) - def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024, - **kwargs): + def raw( + self, file_path, ref, streamed=False, action=None, chunk_size=1024, **kwargs + ): """Return the content of a file for a commit. Args: @@ -2749,11 +3080,12 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, Returns: str: The file content """ - file_path = file_path.replace('/', '%2F').replace('.', '%2E') - path = '%s/%s/raw' % (self.path, file_path) - query_data = {'ref': ref} - result = self.gitlab.http_get(path, query_data=query_data, - streamed=streamed, raw=True, **kwargs) + file_path = file_path.replace("/", "%2F").replace(".", "%2E") + path = "%s/%s/raw" % (self.path, file_path) + query_data = {"ref": ref} + result = self.gitlab.http_get( + path, query_data=query_data, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) @@ -2762,17 +3094,16 @@ class ProjectPipelineJob(RESTObject): class ProjectPipelineJobManager(ListMixin, RESTManager): - _path = '/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs' + _path = "/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs" _obj_cls = ProjectPipelineJob - _from_parent_attrs = {'project_id': 'project_id', - 'pipeline_id': 'id'} - _list_filters = ('scope',) + _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} + _list_filters = ("scope",) class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin): - _managers = (('jobs', 'ProjectPipelineJobManager'), ) + _managers = (("jobs", "ProjectPipelineJobManager"),) - @cli.register_custom_action('ProjectPipeline') + @cli.register_custom_action("ProjectPipeline") @exc.on_http_error(exc.GitlabPipelineCancelError) def cancel(self, **kwargs): """Cancel the job. @@ -2784,10 +3115,10 @@ class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin): GitlabAuthenticationError: If authentication is not correct GitlabPipelineCancelError: If the request failed """ - path = '%s/%s/cancel' % (self.manager.path, self.get_id()) + path = "%s/%s/cancel" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) - @cli.register_custom_action('ProjectPipeline') + @cli.register_custom_action("ProjectPipeline") @exc.on_http_error(exc.GitlabPipelineRetryError) def retry(self, **kwargs): """Retry the job. @@ -2799,18 +3130,26 @@ class ProjectPipeline(RESTObject, RefreshMixin, ObjectDeleteMixin): GitlabAuthenticationError: If authentication is not correct GitlabPipelineRetryError: If the request failed """ - path = '%s/%s/retry' % (self.manager.path, self.get_id()) + path = "%s/%s/retry" % (self.manager.path, self.get_id()) self.manager.gitlab.http_post(path) -class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/pipelines' +class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/pipelines" _obj_cls = ProjectPipeline - _from_parent_attrs = {'project_id': 'id'} - _list_filters = ('scope', 'status', 'ref', 'sha', 'yaml_errors', 'name', - 'username', 'order_by', 'sort') - _create_attrs = (('ref', ), tuple()) + _from_parent_attrs = {"project_id": "id"} + _list_filters = ( + "scope", + "status", + "ref", + "sha", + "yaml_errors", + "name", + "username", + "order_by", + "sort", + ) + _create_attrs = (("ref",), tuple()) def create(self, data, **kwargs): """Creates a new object. @@ -2832,26 +3171,27 @@ class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, return CreateMixin.create(self, data, path=path, **kwargs) -class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, - RESTObject): - _id_attr = 'key' +class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = "key" -class ProjectPipelineScheduleVariableManager(CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = ('/projects/%(project_id)s/pipeline_schedules/' - '%(pipeline_schedule_id)s/variables') +class ProjectPipelineScheduleVariableManager( + CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = ( + "/projects/%(project_id)s/pipeline_schedules/" + "%(pipeline_schedule_id)s/variables" + ) _obj_cls = ProjectPipelineScheduleVariable - _from_parent_attrs = {'project_id': 'project_id', - 'pipeline_schedule_id': 'id'} - _create_attrs = (('key', 'value'), tuple()) - _update_attrs = (('key', 'value'), tuple()) + _from_parent_attrs = {"project_id": "project_id", "pipeline_schedule_id": "id"} + _create_attrs = (("key", "value"), tuple()) + _update_attrs = (("key", "value"), tuple()) class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (('variables', 'ProjectPipelineScheduleVariableManager'),) + _managers = (("variables", "ProjectPipelineScheduleVariableManager"),) - @cli.register_custom_action('ProjectPipelineSchedule') + @cli.register_custom_action("ProjectPipelineSchedule") @exc.on_http_error(exc.GitlabOwnershipError) def take_ownership(self, **kwargs): """Update the owner of a pipeline schedule. @@ -2863,40 +3203,55 @@ class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabOwnershipError: If the request failed """ - path = '%s/%s/take_ownership' % (self.manager.path, self.get_id()) + path = "%s/%s/take_ownership" % (self.manager.path, self.get_id()) server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) class ProjectPipelineScheduleManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/pipeline_schedules' + _path = "/projects/%(project_id)s/pipeline_schedules" _obj_cls = ProjectPipelineSchedule - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('description', 'ref', 'cron'), - ('cron_timezone', 'active')) - _update_attrs = (tuple(), - ('description', 'ref', 'cron', 'cron_timezone', 'active')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("description", "ref", "cron"), ("cron_timezone", "active")) + _update_attrs = (tuple(), ("description", "ref", "cron", "cron_timezone", "active")) class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = None -class ProjectPushRulesManager(GetWithoutIdMixin, CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = '/projects/%(project_id)s/push_rule' +class ProjectPushRulesManager( + GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = "/projects/%(project_id)s/push_rule" _obj_cls = ProjectPushRules - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (tuple(), - ('deny_delete_tag', 'member_check', - 'prevent_secrets', 'commit_message_regex', - 'branch_name_regex', 'author_email_regex', - 'file_name_regex', 'max_file_size')) - _update_attrs = (tuple(), - ('deny_delete_tag', 'member_check', - 'prevent_secrets', 'commit_message_regex', - 'branch_name_regex', 'author_email_regex', - 'file_name_regex', 'max_file_size')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = ( + tuple(), + ( + "deny_delete_tag", + "member_check", + "prevent_secrets", + "commit_message_regex", + "branch_name_regex", + "author_email_regex", + "file_name_regex", + "max_file_size", + ), + ) + _update_attrs = ( + tuple(), + ( + "deny_delete_tag", + "member_check", + "prevent_secrets", + "commit_message_regex", + "branch_name_regex", + "author_email_regex", + "file_name_regex", + "max_file_size", + ), + ) class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): @@ -2904,26 +3259,29 @@ class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = ('/projects/%(project_id)s/snippets/%(snippet_id)s' - '/notes/%(note_id)s/award_emoji') + _path = ( + "/projects/%(project_id)s/snippets/%(snippet_id)s" + "/notes/%(note_id)s/award_emoji" + ) _obj_cls = ProjectSnippetNoteAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', - 'snippet_id': 'snippet_id', - 'note_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "snippet_id": "snippet_id", + "note_id": "id", + } + _create_attrs = (("name",), tuple()) class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (('awardemojis', 'ProjectSnippetNoteAwardEmojiManager'),) + _managers = (("awardemojis", "ProjectSnippetNoteAwardEmojiManager"),) class ProjectSnippetNoteManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/snippets/%(snippet_id)s/notes' + _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/notes" _obj_cls = ProjectSnippetNote - _from_parent_attrs = {'project_id': 'project_id', - 'snippet_id': 'id'} - _create_attrs = (('body', ), tuple()) - _update_attrs = (('body', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} + _create_attrs = (("body",), tuple()) + _update_attrs = (("body",), tuple()) class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject): @@ -2931,50 +3289,54 @@ class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject): class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/snippets/%(snippet_id)s/award_emoji' + _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/award_emoji" _obj_cls = ProjectSnippetAwardEmoji - _from_parent_attrs = {'project_id': 'project_id', 'snippet_id': 'id'} - _create_attrs = (('name', ), tuple()) + _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} + _create_attrs = (("name",), tuple()) class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectSnippetDiscussionNoteManager(GetMixin, CreateMixin, UpdateMixin, - DeleteMixin, RESTManager): - _path = ('/projects/%(project_id)s/snippets/%(snippet_id)s/' - 'discussions/%(discussion_id)s/notes') +class ProjectSnippetDiscussionNoteManager( + GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager +): + _path = ( + "/projects/%(project_id)s/snippets/%(snippet_id)s/" + "discussions/%(discussion_id)s/notes" + ) _obj_cls = ProjectSnippetDiscussionNote - _from_parent_attrs = {'project_id': 'project_id', - 'snippet_id': 'snippet_id', - 'discussion_id': 'id'} - _create_attrs = (('body',), ('created_at',)) - _update_attrs = (('body',), tuple()) + _from_parent_attrs = { + "project_id": "project_id", + "snippet_id": "snippet_id", + "discussion_id": "id", + } + _create_attrs = (("body",), ("created_at",)) + _update_attrs = (("body",), tuple()) class ProjectSnippetDiscussion(RESTObject): - _managers = (('notes', 'ProjectSnippetDiscussionNoteManager'),) + _managers = (("notes", "ProjectSnippetDiscussionNoteManager"),) class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): - _path = '/projects/%(project_id)s/snippets/%(snippet_id)s/discussions' + _path = "/projects/%(project_id)s/snippets/%(snippet_id)s/discussions" _obj_cls = ProjectSnippetDiscussion - _from_parent_attrs = {'project_id': 'project_id', 'snippet_id': 'id'} - _create_attrs = (('body',), ('created_at',)) + _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} + _create_attrs = (("body",), ("created_at",)) -class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, - RESTObject): - _url = '/projects/%(project_id)s/snippets' - _short_print_attr = 'title' +class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): + _url = "/projects/%(project_id)s/snippets" + _short_print_attr = "title" _managers = ( - ('awardemojis', 'ProjectSnippetAwardEmojiManager'), - ('discussions', 'ProjectSnippetDiscussionManager'), - ('notes', 'ProjectSnippetNoteManager'), + ("awardemojis", "ProjectSnippetAwardEmojiManager"), + ("discussions", "ProjectSnippetDiscussionManager"), + ("notes", "ProjectSnippetNoteManager"), ) - @cli.register_custom_action('ProjectSnippet') + @cli.register_custom_action("ProjectSnippet") @exc.on_http_error(exc.GitlabGetError) def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Return the content of a snippet. @@ -2996,22 +3358,22 @@ class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, str: The snippet content """ path = "%s/%s/raw" % (self.manager.path, self.get_id()) - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) class ProjectSnippetManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/snippets' + _path = "/projects/%(project_id)s/snippets" _obj_cls = ProjectSnippet - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('title', 'file_name', 'code'), - ('lifetime', 'visibility')) - _update_attrs = (tuple(), ('title', 'file_name', 'code', 'visibility')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("title", "file_name", "code"), ("lifetime", "visibility")) + _update_attrs = (tuple(), ("title", "file_name", "code", "visibility")) class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action('ProjectTrigger') + @cli.register_custom_action("ProjectTrigger") @exc.on_http_error(exc.GitlabOwnershipError) def take_ownership(self, **kwargs): """Update the owner of a trigger. @@ -3023,17 +3385,17 @@ class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabOwnershipError: If the request failed """ - path = '%s/%s/take_ownership' % (self.manager.path, self.get_id()) + path = "%s/%s/take_ownership" % (self.manager.path, self.get_id()) server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) class ProjectTriggerManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/triggers' + _path = "/projects/%(project_id)s/triggers" _obj_cls = ProjectTrigger - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('description', ), tuple()) - _update_attrs = (('description', ), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("description",), tuple()) + _update_attrs = (("description",), tuple()) class ProjectUser(RESTObject): @@ -3041,22 +3403,22 @@ class ProjectUser(RESTObject): class ProjectUserManager(ListMixin, RESTManager): - _path = '/projects/%(project_id)s/users' + _path = "/projects/%(project_id)s/users" _obj_cls = ProjectUser - _from_parent_attrs = {'project_id': 'id'} - _list_filters = ('search',) + _from_parent_attrs = {"project_id": "id"} + _list_filters = ("search",) class ProjectVariable(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = 'key' + _id_attr = "key" class ProjectVariableManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/variables' + _path = "/projects/%(project_id)s/variables" _obj_cls = ProjectVariable - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('key', 'value'), tuple()) - _update_attrs = (('key', 'value'), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("key", "value"), tuple()) + _update_attrs = (("key", "value"), tuple()) class ProjectService(SaveMixin, ObjectDeleteMixin, RESTObject): @@ -3064,46 +3426,57 @@ class ProjectService(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = '/projects/%(project_id)s/services' - _from_parent_attrs = {'project_id': 'id'} + _path = "/projects/%(project_id)s/services" + _from_parent_attrs = {"project_id": "id"} _obj_cls = ProjectService _service_attrs = { - 'asana': (('api_key', ), ('restrict_to_branch', )), - 'assembla': (('token', ), ('subdomain', )), - 'bamboo': (('bamboo_url', 'build_key', 'username', 'password'), - tuple()), - 'buildkite': (('token', 'project_url'), ('enable_ssl_verification', )), - 'campfire': (('token', ), ('subdomain', 'room')), - 'custom-issue-tracker': (('new_issue_url', 'issues_url', - 'project_url'), - ('description', 'title')), - 'drone-ci': (('token', 'drone_url'), ('enable_ssl_verification', )), - 'emails-on-push': (('recipients', ), ('disable_diffs', - 'send_from_committer_email')), - 'builds-email': (('recipients', ), ('add_pusher', - 'notify_only_broken_builds')), - 'pipelines-email': (('recipients', ), ('add_pusher', - 'notify_only_broken_builds')), - 'external-wiki': (('external_wiki_url', ), tuple()), - 'flowdock': (('token', ), tuple()), - 'gemnasium': (('api_key', 'token', ), tuple()), - 'hipchat': (('token', ), ('color', 'notify', 'room', 'api_version', - 'server')), - 'irker': (('recipients', ), ('default_irc_uri', 'server_port', - 'server_host', 'colorize_messages')), - 'jira': (('url', 'project_key'), - ('new_issue_url', 'project_url', 'issues_url', 'api_url', - 'description', 'username', 'password', - 'jira_issue_transition_id')), - 'mattermost': (('webhook',), ('username', 'channel')), - 'pivotaltracker': (('token', ), tuple()), - 'pushover': (('api_key', 'user_key', 'priority'), ('device', 'sound')), - 'redmine': (('new_issue_url', 'project_url', 'issues_url'), - ('description', )), - 'slack': (('webhook', ), ('username', 'channel')), - 'teamcity': (('teamcity_url', 'build_type', 'username', 'password'), - tuple()) + "asana": (("api_key",), ("restrict_to_branch",)), + "assembla": (("token",), ("subdomain",)), + "bamboo": (("bamboo_url", "build_key", "username", "password"), tuple()), + "buildkite": (("token", "project_url"), ("enable_ssl_verification",)), + "campfire": (("token",), ("subdomain", "room")), + "custom-issue-tracker": ( + ("new_issue_url", "issues_url", "project_url"), + ("description", "title"), + ), + "drone-ci": (("token", "drone_url"), ("enable_ssl_verification",)), + "emails-on-push": ( + ("recipients",), + ("disable_diffs", "send_from_committer_email"), + ), + "builds-email": (("recipients",), ("add_pusher", "notify_only_broken_builds")), + "pipelines-email": ( + ("recipients",), + ("add_pusher", "notify_only_broken_builds"), + ), + "external-wiki": (("external_wiki_url",), tuple()), + "flowdock": (("token",), tuple()), + "gemnasium": (("api_key", "token"), tuple()), + "hipchat": (("token",), ("color", "notify", "room", "api_version", "server")), + "irker": ( + ("recipients",), + ("default_irc_uri", "server_port", "server_host", "colorize_messages"), + ), + "jira": ( + ("url", "project_key"), + ( + "new_issue_url", + "project_url", + "issues_url", + "api_url", + "description", + "username", + "password", + "jira_issue_transition_id", + ), + ), + "mattermost": (("webhook",), ("username", "channel")), + "pivotaltracker": (("token",), tuple()), + "pushover": (("api_key", "user_key", "priority"), ("device", "sound")), + "redmine": (("new_issue_url", "project_url", "issues_url"), ("description",)), + "slack": (("webhook",), ("username", "channel")), + "teamcity": (("teamcity_url", "build_type", "username", "password"), tuple()), } def get(self, id, **kwargs): @@ -3145,7 +3518,7 @@ class ProjectServiceManager(GetMixin, UpdateMixin, DeleteMixin, RESTManager): super(ProjectServiceManager, self).update(id, new_data, **kwargs) self.id = id - @cli.register_custom_action('ProjectServiceManager') + @cli.register_custom_action("ProjectServiceManager") def available(self, **kwargs): """List the services known by python-gitlab. @@ -3159,11 +3532,10 @@ class ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, - RESTManager): - _path = '/projects/%(project_id)s/access_requests' +class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/access_requests" _obj_cls = ProjectAccessRequest - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class ProjectApproval(SaveMixin, RESTObject): @@ -3171,12 +3543,17 @@ class ProjectApproval(SaveMixin, RESTObject): class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/approvals' + _path = "/projects/%(project_id)s/approvals" _obj_cls = ProjectApproval - _from_parent_attrs = {'project_id': 'id'} - _update_attrs = (tuple(), - ('approvals_before_merge', 'reset_approvals_on_push', - 'disable_overriding_approvers_per_merge_request')) + _from_parent_attrs = {"project_id": "id"} + _update_attrs = ( + tuple(), + ( + "approvals_before_merge", + "reset_approvals_on_push", + "disable_overriding_approvers_per_merge_request", + ), + ) _update_uses_post = True @exc.on_http_error(exc.GitlabUpdateError) @@ -3192,9 +3569,8 @@ class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): GitlabUpdateError: If the server failed to perform the request """ - path = '/projects/%s/approvers' % self._parent.get_id() - data = {'approver_ids': approver_ids, - 'approver_group_ids': approver_group_ids} + path = "/projects/%s/approvers" % self._parent.get_id() + data = {"approver_ids": approver_ids, "approver_group_ids": approver_group_ids} self.gitlab.http_put(path, post_data=data, **kwargs) @@ -3203,24 +3579,31 @@ class ProjectDeployment(RESTObject): class ProjectDeploymentManager(RetrieveMixin, RESTManager): - _path = '/projects/%(project_id)s/deployments' + _path = "/projects/%(project_id)s/deployments" _obj_cls = ProjectDeployment - _from_parent_attrs = {'project_id': 'id'} - _list_filters = ('order_by', 'sort') + _from_parent_attrs = {"project_id": "id"} + _list_filters = ("order_by", "sort") class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject): - _id_attr = 'name' + _id_attr = "name" class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/protected_branches' + _path = "/projects/%(project_id)s/protected_branches" _obj_cls = ProjectProtectedBranch - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', ), - ('push_access_level', 'merge_access_level', - 'unprotect_access_level', 'allowed_to_push', - 'allowed_to_merge', 'allowed_to_unprotect')) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = ( + ("name",), + ( + "push_access_level", + "merge_access_level", + "unprotect_access_level", + "allowed_to_push", + "allowed_to_merge", + "allowed_to_unprotect", + ), + ) class ProjectRunner(ObjectDeleteMixin, RESTObject): @@ -3228,30 +3611,30 @@ class ProjectRunner(ObjectDeleteMixin, RESTObject): class ProjectRunnerManager(NoUpdateMixin, RESTManager): - _path = '/projects/%(project_id)s/runners' + _path = "/projects/%(project_id)s/runners" _obj_cls = ProjectRunner - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('runner_id', ), tuple()) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("runner_id",), tuple()) class ProjectWiki(SaveMixin, ObjectDeleteMixin, RESTObject): - _id_attr = 'slug' - _short_print_attr = 'slug' + _id_attr = "slug" + _short_print_attr = "slug" class ProjectWikiManager(CRUDMixin, RESTManager): - _path = '/projects/%(project_id)s/wikis' + _path = "/projects/%(project_id)s/wikis" _obj_cls = ProjectWiki - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('title', 'content'), ('format', )) - _update_attrs = (tuple(), ('title', 'content', 'format')) - _list_filters = ('with_content', ) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (("title", "content"), ("format",)) + _update_attrs = (tuple(), ("title", "content", "format")) + _list_filters = ("with_content",) class ProjectExport(RefreshMixin, RESTObject): _id_attr = None - @cli.register_custom_action('ProjectExport') + @cli.register_custom_action("ProjectExport") @exc.on_http_error(exc.GitlabGetError) def download(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Download the archive of a project export. @@ -3272,17 +3655,18 @@ class ProjectExport(RefreshMixin, RESTObject): Returns: str: The blob content if streamed is False, None otherwise """ - path = '/projects/%s/export/download' % self.project_id - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "/projects/%s/export/download" % self.project_id + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): - _path = '/projects/%(project_id)s/export' + _path = "/projects/%(project_id)s/export" _obj_cls = ProjectExport - _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (tuple(), ('description',)) + _from_parent_attrs = {"project_id": "id"} + _create_attrs = (tuple(), ("description",)) class ProjectImport(RefreshMixin, RESTObject): @@ -3290,59 +3674,59 @@ class ProjectImport(RefreshMixin, RESTObject): class ProjectImportManager(GetWithoutIdMixin, RESTManager): - _path = '/projects/%(project_id)s/import' + _path = "/projects/%(project_id)s/import" _obj_cls = ProjectImport - _from_parent_attrs = {'project_id': 'id'} + _from_parent_attrs = {"project_id": "id"} class Project(SaveMixin, ObjectDeleteMixin, RESTObject): - _short_print_attr = 'path' + _short_print_attr = "path" _managers = ( - ('accessrequests', 'ProjectAccessRequestManager'), - ('approvals', 'ProjectApprovalManager'), - ('badges', 'ProjectBadgeManager'), - ('boards', 'ProjectBoardManager'), - ('branches', 'ProjectBranchManager'), - ('jobs', 'ProjectJobManager'), - ('commits', 'ProjectCommitManager'), - ('customattributes', 'ProjectCustomAttributeManager'), - ('deployments', 'ProjectDeploymentManager'), - ('environments', 'ProjectEnvironmentManager'), - ('events', 'ProjectEventManager'), - ('exports', 'ProjectExportManager'), - ('files', 'ProjectFileManager'), - ('forks', 'ProjectForkManager'), - ('hooks', 'ProjectHookManager'), - ('keys', 'ProjectKeyManager'), - ('imports', 'ProjectImportManager'), - ('issues', 'ProjectIssueManager'), - ('labels', 'ProjectLabelManager'), - ('members', 'ProjectMemberManager'), - ('mergerequests', 'ProjectMergeRequestManager'), - ('milestones', 'ProjectMilestoneManager'), - ('notes', 'ProjectNoteManager'), - ('notificationsettings', 'ProjectNotificationSettingsManager'), - ('pagesdomains', 'ProjectPagesDomainManager'), - ('pipelines', 'ProjectPipelineManager'), - ('protectedbranches', 'ProjectProtectedBranchManager'), - ('protectedtags', 'ProjectProtectedTagManager'), - ('pipelineschedules', 'ProjectPipelineScheduleManager'), - ('pushrules', 'ProjectPushRulesManager'), - ('releases', 'ProjectReleaseManager'), - ('repositories', 'ProjectRegistryRepositoryManager'), - ('runners', 'ProjectRunnerManager'), - ('services', 'ProjectServiceManager'), - ('snippets', 'ProjectSnippetManager'), - ('tags', 'ProjectTagManager'), - ('users', 'ProjectUserManager'), - ('triggers', 'ProjectTriggerManager'), - ('variables', 'ProjectVariableManager'), - ('wikis', 'ProjectWikiManager'), + ("accessrequests", "ProjectAccessRequestManager"), + ("approvals", "ProjectApprovalManager"), + ("badges", "ProjectBadgeManager"), + ("boards", "ProjectBoardManager"), + ("branches", "ProjectBranchManager"), + ("jobs", "ProjectJobManager"), + ("commits", "ProjectCommitManager"), + ("customattributes", "ProjectCustomAttributeManager"), + ("deployments", "ProjectDeploymentManager"), + ("environments", "ProjectEnvironmentManager"), + ("events", "ProjectEventManager"), + ("exports", "ProjectExportManager"), + ("files", "ProjectFileManager"), + ("forks", "ProjectForkManager"), + ("hooks", "ProjectHookManager"), + ("keys", "ProjectKeyManager"), + ("imports", "ProjectImportManager"), + ("issues", "ProjectIssueManager"), + ("labels", "ProjectLabelManager"), + ("members", "ProjectMemberManager"), + ("mergerequests", "ProjectMergeRequestManager"), + ("milestones", "ProjectMilestoneManager"), + ("notes", "ProjectNoteManager"), + ("notificationsettings", "ProjectNotificationSettingsManager"), + ("pagesdomains", "ProjectPagesDomainManager"), + ("pipelines", "ProjectPipelineManager"), + ("protectedbranches", "ProjectProtectedBranchManager"), + ("protectedtags", "ProjectProtectedTagManager"), + ("pipelineschedules", "ProjectPipelineScheduleManager"), + ("pushrules", "ProjectPushRulesManager"), + ("releases", "ProjectReleaseManager"), + ("repositories", "ProjectRegistryRepositoryManager"), + ("runners", "ProjectRunnerManager"), + ("services", "ProjectServiceManager"), + ("snippets", "ProjectSnippetManager"), + ("tags", "ProjectTagManager"), + ("users", "ProjectUserManager"), + ("triggers", "ProjectTriggerManager"), + ("variables", "ProjectVariableManager"), + ("wikis", "ProjectWikiManager"), ) - @cli.register_custom_action('Project', tuple(), ('path', 'ref')) + @cli.register_custom_action("Project", tuple(), ("path", "ref")) @exc.on_http_error(exc.GitlabGetError) - def repository_tree(self, path='', ref='', recursive=False, **kwargs): + def repository_tree(self, path="", ref="", recursive=False, **kwargs): """Return a list of files in the repository. Args: @@ -3363,16 +3747,15 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: list: The representation of the tree """ - gl_path = '/projects/%s/repository/tree' % self.get_id() - query_data = {'recursive': recursive} + gl_path = "/projects/%s/repository/tree" % self.get_id() + query_data = {"recursive": recursive} if path: - query_data['path'] = path + query_data["path"] = path if ref: - query_data['ref'] = ref - return self.manager.gitlab.http_list(gl_path, query_data=query_data, - **kwargs) + query_data["ref"] = ref + return self.manager.gitlab.http_list(gl_path, query_data=query_data, **kwargs) - @cli.register_custom_action('Project', ('sha', )) + @cli.register_custom_action("Project", ("sha",)) @exc.on_http_error(exc.GitlabGetError) def repository_blob(self, sha, **kwargs): """Return a file by blob SHA. @@ -3389,13 +3772,14 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): dict: The blob content and metadata """ - path = '/projects/%s/repository/blobs/%s' % (self.get_id(), sha) + path = "/projects/%s/repository/blobs/%s" % (self.get_id(), sha) return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action('Project', ('sha', )) + @cli.register_custom_action("Project", ("sha",)) @exc.on_http_error(exc.GitlabGetError) - def repository_raw_blob(self, sha, streamed=False, action=None, - chunk_size=1024, **kwargs): + def repository_raw_blob( + self, sha, streamed=False, action=None, chunk_size=1024, **kwargs + ): """Return the raw file contents for a blob. Args: @@ -3415,12 +3799,13 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: str: The blob content if streamed is False, None otherwise """ - path = '/projects/%s/repository/blobs/%s/raw' % (self.get_id(), sha) - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "/projects/%s/repository/blobs/%s/raw" % (self.get_id(), sha) + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action('Project', ('from_', 'to')) + @cli.register_custom_action("Project", ("from_", "to")) @exc.on_http_error(exc.GitlabGetError) def repository_compare(self, from_, to, **kwargs): """Return a diff between two branches/commits. @@ -3437,12 +3822,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: str: The diff """ - path = '/projects/%s/repository/compare' % self.get_id() - query_data = {'from': from_, 'to': to} - return self.manager.gitlab.http_get(path, query_data=query_data, - **kwargs) + path = "/projects/%s/repository/compare" % self.get_id() + query_data = {"from": from_, "to": to} + return self.manager.gitlab.http_get(path, query_data=query_data, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabGetError) def repository_contributors(self, **kwargs): """Return a list of contributors for the project. @@ -3462,13 +3846,14 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: list: The contributors """ - path = '/projects/%s/repository/contributors' % self.get_id() + path = "/projects/%s/repository/contributors" % self.get_id() return self.manager.gitlab.http_list(path, **kwargs) - @cli.register_custom_action('Project', tuple(), ('sha', )) + @cli.register_custom_action("Project", tuple(), ("sha",)) @exc.on_http_error(exc.GitlabListError) - def repository_archive(self, sha=None, streamed=False, action=None, - chunk_size=1024, **kwargs): + def repository_archive( + self, sha=None, streamed=False, action=None, chunk_size=1024, **kwargs + ): """Return a tarball of the repository. Args: @@ -3488,16 +3873,16 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: str: The binary data of the archive """ - path = '/projects/%s/repository/archive' % self.get_id() + path = "/projects/%s/repository/archive" % self.get_id() query_data = {} if sha: - query_data['sha'] = sha - result = self.manager.gitlab.http_get(path, query_data=query_data, - raw=True, streamed=streamed, - **kwargs) + query_data["sha"] = sha + result = self.manager.gitlab.http_get( + path, query_data=query_data, raw=True, streamed=streamed, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action('Project', ('forked_from_id', )) + @cli.register_custom_action("Project", ("forked_from_id",)) @exc.on_http_error(exc.GitlabCreateError) def create_fork_relation(self, forked_from_id, **kwargs): """Create a forked from/to relation between existing projects. @@ -3510,10 +3895,10 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the relation could not be created """ - path = '/projects/%s/fork/%s' % (self.get_id(), forked_from_id) + path = "/projects/%s/fork/%s" % (self.get_id(), forked_from_id) self.manager.gitlab.http_post(path, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabDeleteError) def delete_fork_relation(self, **kwargs): """Delete a forked relation between existing projects. @@ -3525,10 +3910,10 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server failed to perform the request """ - path = '/projects/%s/fork' % self.get_id() + path = "/projects/%s/fork" % self.get_id() self.manager.gitlab.http_delete(path, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabDeleteError) def delete_merged_branches(self, **kwargs): """Delete merged branches. @@ -3540,10 +3925,10 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server failed to perform the request """ - path = '/projects/%s/repository/merged_branches' % self.get_id() + path = "/projects/%s/repository/merged_branches" % self.get_id() self.manager.gitlab.http_delete(path, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabGetError) def languages(self, **kwargs): """Get languages used in the project with percentage value. @@ -3555,10 +3940,10 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabGetError: If the server failed to perform the request """ - path = '/projects/%s/languages' % self.get_id() + path = "/projects/%s/languages" % self.get_id() return self.manager.gitlab.http_get(path, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabCreateError) def star(self, **kwargs): """Star a project. @@ -3570,11 +3955,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - path = '/projects/%s/star' % self.get_id() + path = "/projects/%s/star" % self.get_id() server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabDeleteError) def unstar(self, **kwargs): """Unstar a project. @@ -3586,11 +3971,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server failed to perform the request """ - path = '/projects/%s/unstar' % self.get_id() + path = "/projects/%s/unstar" % self.get_id() server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabCreateError) def archive(self, **kwargs): """Archive a project. @@ -3602,11 +3987,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - path = '/projects/%s/archive' % self.get_id() + path = "/projects/%s/archive" % self.get_id() server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabDeleteError) def unarchive(self, **kwargs): """Unarchive a project. @@ -3618,12 +4003,13 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server failed to perform the request """ - path = '/projects/%s/unarchive' % self.get_id() + path = "/projects/%s/unarchive" % self.get_id() server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('Project', ('group_id', 'group_access'), - ('expires_at', )) + @cli.register_custom_action( + "Project", ("group_id", "group_access"), ("expires_at",) + ) @exc.on_http_error(exc.GitlabCreateError) def share(self, group_id, group_access, expires_at=None, **kwargs): """Share the project with a group. @@ -3637,13 +4023,15 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - path = '/projects/%s/share' % self.get_id() - data = {'group_id': group_id, - 'group_access': group_access, - 'expires_at': expires_at} + path = "/projects/%s/share" % self.get_id() + data = { + "group_id": group_id, + "group_access": group_access, + "expires_at": expires_at, + } self.manager.gitlab.http_post(path, post_data=data, **kwargs) - @cli.register_custom_action('Project', ('group_id', )) + @cli.register_custom_action("Project", ("group_id",)) @exc.on_http_error(exc.GitlabDeleteError) def unshare(self, group_id, **kwargs): """Delete a shared project link within a group. @@ -3656,11 +4044,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server failed to perform the request """ - path = '/projects/%s/share/%s' % (self.get_id(), group_id) + path = "/projects/%s/share/%s" % (self.get_id(), group_id) self.manager.gitlab.http_delete(path, **kwargs) # variables not supported in CLI - @cli.register_custom_action('Project', ('ref', 'token')) + @cli.register_custom_action("Project", ("ref", "token")) @exc.on_http_error(exc.GitlabCreateError) def trigger_pipeline(self, ref, token, variables={}, **kwargs): """Trigger a CI build. @@ -3677,13 +4065,12 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - path = '/projects/%s/trigger/pipeline' % self.get_id() - post_data = {'ref': ref, 'token': token, 'variables': variables} - attrs = self.manager.gitlab.http_post( - path, post_data=post_data, **kwargs) + path = "/projects/%s/trigger/pipeline" % self.get_id() + post_data = {"ref": ref, "token": token, "variables": variables} + attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) return ProjectPipeline(self.pipelines, attrs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabHousekeepingError) def housekeeping(self, **kwargs): """Start the housekeeping task. @@ -3696,11 +4083,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabHousekeepingError: If the server failed to perform the request """ - path = '/projects/%s/housekeeping' % self.get_id() + path = "/projects/%s/housekeeping" % self.get_id() self.manager.gitlab.http_post(path, **kwargs) # see #56 - add file attachment features - @cli.register_custom_action('Project', ('filename', 'filepath')) + @cli.register_custom_action("Project", ("filename", "filepath")) @exc.on_http_error(exc.GitlabUploadError) def upload(self, filename, filedata=None, filepath=None, **kwargs): """Upload the specified file into the project. @@ -3738,24 +4125,17 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): with open(filepath, "rb") as f: filedata = f.read() - url = ('/projects/%(id)s/uploads' % { - 'id': self.id, - }) - file_info = { - 'file': (filename, filedata), - } + url = "/projects/%(id)s/uploads" % {"id": self.id} + file_info = {"file": (filename, filedata)} data = self.manager.gitlab.http_post(url, files=file_info) - return { - "alt": data['alt'], - "url": data['url'], - "markdown": data['markdown'] - } + return {"alt": data["alt"], "url": data["url"], "markdown": data["markdown"]} - @cli.register_custom_action('Project', optional=('wiki',)) + @cli.register_custom_action("Project", optional=("wiki",)) @exc.on_http_error(exc.GitlabGetError) - def snapshot(self, wiki=False, streamed=False, action=None, - chunk_size=1024, **kwargs): + def snapshot( + self, wiki=False, streamed=False, action=None, chunk_size=1024, **kwargs + ): """Return a snapshot of the repository. Args: @@ -3775,12 +4155,13 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: str: The uncompressed tar archive of the repository """ - path = '/projects/%s/snapshot' % self.get_id() - result = self.manager.gitlab.http_get(path, streamed=streamed, - raw=True, **kwargs) + path = "/projects/%s/snapshot" % self.get_id() + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) return utils.response_content(result, streamed, action, chunk_size) - @cli.register_custom_action('Project', ('scope', 'search')) + @cli.register_custom_action("Project", ("scope", "search")) @exc.on_http_error(exc.GitlabSearchError) def search(self, scope, search, **kwargs): """Search the project resources matching the provided string.' @@ -3797,11 +4178,11 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: GitlabList: A list of dicts describing the resources found. """ - data = {'scope': scope, 'search': search} - path = '/projects/%s/search' % self.get_id() + data = {"scope": scope, "search": search} + path = "/projects/%s/search" % self.get_id() return self.manager.gitlab.http_list(path, query_data=data, **kwargs) - @cli.register_custom_action('Project') + @cli.register_custom_action("Project") @exc.on_http_error(exc.GitlabCreateError) def mirror_pull(self, **kwargs): """Start the pull mirroring process for the project. @@ -3813,10 +4194,10 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - path = '/projects/%s/mirror/pull' % self.get_id() + path = "/projects/%s/mirror/pull" % self.get_id() self.manager.gitlab.http_post(path, **kwargs) - @cli.register_custom_action('Project', ('to_namespace', )) + @cli.register_custom_action("Project", ("to_namespace",)) @exc.on_http_error(exc.GitlabTransferProjectError) def transfer_project(self, to_namespace, **kwargs): """Transfer a project to the given namespace ID @@ -3830,44 +4211,97 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabTransferProjectError: If the project could not be transfered """ - path = '/projects/%s/transfer' % (self.id,) - self.manager.gitlab.http_put(path, - post_data={"namespace": to_namespace}, - **kwargs) + path = "/projects/%s/transfer" % (self.id,) + self.manager.gitlab.http_put( + path, post_data={"namespace": to_namespace}, **kwargs + ) class ProjectManager(CRUDMixin, RESTManager): - _path = '/projects' + _path = "/projects" _obj_cls = Project _create_attrs = ( tuple(), - ('name', 'path', 'namespace_id', 'description', 'issues_enabled', - 'merge_requests_enabled', 'jobs_enabled', 'wiki_enabled', - 'snippets_enabled', 'resolve_outdated_diff_discussions', - 'container_registry_enabled', 'shared_runners_enabled', 'visibility', - 'import_url', 'public_jobs', 'only_allow_merge_if_pipeline_succeeds', - 'only_allow_merge_if_all_discussions_are_resolved', 'merge_method', - 'lfs_enabled', 'request_access_enabled', 'tag_list', 'avatar', - 'printing_merge_request_link_enabled', 'ci_config_path') + ( + "name", + "path", + "namespace_id", + "description", + "issues_enabled", + "merge_requests_enabled", + "jobs_enabled", + "wiki_enabled", + "snippets_enabled", + "resolve_outdated_diff_discussions", + "container_registry_enabled", + "shared_runners_enabled", + "visibility", + "import_url", + "public_jobs", + "only_allow_merge_if_pipeline_succeeds", + "only_allow_merge_if_all_discussions_are_resolved", + "merge_method", + "lfs_enabled", + "request_access_enabled", + "tag_list", + "avatar", + "printing_merge_request_link_enabled", + "ci_config_path", + ), ) _update_attrs = ( tuple(), - ('name', 'path', 'default_branch', 'description', 'issues_enabled', - 'merge_requests_enabled', 'jobs_enabled', 'wiki_enabled', - 'snippets_enabled', 'resolve_outdated_diff_discussions', - 'container_registry_enabled', 'shared_runners_enabled', 'visibility', - 'import_url', 'public_jobs', 'only_allow_merge_if_pipeline_succeeds', - 'only_allow_merge_if_all_discussions_are_resolved', 'merge_method', - 'lfs_enabled', 'request_access_enabled', 'tag_list', 'avatar', - 'ci_config_path') + ( + "name", + "path", + "default_branch", + "description", + "issues_enabled", + "merge_requests_enabled", + "jobs_enabled", + "wiki_enabled", + "snippets_enabled", + "resolve_outdated_diff_discussions", + "container_registry_enabled", + "shared_runners_enabled", + "visibility", + "import_url", + "public_jobs", + "only_allow_merge_if_pipeline_succeeds", + "only_allow_merge_if_all_discussions_are_resolved", + "merge_method", + "lfs_enabled", + "request_access_enabled", + "tag_list", + "avatar", + "ci_config_path", + ), + ) + _list_filters = ( + "search", + "owned", + "starred", + "archived", + "visibility", + "order_by", + "sort", + "simple", + "membership", + "statistics", + "with_issues_enabled", + "with_merge_requests_enabled", + "with_custom_attributes", ) - _list_filters = ('search', 'owned', 'starred', 'archived', 'visibility', - 'order_by', 'sort', 'simple', 'membership', 'statistics', - 'with_issues_enabled', 'with_merge_requests_enabled', - 'with_custom_attributes') - def import_project(self, file, path, namespace=None, overwrite=False, - override_params=None, **kwargs): + def import_project( + self, + file, + path, + namespace=None, + overwrite=False, + override_params=None, + **kwargs + ): """Import a project from an archive file. Args: @@ -3887,20 +4321,16 @@ class ProjectManager(CRUDMixin, RESTManager): Returns: dict: A representation of the import status. """ - files = { - 'file': ('file.tar.gz', file) - } - data = { - 'path': path, - 'overwrite': overwrite - } + files = {"file": ("file.tar.gz", file)} + data = {"path": path, "overwrite": overwrite} if override_params: for k, v in override_params.items(): - data['override_params[%s]' % k] = v + data["override_params[%s]" % k] = v if namespace: - data['namespace'] = namespace - return self.gitlab.http_post('/projects/import', post_data=data, - files=files, **kwargs) + data["namespace"] = namespace + return self.gitlab.http_post( + "/projects/import", post_data=data, files=files, **kwargs + ) class RunnerJob(RESTObject): @@ -3908,28 +4338,46 @@ class RunnerJob(RESTObject): class RunnerJobManager(ListMixin, RESTManager): - _path = '/runners/%(runner_id)s/jobs' + _path = "/runners/%(runner_id)s/jobs" _obj_cls = RunnerJob - _from_parent_attrs = {'runner_id': 'id'} - _list_filters = ('status',) + _from_parent_attrs = {"runner_id": "id"} + _list_filters = ("status",) class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): - _managers = (('jobs', 'RunnerJobManager'),) + _managers = (("jobs", "RunnerJobManager"),) class RunnerManager(CRUDMixin, RESTManager): - _path = '/runners' + _path = "/runners" _obj_cls = Runner - _list_filters = ('scope', ) - _create_attrs = (('token',), ('description', 'info', 'active', 'locked', - 'run_untagged', 'tag_list', - 'maximum_timeout')) - _update_attrs = (tuple(), ('description', 'active', 'tag_list', - 'run_untagged', 'locked', 'access_level', - 'maximum_timeout')) - - @cli.register_custom_action('RunnerManager', tuple(), ('scope', )) + _list_filters = ("scope",) + _create_attrs = ( + ("token",), + ( + "description", + "info", + "active", + "locked", + "run_untagged", + "tag_list", + "maximum_timeout", + ), + ) + _update_attrs = ( + tuple(), + ( + "description", + "active", + "tag_list", + "run_untagged", + "locked", + "access_level", + "maximum_timeout", + ), + ) + + @cli.register_custom_action("RunnerManager", tuple(), ("scope",)) @exc.on_http_error(exc.GitlabListError) def all(self, scope=None, **kwargs): """List all the runners. @@ -3951,13 +4399,13 @@ class RunnerManager(CRUDMixin, RESTManager): Returns: list(Runner): a list of runners matching the scope. """ - path = '/runners/all' + path = "/runners/all" query_data = {} if scope is not None: - query_data['scope'] = scope + query_data["scope"] = scope return self.gitlab.http_list(path, query_data, **kwargs) - @cli.register_custom_action('RunnerManager', ('token',)) + @cli.register_custom_action("RunnerManager", ("token",)) @exc.on_http_error(exc.GitlabVerifyError) def verify(self, token, **kwargs): """Validates authentication credentials for a registered Runner. @@ -3970,13 +4418,13 @@ class RunnerManager(CRUDMixin, RESTManager): GitlabAuthenticationError: If authentication is not correct GitlabVerifyError: If the server failed to verify the token """ - path = '/runners/verify' - post_data = {'token': token} + path = "/runners/verify" + post_data = {"token": token} self.gitlab.http_post(path, post_data=post_data, **kwargs) class Todo(ObjectDeleteMixin, RESTObject): - @cli.register_custom_action('Todo') + @cli.register_custom_action("Todo") @exc.on_http_error(exc.GitlabTodoError) def mark_as_done(self, **kwargs): """Mark the todo as done. @@ -3988,17 +4436,17 @@ class Todo(ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabTodoError: If the server failed to perform the request """ - path = '%s/%s/mark_as_done' % (self.manager.path, self.id) + path = "%s/%s/mark_as_done" % (self.manager.path, self.id) server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) class TodoManager(ListMixin, DeleteMixin, RESTManager): - _path = '/todos' + _path = "/todos" _obj_cls = Todo - _list_filters = ('action', 'author_id', 'project_id', 'state', 'type') + _list_filters = ("action", "author_id", "project_id", "state", "type") - @cli.register_custom_action('TodoManager') + @cli.register_custom_action("TodoManager") @exc.on_http_error(exc.GitlabTodoError) def mark_all_as_done(self, **kwargs): """Mark all the todos as done. @@ -4013,7 +4461,7 @@ class TodoManager(ListMixin, DeleteMixin, RESTManager): Returns: int: The number of todos maked done """ - result = self.gitlab.http_post('/todos/mark_as_done', **kwargs) + result = self.gitlab.http_post("/todos/mark_as_done", **kwargs) try: return int(result) except ValueError: @@ -4021,7 +4469,7 @@ class TodoManager(ListMixin, DeleteMixin, RESTManager): class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): - @cli.register_custom_action('GeoNode') + @cli.register_custom_action("GeoNode") @exc.on_http_error(exc.GitlabRepairError) def repair(self, **kwargs): """Repair the OAuth authentication of the geo node. @@ -4033,11 +4481,11 @@ class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): GitlabAuthenticationError: If authentication is not correct GitlabRepairError: If the server failed to perform the request """ - path = '/geo_nodes/%s/repair' % self.get_id() + path = "/geo_nodes/%s/repair" % self.get_id() server_data = self.manager.gitlab.http_post(path, **kwargs) self._update_attrs(server_data) - @cli.register_custom_action('GeoNode') + @cli.register_custom_action("GeoNode") @exc.on_http_error(exc.GitlabGetError) def status(self, **kwargs): """Get the status of the geo node. @@ -4052,17 +4500,19 @@ class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject): Returns: dict: The status of the geo node """ - path = '/geo_nodes/%s/status' % self.get_id() + path = "/geo_nodes/%s/status" % self.get_id() return self.manager.gitlab.http_get(path, **kwargs) class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): - _path = '/geo_nodes' + _path = "/geo_nodes" _obj_cls = GeoNode - _update_attrs = (tuple(), ('enabled', 'url', 'files_max_capacity', - 'repos_max_capacity')) + _update_attrs = ( + tuple(), + ("enabled", "url", "files_max_capacity", "repos_max_capacity"), + ) - @cli.register_custom_action('GeoNodeManager') + @cli.register_custom_action("GeoNodeManager") @exc.on_http_error(exc.GitlabGetError) def status(self, **kwargs): """Get the status of all the geo nodes. @@ -4077,9 +4527,9 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): Returns: list: The status of all the geo nodes """ - return self.gitlab.http_list('/geo_nodes/status', **kwargs) + return self.gitlab.http_list("/geo_nodes/status", **kwargs) - @cli.register_custom_action('GeoNodeManager') + @cli.register_custom_action("GeoNodeManager") @exc.on_http_error(exc.GitlabGetError) def current_failures(self, **kwargs): """Get the list of failures on the current geo node. @@ -4094,4 +4544,4 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): Returns: list: The list of failures """ - return self.gitlab.http_list('/geo_nodes/current/failures', **kwargs) + return self.gitlab.http_list("/geo_nodes/current/failures", **kwargs) @@ -6,43 +6,41 @@ from setuptools import find_packages def get_version(): - with open('gitlab/__init__.py') as f: + with open("gitlab/__init__.py") as f: for line in f: - if line.startswith('__version__'): - return eval(line.split('=')[-1]) + if line.startswith("__version__"): + return eval(line.split("=")[-1]) + with open("README.rst", "r") as readme_file: readme = readme_file.read() -setup(name='python-gitlab', - version=get_version(), - description='Interact with GitLab API', - long_description=readme, - author='Gauvain Pocentek', - author_email='gauvain@pocentek.net', - license='LGPLv3', - url='https://github.com/python-gitlab/python-gitlab', - packages=find_packages(), - install_requires=['requests>=2.4.2', 'six'], - entry_points={ - 'console_scripts': [ - 'gitlab = gitlab.cli:main' - ] - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', - 'Natural Language :: English', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - ] - ) +setup( + name="python-gitlab", + version=get_version(), + description="Interact with GitLab API", + long_description=readme, + author="Gauvain Pocentek", + author_email="gauvain@pocentek.net", + license="LGPLv3", + url="https://github.com/python-gitlab/python-gitlab", + packages=find_packages(), + install_requires=["requests>=2.4.2", "six"], + entry_points={"console_scripts": ["gitlab = gitlab.cli:main"]}, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + ], +) diff --git a/tools/ee-test.py b/tools/ee-test.py index bc98cc6..24a9b35 100755 --- a/tools/ee-test.py +++ b/tools/ee-test.py @@ -3,26 +3,26 @@ import gitlab -P1 = 'root/project1' -P2 = 'root/project2' +P1 = "root/project1" +P2 = "root/project2" MR_P1 = 1 I_P1 = 1 I_P2 = 1 EPIC_ISSUES = [4, 5] -G1 = 'group1' -LDAP_CN = 'app1' -LDAP_PROVIDER = 'ldapmain' +G1 = "group1" +LDAP_CN = "app1" +LDAP_PROVIDER = "ldapmain" def start_log(message): - print('Testing %s... ' % message, end='') + print("Testing %s... " % message, end="") def end_log(): - print('OK') + print("OK") -gl = gitlab.Gitlab.from_config('ee') +gl = gitlab.Gitlab.from_config("ee") project1 = gl.projects.get(P1) project2 = gl.projects.get(P2) issue_p1 = project1.issues.get(I_P1) @@ -30,113 +30,112 @@ issue_p2 = project2.issues.get(I_P2) group1 = gl.groups.get(G1) mr = project1.mergerequests.get(1) -start_log('MR approvals') +start_log("MR approvals") approval = project1.approvals.get() v = approval.reset_approvals_on_push approval.reset_approvals_on_push = not v approval.save() approval = project1.approvals.get() -assert(v != approval.reset_approvals_on_push) +assert v != approval.reset_approvals_on_push project1.approvals.set_approvers([1], []) approval = project1.approvals.get() -assert(approval.approvers[0]['user']['id'] == 1) +assert approval.approvers[0]["user"]["id"] == 1 approval = mr.approvals.get() approval.approvals_required = 2 approval.save() approval = mr.approvals.get() -assert(approval.approvals_required == 2) +assert approval.approvals_required == 2 approval.approvals_required = 3 approval.save() approval = mr.approvals.get() -assert(approval.approvals_required == 3) +assert approval.approvals_required == 3 mr.approvals.set_approvers([1], []) approval = mr.approvals.get() -assert(approval.approvers[0]['user']['id'] == 1) +assert approval.approvers[0]["user"]["id"] == 1 end_log() -start_log('geo nodes') +start_log("geo nodes") # very basic tests because we only have 1 node... nodes = gl.geonodes.list() status = gl.geonodes.status() end_log() -start_log('issue links') +start_log("issue links") # bit of cleanup just in case for link in issue_p1.links.list(): issue_p1.links.delete(link.issue_link_id) -src, dst = issue_p1.links.create({'target_project_id': P2, - 'target_issue_iid': I_P2}) +src, dst = issue_p1.links.create({"target_project_id": P2, "target_issue_iid": I_P2}) links = issue_p1.links.list() link_id = links[0].issue_link_id issue_p1.links.delete(link_id) end_log() -start_log('LDAP links') +start_log("LDAP links") # bit of cleanup just in case -if hasattr(group1, 'ldap_group_links'): +if hasattr(group1, "ldap_group_links"): for link in group1.ldap_group_links: - group1.delete_ldap_group_link(link['cn'], link['provider']) -assert(gl.ldapgroups.list()) + group1.delete_ldap_group_link(link["cn"], link["provider"]) +assert gl.ldapgroups.list() group1.add_ldap_group_link(LDAP_CN, 30, LDAP_PROVIDER) group1.ldap_sync() group1.delete_ldap_group_link(LDAP_CN) end_log() -start_log('boards') +start_log("boards") # bit of cleanup just in case for board in project1.boards.list(): - if board.name == 'testboard': + if board.name == "testboard": board.delete() -board = project1.boards.create({'name': 'testboard'}) +board = project1.boards.create({"name": "testboard"}) board = project1.boards.get(board.id) project1.boards.delete(board.id) for board in group1.boards.list(): - if board.name == 'testboard': + if board.name == "testboard": board.delete() -board = group1.boards.create({'name': 'testboard'}) +board = group1.boards.create({"name": "testboard"}) board = group1.boards.get(board.id) group1.boards.delete(board.id) end_log() -start_log('push rules') +start_log("push rules") pr = project1.pushrules.get() if pr: pr.delete() -pr = project1.pushrules.create({'deny_delete_tag': True}) +pr = project1.pushrules.create({"deny_delete_tag": True}) pr.deny_delete_tag = False pr.save() pr = project1.pushrules.get() -assert(pr is not None) -assert(pr.deny_delete_tag == False) +assert pr is not None +assert pr.deny_delete_tag == False pr.delete() end_log() -start_log('license') +start_log("license") l = gl.get_license() -assert('user_limit' in l) +assert "user_limit" in l try: - gl.set_license('dummykey') + gl.set_license("dummykey") except Exception as e: - assert('The license key is invalid.' in e.error_message) + assert "The license key is invalid." in e.error_message end_log() -start_log('epics') -epic = group1.epics.create({'title': 'Test epic'}) -epic.title = 'Fixed title' -epic.labels = ['label1', 'label2'] +start_log("epics") +epic = group1.epics.create({"title": "Test epic"}) +epic.title = "Fixed title" +epic.labels = ["label1", "label2"] epic.save() epic = group1.epics.get(epic.iid) -assert(epic.title == 'Fixed title') -assert(len(group1.epics.list())) +assert epic.title == "Fixed title" +assert len(group1.epics.list()) # issues -assert(not epic.issues.list()) +assert not epic.issues.list() for i in EPIC_ISSUES: - epic.issues.create({'issue_id': i}) -assert(len(EPIC_ISSUES) == len(epic.issues.list())) + epic.issues.create({"issue_id": i}) +assert len(EPIC_ISSUES) == len(epic.issues.list()) for ei in epic.issues.list(): ei.delete() diff --git a/tools/generate_token.py b/tools/generate_token.py index ab14188..9fa2ff2 100755 --- a/tools/generate_token.py +++ b/tools/generate_token.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys + try: from urllib.parse import urljoin except ImportError: @@ -33,10 +34,7 @@ def obtain_csrf_token(): def sign_in(csrf, cookies): - data = { - "user[login]": login, - "user[password]": password, - } + data = {"user[login]": login, "user[password]": password} data.update(csrf) r = requests.post(sign_in_route, data=data, cookies=cookies) token = find_csrf_token(r.text) @@ -51,7 +49,7 @@ def obtain_personal_access_token(name, csrf, cookies): data.update(csrf) r = requests.post(pat_route, data=data, cookies=cookies) soup = BeautifulSoup(r.text, "lxml") - token = soup.find('input', id='created-personal-access-token').get('value') + token = soup.find("input", id="created-personal-access-token").get("value") return token @@ -59,7 +57,7 @@ def main(): csrf1, cookies1 = obtain_csrf_token() csrf2, cookies2 = sign_in(csrf1, cookies1) - token = obtain_personal_access_token('default', csrf2, cookies2) + token = obtain_personal_access_token("default", csrf2, cookies2) print(token) diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 958e350..a00ae29 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -6,24 +6,28 @@ import requests import gitlab -LOGIN = 'root' -PASSWORD = '5iveL!fe' - -SSH_KEY = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" - "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" - "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" - "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" - "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" - "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar") -DEPLOY_KEY = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" - "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" - "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" - "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" - "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" - "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" - "vn bar@foo") - -GPG_KEY = '''-----BEGIN PGP PUBLIC KEY BLOCK----- +LOGIN = "root" +PASSWORD = "5iveL!fe" + +SSH_KEY = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZAjAX8vTiHD7Yi3/EzuVaDChtih" + "79HyJZ6H9dEqxFfmGA1YnncE0xujQ64TCebhkYJKzmTJCImSVkOu9C4hZgsw6eE76n" + "+Cg3VwEeDUFy+GXlEJWlHaEyc3HWioxgOALbUp3rOezNh+d8BDwwqvENGoePEBsz5l" + "a6WP5lTi/HJIjAl6Hu+zHgdj1XVExeH+S52EwpZf/ylTJub0Bl5gHwf/siVE48mLMI" + "sqrukXTZ6Zg+8EHAIvIQwJ1dKcXe8P5IoLT7VKrbkgAnolS0I8J+uH7KtErZJb5oZh" + "S4OEwsNpaXMAr+6/wWSpircV2/e7sFLlhlKBC4Iq1MpqlZ7G3p foo@bar" +) +DEPLOY_KEY = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFdRyjJQh+1niBpXqE2I8dzjG" + "MXFHlRjX9yk/UfOn075IdaockdU58sw2Ai1XIWFpZpfJkW7z+P47ZNSqm1gzeXI" + "rtKa9ZUp8A7SZe8vH4XVn7kh7bwWCUirqtn8El9XdqfkzOs/+FuViriUWoJVpA6" + "WZsDNaqINFKIA5fj/q8XQw+BcS92L09QJg9oVUuH0VVwNYbU2M2IRmSpybgC/gu" + "uWTrnCDMmLItksATifLvRZwgdI8dr+q6tbxbZknNcgEPrI2jT0hYN9ZcjNeWuyv" + "rke9IepE7SPBT41C+YtUX4dfDZDmczM1cE0YL/krdUCfuZHMa4ZS2YyNd6slufc" + "vn bar@foo" +) + +GPG_KEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFn5mzYBCADH6SDVPAp1zh/hxmTi0QplkOfExBACpuY6OhzNdIg+8/528b3g Y5YFR6T/HLv/PmeHskUj21end1C0PNG2T9dTx+2Vlh9ISsSG1kyF9T5fvMR3bE0x @@ -51,608 +55,636 @@ YzGsC/I9d7k6uxAv1L9Nm5F2HaAQDzhkdd16nKkGaPGR35cT1JLInkfl5cdm7ldN nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== =5OGa ------END PGP PUBLIC KEY BLOCK-----''' -AVATAR_PATH = os.path.join(os.path.dirname(__file__), 'avatar.png') +-----END PGP PUBLIC KEY BLOCK-----""" +AVATAR_PATH = os.path.join(os.path.dirname(__file__), "avatar.png") # token authentication from config file -gl = gitlab.Gitlab.from_config(config_files=['/tmp/python-gitlab.cfg']) +gl = gitlab.Gitlab.from_config(config_files=["/tmp/python-gitlab.cfg"]) gl.auth() -assert(isinstance(gl.user, gitlab.v4.objects.CurrentUser)) +assert isinstance(gl.user, gitlab.v4.objects.CurrentUser) # markdown (need to wait for gitlab 11 to enable the test) # html = gl.markdown('foo') # assert('foo' in html) -success, errors = gl.lint('Invalid') -assert(success is False) -assert(errors) +success, errors = gl.lint("Invalid") +assert success is False +assert errors # sidekiq out = gl.sidekiq.queue_metrics() -assert(isinstance(out, dict)) -assert('pages' in out['queues']) +assert isinstance(out, dict) +assert "pages" in out["queues"] out = gl.sidekiq.process_metrics() -assert(isinstance(out, dict)) -assert('hostname' in out['processes'][0]) +assert isinstance(out, dict) +assert "hostname" in out["processes"][0] out = gl.sidekiq.job_stats() -assert(isinstance(out, dict)) -assert('processed' in out['jobs']) +assert isinstance(out, dict) +assert "processed" in out["jobs"] out = gl.sidekiq.compound_metrics() -assert(isinstance(out, dict)) -assert('jobs' in out) -assert('processes' in out) -assert('queues' in out) +assert isinstance(out, dict) +assert "jobs" in out +assert "processes" in out +assert "queues" in out # settings settings = gl.settings.get() settings.default_projects_limit = 42 settings.save() settings = gl.settings.get() -assert(settings.default_projects_limit == 42) +assert settings.default_projects_limit == 42 # users -new_user = gl.users.create({'email': 'foo@bar.com', 'username': 'foo', - 'name': 'foo', 'password': 'foo_password', - 'avatar': open(AVATAR_PATH, 'rb')}) -avatar_url = new_user.avatar_url.replace('gitlab.test', 'localhost:8080') +new_user = gl.users.create( + { + "email": "foo@bar.com", + "username": "foo", + "name": "foo", + "password": "foo_password", + "avatar": open(AVATAR_PATH, "rb"), + } +) +avatar_url = new_user.avatar_url.replace("gitlab.test", "localhost:8080") uploaded_avatar = requests.get(avatar_url).content -assert(uploaded_avatar == open(AVATAR_PATH, 'rb').read()) +assert uploaded_avatar == open(AVATAR_PATH, "rb").read() users_list = gl.users.list() for user in users_list: - if user.username == 'foo': + if user.username == "foo": break -assert(new_user.username == user.username) -assert(new_user.email == user.email) +assert new_user.username == user.username +assert new_user.email == user.email new_user.block() new_user.unblock() # user projects list -assert(len(new_user.projects.list()) == 0) +assert len(new_user.projects.list()) == 0 # events list new_user.events.list() foobar_user = gl.users.create( - {'email': 'foobar@example.com', 'username': 'foobar', - 'name': 'Foo Bar', 'password': 'foobar_password'}) + { + "email": "foobar@example.com", + "username": "foobar", + "name": "Foo Bar", + "password": "foobar_password", + } +) -assert gl.users.list(search='foobar')[0].id == foobar_user.id +assert gl.users.list(search="foobar")[0].id == foobar_user.id expected = [new_user, foobar_user] -actual = list(gl.users.list(search='foo')) +actual = list(gl.users.list(search="foo")) assert len(expected) == len(actual) -assert len(gl.users.list(search='asdf')) == 0 -foobar_user.bio = 'This is the user bio' +assert len(gl.users.list(search="asdf")) == 0 +foobar_user.bio = "This is the user bio" foobar_user.save() # GPG keys -gkey = new_user.gpgkeys.create({'key': GPG_KEY}) -assert(len(new_user.gpgkeys.list()) == 1) +gkey = new_user.gpgkeys.create({"key": GPG_KEY}) +assert len(new_user.gpgkeys.list()) == 1 # Seems broken on the gitlab side # gkey = new_user.gpgkeys.get(gkey.id) gkey.delete() -assert(len(new_user.gpgkeys.list()) == 0) +assert len(new_user.gpgkeys.list()) == 0 # SSH keys -key = new_user.keys.create({'title': 'testkey', 'key': SSH_KEY}) -assert(len(new_user.keys.list()) == 1) +key = new_user.keys.create({"title": "testkey", "key": SSH_KEY}) +assert len(new_user.keys.list()) == 1 key.delete() -assert(len(new_user.keys.list()) == 0) +assert len(new_user.keys.list()) == 0 # emails -email = new_user.emails.create({'email': 'foo2@bar.com'}) -assert(len(new_user.emails.list()) == 1) +email = new_user.emails.create({"email": "foo2@bar.com"}) +assert len(new_user.emails.list()) == 1 email.delete() -assert(len(new_user.emails.list()) == 0) +assert len(new_user.emails.list()) == 0 # custom attributes attrs = new_user.customattributes.list() -assert(len(attrs) == 0) -attr = new_user.customattributes.set('key', 'value1') -assert(len(gl.users.list(custom_attributes={'key': 'value1'})) == 1) -assert(attr.key == 'key') -assert(attr.value == 'value1') -assert(len(new_user.customattributes.list()) == 1) -attr = new_user.customattributes.set('key', 'value2') -attr = new_user.customattributes.get('key') -assert(attr.value == 'value2') -assert(len(new_user.customattributes.list()) == 1) +assert len(attrs) == 0 +attr = new_user.customattributes.set("key", "value1") +assert len(gl.users.list(custom_attributes={"key": "value1"})) == 1 +assert attr.key == "key" +assert attr.value == "value1" +assert len(new_user.customattributes.list()) == 1 +attr = new_user.customattributes.set("key", "value2") +attr = new_user.customattributes.get("key") +assert attr.value == "value2" +assert len(new_user.customattributes.list()) == 1 attr.delete() -assert(len(new_user.customattributes.list()) == 0) +assert len(new_user.customattributes.list()) == 0 # impersonation tokens user_token = new_user.impersonationtokens.create( - {'name': 'token1', 'scopes': ['api', 'read_user']}) -l = new_user.impersonationtokens.list(state='active') -assert(len(l) == 1) + {"name": "token1", "scopes": ["api", "read_user"]} +) +l = new_user.impersonationtokens.list(state="active") +assert len(l) == 1 user_token.delete() -l = new_user.impersonationtokens.list(state='active') -assert(len(l) == 0) -l = new_user.impersonationtokens.list(state='inactive') -assert(len(l) == 1) +l = new_user.impersonationtokens.list(state="active") +assert len(l) == 0 +l = new_user.impersonationtokens.list(state="inactive") +assert len(l) == 1 new_user.delete() foobar_user.delete() -assert(len(gl.users.list()) == 3) +assert len(gl.users.list()) == 3 # current user mail -mail = gl.user.emails.create({'email': 'current@user.com'}) -assert(len(gl.user.emails.list()) == 1) +mail = gl.user.emails.create({"email": "current@user.com"}) +assert len(gl.user.emails.list()) == 1 mail.delete() -assert(len(gl.user.emails.list()) == 0) +assert len(gl.user.emails.list()) == 0 # current user GPG keys -gkey = gl.user.gpgkeys.create({'key': GPG_KEY}) -assert(len(gl.user.gpgkeys.list()) == 1) +gkey = gl.user.gpgkeys.create({"key": GPG_KEY}) +assert len(gl.user.gpgkeys.list()) == 1 # Seems broken on the gitlab side gkey = gl.user.gpgkeys.get(gkey.id) gkey.delete() -assert(len(gl.user.gpgkeys.list()) == 0) +assert len(gl.user.gpgkeys.list()) == 0 # current user key -key = gl.user.keys.create({'title': 'testkey', 'key': SSH_KEY}) -assert(len(gl.user.keys.list()) == 1) +key = gl.user.keys.create({"title": "testkey", "key": SSH_KEY}) +assert len(gl.user.keys.list()) == 1 key.delete() -assert(len(gl.user.keys.list()) == 0) +assert len(gl.user.keys.list()) == 0 # templates -assert(gl.dockerfiles.list()) -dockerfile = gl.dockerfiles.get('Node') -assert(dockerfile.content is not None) +assert gl.dockerfiles.list() +dockerfile = gl.dockerfiles.get("Node") +assert dockerfile.content is not None -assert(gl.gitignores.list()) -gitignore = gl.gitignores.get('Node') -assert(gitignore.content is not None) +assert gl.gitignores.list() +gitignore = gl.gitignores.get("Node") +assert gitignore.content is not None -assert(gl.gitlabciymls.list()) -gitlabciyml = gl.gitlabciymls.get('Nodejs') -assert(gitlabciyml.content is not None) +assert gl.gitlabciymls.list() +gitlabciyml = gl.gitlabciymls.get("Nodejs") +assert gitlabciyml.content is not None -assert(gl.licenses.list()) -license = gl.licenses.get('bsd-2-clause', project='mytestproject', - fullname='mytestfullname') -assert('mytestfullname' in license.content) +assert gl.licenses.list() +license = gl.licenses.get( + "bsd-2-clause", project="mytestproject", fullname="mytestfullname" +) +assert "mytestfullname" in license.content # groups -user1 = gl.users.create({'email': 'user1@test.com', 'username': 'user1', - 'name': 'user1', 'password': 'user1_pass'}) -user2 = gl.users.create({'email': 'user2@test.com', 'username': 'user2', - 'name': 'user2', 'password': 'user2_pass'}) -group1 = gl.groups.create({'name': 'group1', 'path': 'group1'}) -group2 = gl.groups.create({'name': 'group2', 'path': 'group2'}) +user1 = gl.users.create( + { + "email": "user1@test.com", + "username": "user1", + "name": "user1", + "password": "user1_pass", + } +) +user2 = gl.users.create( + { + "email": "user2@test.com", + "username": "user2", + "name": "user2", + "password": "user2_pass", + } +) +group1 = gl.groups.create({"name": "group1", "path": "group1"}) +group2 = gl.groups.create({"name": "group2", "path": "group2"}) -p_id = gl.groups.list(search='group2')[0].id -group3 = gl.groups.create({'name': 'group3', 'path': 'group3', 'parent_id': p_id}) +p_id = gl.groups.list(search="group2")[0].id +group3 = gl.groups.create({"name": "group3", "path": "group3", "parent_id": p_id}) -assert(len(gl.groups.list()) == 3) -assert(len(gl.groups.list(search='oup1')) == 1) -assert(group3.parent_id == p_id) -assert(group2.subgroups.list()[0].id == group3.id) +assert len(gl.groups.list()) == 3 +assert len(gl.groups.list(search="oup1")) == 1 +assert group3.parent_id == p_id +assert group2.subgroups.list()[0].id == group3.id -group1.members.create({'access_level': gitlab.const.OWNER_ACCESS, - 'user_id': user1.id}) -group1.members.create({'access_level': gitlab.const.GUEST_ACCESS, - 'user_id': user2.id}) +group1.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user1.id}) +group1.members.create({"access_level": gitlab.const.GUEST_ACCESS, "user_id": user2.id}) -group2.members.create({'access_level': gitlab.const.OWNER_ACCESS, - 'user_id': user2.id}) +group2.members.create({"access_level": gitlab.const.OWNER_ACCESS, "user_id": user2.id}) # Administrator belongs to the groups -assert(len(group1.members.list()) == 3) -assert(len(group2.members.list()) == 2) +assert len(group1.members.list()) == 3 +assert len(group2.members.list()) == 2 group1.members.delete(user1.id) -assert(len(group1.members.list()) == 2) -assert(len(group1.members.all())) +assert len(group1.members.list()) == 2 +assert len(group1.members.all()) member = group1.members.get(user2.id) member.access_level = gitlab.const.OWNER_ACCESS member.save() member = group1.members.get(user2.id) -assert(member.access_level == gitlab.const.OWNER_ACCESS) +assert member.access_level == gitlab.const.OWNER_ACCESS group2.members.delete(gl.user.id) # group custom attributes attrs = group2.customattributes.list() -assert(len(attrs) == 0) -attr = group2.customattributes.set('key', 'value1') -assert(len(gl.groups.list(custom_attributes={'key': 'value1'})) == 1) -assert(attr.key == 'key') -assert(attr.value == 'value1') -assert(len(group2.customattributes.list()) == 1) -attr = group2.customattributes.set('key', 'value2') -attr = group2.customattributes.get('key') -assert(attr.value == 'value2') -assert(len(group2.customattributes.list()) == 1) +assert len(attrs) == 0 +attr = group2.customattributes.set("key", "value1") +assert len(gl.groups.list(custom_attributes={"key": "value1"})) == 1 +assert attr.key == "key" +assert attr.value == "value1" +assert len(group2.customattributes.list()) == 1 +attr = group2.customattributes.set("key", "value2") +attr = group2.customattributes.get("key") +assert attr.value == "value2" +assert len(group2.customattributes.list()) == 1 attr.delete() -assert(len(group2.customattributes.list()) == 0) +assert len(group2.customattributes.list()) == 0 # group notification settings settings = group2.notificationsettings.get() -settings.level = 'disabled' +settings.level = "disabled" settings.save() settings = group2.notificationsettings.get() -assert(settings.level == 'disabled') +assert settings.level == "disabled" # group badges -badge_image = 'http://example.com' -badge_link = 'http://example/img.svg' -badge = group2.badges.create({'link_url': badge_link, 'image_url': badge_image}) -assert(len(group2.badges.list()) == 1) -badge.image_url = 'http://another.example.com' +badge_image = "http://example.com" +badge_link = "http://example/img.svg" +badge = group2.badges.create({"link_url": badge_link, "image_url": badge_image}) +assert len(group2.badges.list()) == 1 +badge.image_url = "http://another.example.com" badge.save() badge = group2.badges.get(badge.id) -assert(badge.image_url == 'http://another.example.com') +assert badge.image_url == "http://another.example.com" badge.delete() -assert(len(group2.badges.list()) == 0) +assert len(group2.badges.list()) == 0 # group milestones -gm1 = group1.milestones.create({'title': 'groupmilestone1'}) -assert(len(group1.milestones.list()) == 1) -gm1.due_date = '2020-01-01T00:00:00Z' +gm1 = group1.milestones.create({"title": "groupmilestone1"}) +assert len(group1.milestones.list()) == 1 +gm1.due_date = "2020-01-01T00:00:00Z" gm1.save() -gm1.state_event = 'close' +gm1.state_event = "close" gm1.save() gm1 = group1.milestones.get(gm1.id) -assert(gm1.state == 'closed') -assert(len(gm1.issues()) == 0) -assert(len(gm1.merge_requests()) == 0) +assert gm1.state == "closed" +assert len(gm1.issues()) == 0 +assert len(gm1.merge_requests()) == 0 # group variables -group1.variables.create({'key': 'foo', 'value': 'bar'}) -g_v = group1.variables.get('foo') -assert(g_v.value == 'bar') -g_v.value = 'baz' +group1.variables.create({"key": "foo", "value": "bar"}) +g_v = group1.variables.get("foo") +assert g_v.value == "bar" +g_v.value = "baz" g_v.save() -g_v = group1.variables.get('foo') -assert(g_v.value == 'baz') -assert(len(group1.variables.list()) == 1) +g_v = group1.variables.get("foo") +assert g_v.value == "baz" +assert len(group1.variables.list()) == 1 g_v.delete() -assert(len(group1.variables.list()) == 0) +assert len(group1.variables.list()) == 0 # hooks -hook = gl.hooks.create({'url': 'http://whatever.com'}) -assert(len(gl.hooks.list()) == 1) +hook = gl.hooks.create({"url": "http://whatever.com"}) +assert len(gl.hooks.list()) == 1 hook.delete() -assert(len(gl.hooks.list()) == 0) +assert len(gl.hooks.list()) == 0 # projects -admin_project = gl.projects.create({'name': 'admin_project'}) -gr1_project = gl.projects.create({'name': 'gr1_project', - 'namespace_id': group1.id}) -gr2_project = gl.projects.create({'name': 'gr2_project', - 'namespace_id': group2.id}) -sudo_project = gl.projects.create({'name': 'sudo_project'}, sudo=user1.name) +admin_project = gl.projects.create({"name": "admin_project"}) +gr1_project = gl.projects.create({"name": "gr1_project", "namespace_id": group1.id}) +gr2_project = gl.projects.create({"name": "gr2_project", "namespace_id": group2.id}) +sudo_project = gl.projects.create({"name": "sudo_project"}, sudo=user1.name) -assert(len(gl.projects.list(owned=True)) == 2) -assert(len(gl.projects.list(search="admin")) == 1) +assert len(gl.projects.list(owned=True)) == 2 +assert len(gl.projects.list(search="admin")) == 1 # test pagination l1 = gl.projects.list(per_page=1, page=1) l2 = gl.projects.list(per_page=1, page=2) -assert(len(l1) == 1) -assert(len(l2) == 1) -assert(l1[0].id != l2[0].id) +assert len(l1) == 1 +assert len(l2) == 1 +assert l1[0].id != l2[0].id # group custom attributes attrs = admin_project.customattributes.list() -assert(len(attrs) == 0) -attr = admin_project.customattributes.set('key', 'value1') -assert(len(gl.projects.list(custom_attributes={'key': 'value1'})) == 1) -assert(attr.key == 'key') -assert(attr.value == 'value1') -assert(len(admin_project.customattributes.list()) == 1) -attr = admin_project.customattributes.set('key', 'value2') -attr = admin_project.customattributes.get('key') -assert(attr.value == 'value2') -assert(len(admin_project.customattributes.list()) == 1) +assert len(attrs) == 0 +attr = admin_project.customattributes.set("key", "value1") +assert len(gl.projects.list(custom_attributes={"key": "value1"})) == 1 +assert attr.key == "key" +assert attr.value == "value1" +assert len(admin_project.customattributes.list()) == 1 +attr = admin_project.customattributes.set("key", "value2") +attr = admin_project.customattributes.get("key") +assert attr.value == "value2" +assert len(admin_project.customattributes.list()) == 1 attr.delete() -assert(len(admin_project.customattributes.list()) == 0) +assert len(admin_project.customattributes.list()) == 0 # project pages domains -domain = admin_project.pagesdomains.create({'domain': 'foo.domain.com'}) -assert(len(admin_project.pagesdomains.list()) == 1) -assert(len(gl.pagesdomains.list()) == 1) -domain = admin_project.pagesdomains.get('foo.domain.com') -assert(domain.domain == 'foo.domain.com') +domain = admin_project.pagesdomains.create({"domain": "foo.domain.com"}) +assert len(admin_project.pagesdomains.list()) == 1 +assert len(gl.pagesdomains.list()) == 1 +domain = admin_project.pagesdomains.get("foo.domain.com") +assert domain.domain == "foo.domain.com" domain.delete() -assert(len(admin_project.pagesdomains.list()) == 0) +assert len(admin_project.pagesdomains.list()) == 0 # project content (files) -admin_project.files.create({'file_path': 'README', - 'branch': 'master', - 'content': 'Initial content', - 'commit_message': 'Initial commit'}) -readme = admin_project.files.get(file_path='README', ref='master') +admin_project.files.create( + { + "file_path": "README", + "branch": "master", + "content": "Initial content", + "commit_message": "Initial commit", + } +) +readme = admin_project.files.get(file_path="README", ref="master") readme.content = base64.b64encode(b"Improved README").decode() time.sleep(2) readme.save(branch="master", commit_message="new commit") readme.delete(commit_message="Removing README", branch="master") -admin_project.files.create({'file_path': 'README.rst', - 'branch': 'master', - 'content': 'Initial content', - 'commit_message': 'New commit'}) -readme = admin_project.files.get(file_path='README.rst', ref='master') +admin_project.files.create( + { + "file_path": "README.rst", + "branch": "master", + "content": "Initial content", + "commit_message": "New commit", + } +) +readme = admin_project.files.get(file_path="README.rst", ref="master") # The first decode() is the ProjectFile method, the second one is the bytes # object method -assert(readme.decode().decode() == 'Initial content') +assert readme.decode().decode() == "Initial content" data = { - 'branch': 'master', - 'commit_message': 'blah blah blah', - 'actions': [ - { - 'action': 'create', - 'file_path': 'blah', - 'content': 'blah' - } - ] + "branch": "master", + "commit_message": "blah blah blah", + "actions": [{"action": "create", "file_path": "blah", "content": "blah"}], } admin_project.commits.create(data) -assert('@@' in admin_project.commits.list()[0].diff()[0]['diff']) +assert "@@" in admin_project.commits.list()[0].diff()[0]["diff"] # commit status commit = admin_project.commits.list()[0] -status = commit.statuses.create({'state': 'success', 'sha': commit.id}) -assert(len(commit.statuses.list()) == 1) +status = commit.statuses.create({"state": "success", "sha": commit.id}) +assert len(commit.statuses.list()) == 1 -assert(commit.refs()) -assert(commit.merge_requests() is not None) +assert commit.refs() +assert commit.merge_requests() is not None # commit comment -commit.comments.create({'note': 'This is a commit comment'}) -assert(len(commit.comments.list()) == 1) +commit.comments.create({"note": "This is a commit comment"}) +assert len(commit.comments.list()) == 1 # commit discussion count = len(commit.discussions.list()) -discussion = commit.discussions.create({'body': 'Discussion body'}) -assert(len(commit.discussions.list()) == (count + 1)) -d_note = discussion.notes.create({'body': 'first note'}) +discussion = commit.discussions.create({"body": "Discussion body"}) +assert len(commit.discussions.list()) == (count + 1) +d_note = discussion.notes.create({"body": "first note"}) d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = 'updated body' +d_note_from_get.body = "updated body" d_note_from_get.save() discussion = commit.discussions.get(discussion.id) -assert(discussion.attributes['notes'][-1]['body'] == 'updated body') +assert discussion.attributes["notes"][-1]["body"] == "updated body" d_note_from_get.delete() discussion = commit.discussions.get(discussion.id) -assert(len(discussion.attributes['notes']) == 1) +assert len(discussion.attributes["notes"]) == 1 # housekeeping admin_project.housekeeping() # repository tree = admin_project.repository_tree() -assert(len(tree) != 0) -assert(tree[0]['name'] == 'README.rst') -blob_id = tree[0]['id'] +assert len(tree) != 0 +assert tree[0]["name"] == "README.rst" +blob_id = tree[0]["id"] blob = admin_project.repository_raw_blob(blob_id) -assert(blob.decode() == 'Initial content') +assert blob.decode() == "Initial content" archive1 = admin_project.repository_archive() -archive2 = admin_project.repository_archive('master') -assert(archive1 == archive2) +archive2 = admin_project.repository_archive("master") +assert archive1 == archive2 snapshot = admin_project.snapshot() # project file uploads filename = "test.txt" file_contents = "testing contents" uploaded_file = admin_project.upload(filename, file_contents) -assert(uploaded_file["alt"] == filename) -assert(uploaded_file["url"].startswith("/uploads/")) -assert(uploaded_file["url"].endswith("/" + filename)) -assert(uploaded_file["markdown"] == "[{}]({})".format( - uploaded_file["alt"], - uploaded_file["url"], -)) +assert uploaded_file["alt"] == filename +assert uploaded_file["url"].startswith("/uploads/") +assert uploaded_file["url"].endswith("/" + filename) +assert uploaded_file["markdown"] == "[{}]({})".format( + uploaded_file["alt"], uploaded_file["url"] +) # environments -admin_project.environments.create({'name': 'env1', 'external_url': - 'http://fake.env/whatever'}) +admin_project.environments.create( + {"name": "env1", "external_url": "http://fake.env/whatever"} +) envs = admin_project.environments.list() -assert(len(envs) == 1) +assert len(envs) == 1 env = envs[0] -env.external_url = 'http://new.env/whatever' +env.external_url = "http://new.env/whatever" env.save() env = admin_project.environments.list()[0] -assert(env.external_url == 'http://new.env/whatever') +assert env.external_url == "http://new.env/whatever" env.stop() env.delete() -assert(len(admin_project.environments.list()) == 0) +assert len(admin_project.environments.list()) == 0 # project events admin_project.events.list() # forks -fork = admin_project.forks.create({'namespace': user1.username}) +fork = admin_project.forks.create({"namespace": user1.username}) p = gl.projects.get(fork.id) -assert(p.forked_from_project['id'] == admin_project.id) +assert p.forked_from_project["id"] == admin_project.id forks = admin_project.forks.list() -assert(fork.id in map(lambda p: p.id, forks)) +assert fork.id in map(lambda p: p.id, forks) # project hooks -hook = admin_project.hooks.create({'url': 'http://hook.url'}) -assert(len(admin_project.hooks.list()) == 1) +hook = admin_project.hooks.create({"url": "http://hook.url"}) +assert len(admin_project.hooks.list()) == 1 hook.note_events = True hook.save() hook = admin_project.hooks.get(hook.id) -assert(hook.note_events is True) +assert hook.note_events is True hook.delete() # deploy keys -deploy_key = admin_project.keys.create({'title': 'foo@bar', 'key': DEPLOY_KEY}) +deploy_key = admin_project.keys.create({"title": "foo@bar", "key": DEPLOY_KEY}) project_keys = list(admin_project.keys.list()) -assert(len(project_keys) == 1) +assert len(project_keys) == 1 sudo_project.keys.enable(deploy_key.id) -assert(len(sudo_project.keys.list()) == 1) +assert len(sudo_project.keys.list()) == 1 sudo_project.keys.delete(deploy_key.id) -assert(len(sudo_project.keys.list()) == 0) +assert len(sudo_project.keys.list()) == 0 # labels -label1 = admin_project.labels.create({'name': 'label1', 'color': '#778899'}) +label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) label1 = admin_project.labels.list()[0] -assert(len(admin_project.labels.list()) == 1) -label1.new_name = 'label1updated' +assert len(admin_project.labels.list()) == 1 +label1.new_name = "label1updated" label1.save() -assert(label1.name == 'label1updated') +assert label1.name == "label1updated" label1.subscribe() -assert(label1.subscribed == True) +assert label1.subscribed == True label1.unsubscribe() -assert(label1.subscribed == False) +assert label1.subscribed == False label1.delete() # milestones -m1 = admin_project.milestones.create({'title': 'milestone1'}) -assert(len(admin_project.milestones.list()) == 1) -m1.due_date = '2020-01-01T00:00:00Z' +m1 = admin_project.milestones.create({"title": "milestone1"}) +assert len(admin_project.milestones.list()) == 1 +m1.due_date = "2020-01-01T00:00:00Z" m1.save() -m1.state_event = 'close' +m1.state_event = "close" m1.save() m1 = admin_project.milestones.get(m1.id) -assert(m1.state == 'closed') -assert(len(m1.issues()) == 0) -assert(len(m1.merge_requests()) == 0) +assert m1.state == "closed" +assert len(m1.issues()) == 0 +assert len(m1.merge_requests()) == 0 # issues -issue1 = admin_project.issues.create({'title': 'my issue 1', - 'milestone_id': m1.id}) -issue2 = admin_project.issues.create({'title': 'my issue 2'}) -issue3 = admin_project.issues.create({'title': 'my issue 3'}) -assert(len(admin_project.issues.list()) == 3) -issue3.state_event = 'close' +issue1 = admin_project.issues.create({"title": "my issue 1", "milestone_id": m1.id}) +issue2 = admin_project.issues.create({"title": "my issue 2"}) +issue3 = admin_project.issues.create({"title": "my issue 3"}) +assert len(admin_project.issues.list()) == 3 +issue3.state_event = "close" issue3.save() -assert(len(admin_project.issues.list(state='closed')) == 1) -assert(len(admin_project.issues.list(state='opened')) == 2) -assert(len(admin_project.issues.list(milestone='milestone1')) == 1) -assert(m1.issues().next().title == 'my issue 1') -note = issue1.notes.create({'body': 'This is an issue note'}) -assert(len(issue1.notes.list()) == 1) -emoji = note.awardemojis.create({'name': 'tractor'}) -assert(len(note.awardemojis.list()) == 1) +assert len(admin_project.issues.list(state="closed")) == 1 +assert len(admin_project.issues.list(state="opened")) == 2 +assert len(admin_project.issues.list(milestone="milestone1")) == 1 +assert m1.issues().next().title == "my issue 1" +note = issue1.notes.create({"body": "This is an issue note"}) +assert len(issue1.notes.list()) == 1 +emoji = note.awardemojis.create({"name": "tractor"}) +assert len(note.awardemojis.list()) == 1 emoji.delete() -assert(len(note.awardemojis.list()) == 0) +assert len(note.awardemojis.list()) == 0 note.delete() -assert(len(issue1.notes.list()) == 0) -assert(isinstance(issue1.user_agent_detail(), dict)) +assert len(issue1.notes.list()) == 0 +assert isinstance(issue1.user_agent_detail(), dict) -assert(issue1.user_agent_detail()['user_agent']) -assert(issue1.participants()) +assert issue1.user_agent_detail()["user_agent"] +assert issue1.participants() # issues labels and events -label2 = admin_project.labels.create({'name': 'label2', 'color': '#aabbcc'}) -issue1.labels = ['label2'] +label2 = admin_project.labels.create({"name": "label2", "color": "#aabbcc"}) +issue1.labels = ["label2"] issue1.save() events = issue1.resourcelabelevents.list() -assert(events) +assert events event = issue1.resourcelabelevents.get(events[0].id) -assert(event) +assert event -discussion = issue1.discussions.create({'body': 'Discussion body'}) -assert(len(issue1.discussions.list()) == 1) -d_note = discussion.notes.create({'body': 'first note'}) +discussion = issue1.discussions.create({"body": "Discussion body"}) +assert len(issue1.discussions.list()) == 1 +d_note = discussion.notes.create({"body": "first note"}) d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = 'updated body' +d_note_from_get.body = "updated body" d_note_from_get.save() discussion = issue1.discussions.get(discussion.id) -assert(discussion.attributes['notes'][-1]['body'] == 'updated body') +assert discussion.attributes["notes"][-1]["body"] == "updated body" d_note_from_get.delete() discussion = issue1.discussions.get(discussion.id) -assert(len(discussion.attributes['notes']) == 1) +assert len(discussion.attributes["notes"]) == 1 # tags -tag1 = admin_project.tags.create({'tag_name': 'v1.0', 'ref': 'master'}) -assert(len(admin_project.tags.list()) == 1) -tag1.set_release_description('Description 1') -tag1.set_release_description('Description 2') -assert(tag1.release['description'] == 'Description 2') +tag1 = admin_project.tags.create({"tag_name": "v1.0", "ref": "master"}) +assert len(admin_project.tags.list()) == 1 +tag1.set_release_description("Description 1") +tag1.set_release_description("Description 2") +assert tag1.release["description"] == "Description 2" tag1.delete() # project snippet admin_project.snippets_enabled = True admin_project.save() snippet = admin_project.snippets.create( - {'title': 'snip1', 'file_name': 'foo.py', 'code': 'initial content', - 'visibility': gitlab.v4.objects.VISIBILITY_PRIVATE} + { + "title": "snip1", + "file_name": "foo.py", + "code": "initial content", + "visibility": gitlab.v4.objects.VISIBILITY_PRIVATE, + } ) -assert(snippet.user_agent_detail()['user_agent']) +assert snippet.user_agent_detail()["user_agent"] -discussion = snippet.discussions.create({'body': 'Discussion body'}) -assert(len(snippet.discussions.list()) == 1) -d_note = discussion.notes.create({'body': 'first note'}) +discussion = snippet.discussions.create({"body": "Discussion body"}) +assert len(snippet.discussions.list()) == 1 +d_note = discussion.notes.create({"body": "first note"}) d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = 'updated body' +d_note_from_get.body = "updated body" d_note_from_get.save() discussion = snippet.discussions.get(discussion.id) -assert(discussion.attributes['notes'][-1]['body'] == 'updated body') +assert discussion.attributes["notes"][-1]["body"] == "updated body" d_note_from_get.delete() discussion = snippet.discussions.get(discussion.id) -assert(len(discussion.attributes['notes']) == 1) +assert len(discussion.attributes["notes"]) == 1 -snippet.file_name = 'bar.py' +snippet.file_name = "bar.py" snippet.save() snippet = admin_project.snippets.get(snippet.id) -assert(snippet.content().decode() == 'initial content') -assert(snippet.file_name == 'bar.py') +assert snippet.content().decode() == "initial content" +assert snippet.file_name == "bar.py" size = len(admin_project.snippets.list()) snippet.delete() -assert(len(admin_project.snippets.list()) == (size - 1)) +assert len(admin_project.snippets.list()) == (size - 1) # triggers -tr1 = admin_project.triggers.create({'description': 'trigger1'}) -assert(len(admin_project.triggers.list()) == 1) +tr1 = admin_project.triggers.create({"description": "trigger1"}) +assert len(admin_project.triggers.list()) == 1 tr1.delete() # variables -v1 = admin_project.variables.create({'key': 'key1', 'value': 'value1'}) -assert(len(admin_project.variables.list()) == 1) -v1.value = 'new_value1' +v1 = admin_project.variables.create({"key": "key1", "value": "value1"}) +assert len(admin_project.variables.list()) == 1 +v1.value = "new_value1" v1.save() v1 = admin_project.variables.get(v1.key) -assert(v1.value == 'new_value1') +assert v1.value == "new_value1" v1.delete() # branches and merges -to_merge = admin_project.branches.create({'branch': 'branch1', - 'ref': 'master'}) -admin_project.files.create({'file_path': 'README2.rst', - 'branch': 'branch1', - 'content': 'Initial content', - 'commit_message': 'New commit in new branch'}) -mr = admin_project.mergerequests.create({'source_branch': 'branch1', - 'target_branch': 'master', - 'title': 'MR readme2'}) +to_merge = admin_project.branches.create({"branch": "branch1", "ref": "master"}) +admin_project.files.create( + { + "file_path": "README2.rst", + "branch": "branch1", + "content": "Initial content", + "commit_message": "New commit in new branch", + } +) +mr = admin_project.mergerequests.create( + {"source_branch": "branch1", "target_branch": "master", "title": "MR readme2"} +) # discussion -discussion = mr.discussions.create({'body': 'Discussion body'}) -assert(len(mr.discussions.list()) == 1) -d_note = discussion.notes.create({'body': 'first note'}) +discussion = mr.discussions.create({"body": "Discussion body"}) +assert len(mr.discussions.list()) == 1 +d_note = discussion.notes.create({"body": "first note"}) d_note_from_get = discussion.notes.get(d_note.id) -d_note_from_get.body = 'updated body' +d_note_from_get.body = "updated body" d_note_from_get.save() discussion = mr.discussions.get(discussion.id) -assert(discussion.attributes['notes'][-1]['body'] == 'updated body') +assert discussion.attributes["notes"][-1]["body"] == "updated body" d_note_from_get.delete() discussion = mr.discussions.get(discussion.id) -assert(len(discussion.attributes['notes']) == 1) +assert len(discussion.attributes["notes"]) == 1 # mr labels and events -mr.labels = ['label2'] +mr.labels = ["label2"] mr.save() events = mr.resourcelabelevents.list() -assert(events) +assert events event = mr.resourcelabelevents.get(events[0].id) -assert(event) +assert event # basic testing: only make sure that the methods exist mr.commits() mr.changes() -assert(mr.participants()) +assert mr.participants() mr.merge() -admin_project.branches.delete('branch1') +admin_project.branches.delete("branch1") try: mr.merge() @@ -660,52 +692,52 @@ except gitlab.GitlabMRClosedError: pass # protected branches -p_b = admin_project.protectedbranches.create({'name': '*-stable'}) -assert(p_b.name == '*-stable') -p_b = admin_project.protectedbranches.get('*-stable') +p_b = admin_project.protectedbranches.create({"name": "*-stable"}) +assert p_b.name == "*-stable" +p_b = admin_project.protectedbranches.get("*-stable") # master is protected by default when a branch has been created -assert(len(admin_project.protectedbranches.list()) == 2) -admin_project.protectedbranches.delete('master') +assert len(admin_project.protectedbranches.list()) == 2 +admin_project.protectedbranches.delete("master") p_b.delete() -assert(len(admin_project.protectedbranches.list()) == 0) +assert len(admin_project.protectedbranches.list()) == 0 # stars admin_project.star() -assert(admin_project.star_count == 1) +assert admin_project.star_count == 1 admin_project.unstar() -assert(admin_project.star_count == 0) +assert admin_project.star_count == 0 # project boards -#boards = admin_project.boards.list() -#assert(len(boards)) -#board = boards[0] -#lists = board.lists.list() -#begin_size = len(lists) -#last_list = lists[-1] -#last_list.position = 0 -#last_list.save() -#last_list.delete() -#lists = board.lists.list() -#assert(len(lists) == begin_size - 1) +# boards = admin_project.boards.list() +# assert(len(boards)) +# board = boards[0] +# lists = board.lists.list() +# begin_size = len(lists) +# last_list = lists[-1] +# last_list.position = 0 +# last_list.save() +# last_list.delete() +# lists = board.lists.list() +# assert(len(lists) == begin_size - 1) # project badges -badge_image = 'http://example.com' -badge_link = 'http://example/img.svg' -badge = admin_project.badges.create({'link_url': badge_link, 'image_url': badge_image}) -assert(len(admin_project.badges.list()) == 1) -badge.image_url = 'http://another.example.com' +badge_image = "http://example.com" +badge_link = "http://example/img.svg" +badge = admin_project.badges.create({"link_url": badge_link, "image_url": badge_image}) +assert len(admin_project.badges.list()) == 1 +badge.image_url = "http://another.example.com" badge.save() badge = admin_project.badges.get(badge.id) -assert(badge.image_url == 'http://another.example.com') +assert badge.image_url == "http://another.example.com" badge.delete() -assert(len(admin_project.badges.list()) == 0) +assert len(admin_project.badges.list()) == 0 # project wiki -wiki_content = 'Wiki page content' -wp = admin_project.wikis.create({'title': 'wikipage', 'content': wiki_content}) -assert(len(admin_project.wikis.list()) == 1) +wiki_content = "Wiki page content" +wp = admin_project.wikis.create({"title": "wikipage", "content": wiki_content}) +assert len(admin_project.wikis.list()) == 1 wp = admin_project.wikis.get(wp.slug) -assert(wp.content == wiki_content) +assert wp.content == wiki_content # update and delete seem broken # wp.content = 'new content' # wp.save() @@ -714,66 +746,67 @@ assert(wp.content == wiki_content) # namespaces ns = gl.namespaces.list(all=True) -assert(len(ns) != 0) -ns = gl.namespaces.list(search='root', all=True)[0] -assert(ns.kind == 'user') +assert len(ns) != 0 +ns = gl.namespaces.list(search="root", all=True)[0] +assert ns.kind == "user" # features -feat = gl.features.set('foo', 30) -assert(feat.name == 'foo') -assert(len(gl.features.list()) == 1) +feat = gl.features.set("foo", 30) +assert feat.name == "foo" +assert len(gl.features.list()) == 1 feat.delete() -assert(len(gl.features.list()) == 0) +assert len(gl.features.list()) == 0 # broadcast messages -msg = gl.broadcastmessages.create({'message': 'this is the message'}) -msg.color = '#444444' +msg = gl.broadcastmessages.create({"message": "this is the message"}) +msg.color = "#444444" msg.save() msg = gl.broadcastmessages.list(all=True)[0] -assert(msg.color == '#444444') +assert msg.color == "#444444" msg = gl.broadcastmessages.get(1) -assert(msg.color == '#444444') +assert msg.color == "#444444" msg.delete() -assert(len(gl.broadcastmessages.list()) == 0) +assert len(gl.broadcastmessages.list()) == 0 # notification settings settings = gl.notificationsettings.get() settings.level = gitlab.NOTIFICATION_LEVEL_WATCH settings.save() settings = gl.notificationsettings.get() -assert(settings.level == gitlab.NOTIFICATION_LEVEL_WATCH) +assert settings.level == gitlab.NOTIFICATION_LEVEL_WATCH # services -service = admin_project.services.get('asana') -service.api_key = 'whatever' +service = admin_project.services.get("asana") +service.api_key = "whatever" service.save() -service = admin_project.services.get('asana') -assert(service.active == True) +service = admin_project.services.get("asana") +assert service.active == True service.delete() -service = admin_project.services.get('asana') -assert(service.active == False) +service = admin_project.services.get("asana") +assert service.active == False # snippets snippets = gl.snippets.list(all=True) -assert(len(snippets) == 0) -snippet = gl.snippets.create({'title': 'snippet1', 'file_name': 'snippet1.py', - 'content': 'import gitlab'}) +assert len(snippets) == 0 +snippet = gl.snippets.create( + {"title": "snippet1", "file_name": "snippet1.py", "content": "import gitlab"} +) snippet = gl.snippets.get(snippet.id) -snippet.title = 'updated_title' +snippet.title = "updated_title" snippet.save() snippet = gl.snippets.get(snippet.id) -assert(snippet.title == 'updated_title') +assert snippet.title == "updated_title" content = snippet.content() -assert(content.decode() == 'import gitlab') +assert content.decode() == "import gitlab" -assert(snippet.user_agent_detail()['user_agent']) +assert snippet.user_agent_detail()["user_agent"] snippet.delete() snippets = gl.snippets.list(all=True) -assert(len(snippets) == 0) +assert len(snippets) == 0 # user activities -gl.user_activities.list(query_parameters={'from': '2019-01-01'}) +gl.user_activities.list(query_parameters={"from": "2019-01-01"}) # events gl.events.list() @@ -786,19 +819,18 @@ settings.throttle_authenticated_api_period_in_seconds = 3 settings.save() projects = list() for i in range(0, 20): - projects.append(gl.projects.create( - {'name': str(i) + "ok"})) + projects.append(gl.projects.create({"name": str(i) + "ok"})) error_message = None for i in range(20, 40): try: projects.append( - gl.projects.create( - {'name': str(i) + 'shouldfail'}, obey_rate_limit=False)) + gl.projects.create({"name": str(i) + "shouldfail"}, obey_rate_limit=False) + ) except gitlab.GitlabCreateError as e: error_message = e.error_message break -assert 'Retry later' in error_message +assert "Retry later" in error_message [current_project.delete() for current_project in projects] settings.throttle_authenticated_api_enabled = False settings.save() @@ -807,22 +839,23 @@ settings.save() ex = admin_project.exports.create({}) ex.refresh() count = 0 -while ex.export_status != 'finished': +while ex.export_status != "finished": time.sleep(1) ex.refresh() count += 1 if count == 10: - raise Exception('Project export taking too much time') -with open('/tmp/gitlab-export.tgz', 'wb') as f: + raise Exception("Project export taking too much time") +with open("/tmp/gitlab-export.tgz", "wb") as f: ex.download(streamed=True, action=f.write) -output = gl.projects.import_project(open('/tmp/gitlab-export.tgz', 'rb'), - 'imported_project') -project_import = gl.projects.get(output['id'], lazy=True).imports.get() +output = gl.projects.import_project( + open("/tmp/gitlab-export.tgz", "rb"), "imported_project" +) +project_import = gl.projects.get(output["id"], lazy=True).imports.get() count = 0 -while project_import.import_status != 'finished': +while project_import.import_status != "finished": time.sleep(1) project_import.refresh() count += 1 if count == 10: - raise Exception('Project import taking too much time') + raise Exception("Project import taking too much time") |