From 5055e6c0837ffd679f20e5f403fe664f3ee4ff29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:47:28 +0000 Subject: [PATCH 1/3] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/autoflake: v2.2.1 → v2.3.0](https://github.com/PyCQA/autoflake/compare/v2.2.1...v2.3.0) - [github.com/psf/black: 23.12.1 → 24.2.0](https://github.com/psf/black/compare/23.12.1...24.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f285ce6..ed2af5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: name: "reorder python imports" args: ['--application-directories=.:src', --py36-plus] - repo: https://github.com/PyCQA/autoflake - rev: v2.2.1 + rev: v2.3.0 hooks: - id: autoflake name: autoflake @@ -24,7 +24,7 @@ repos: language: python files: \.py$ - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.2.0 hooks: - id: black args: [--safe, --quiet] From c17469c4f8d78614d2e03f5a8395fa9163f626cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:48:26 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/jobs_done10/generators/jenkins.py | 85 ++++++++++++----------- src/jobs_done10/server_email_templates.py | 1 - 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/jobs_done10/generators/jenkins.py b/src/jobs_done10/generators/jenkins.py index 234b5e2..a71830e 100644 --- a/src/jobs_done10/generators/jenkins.py +++ b/src/jobs_done10/generators/jenkins.py @@ -3,6 +3,7 @@ This includes a generator, job publishers, constants and command line interface commands. """ + from collections import namedtuple from contextlib import suppress @@ -328,21 +329,21 @@ def SetNotifyGithub(self, args): notifier = self.xml[ "publishers/org.jenkinsci.plugins.github.status.GitHubCommitStatusSetter" ] - notifier[ - "commitShaSource@class" - ] = "org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource" - notifier[ - "reposSource@class" - ] = "org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource" - notifier[ - "contextSource@class" - ] = "org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource" - notifier[ - "statusResultSource@class" - ] = "org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource" - notifier[ - "statusBackrefSource@class" - ] = "org.jenkinsci.plugins.github.status.sources.BuildRefBackrefSource" + notifier["commitShaSource@class"] = ( + "org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource" + ) + notifier["reposSource@class"] = ( + "org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource" + ) + notifier["contextSource@class"] = ( + "org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource" + ) + notifier["statusResultSource@class"] = ( + "org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource" + ) + notifier["statusBackrefSource@class"] = ( + "org.jenkinsci.plugins.github.status.sources.BuildRefBackrefSource" + ) notifier["errorHandlers"] = "" def SetParameters(self, parameters): @@ -381,9 +382,9 @@ def SetTimeoutNoActivity(self, timeout): timeout_xml = self.xml[ "buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper" ] - timeout_xml[ - "strategy@class" - ] = "hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy" + timeout_xml["strategy@class"] = ( + "hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy" + ) timeout_xml["strategy/timeoutSecondsString"] = timeout timeout_xml[ "operationList/hudson.plugins.build__timeout.operations.FailOperation" @@ -471,30 +472,30 @@ def SetCoverage(self, options): # and may not be the same as the workspace root. Cobertura must be configured to generate # XML reports for this plugin to function. cobertura_publisher["coberturaReportFile"] = report_pattern - cobertura_publisher[ - "onlyStable" - ] = "false" # Include only stable builds, i.e. exclude unstable and failed ones. - cobertura_publisher[ - "failUnhealthy" - ] = "true" # fail builds if No coverage reports are found. - cobertura_publisher[ - "failUnstable" - ] = "true" # Unhealthy projects will be failed. - cobertura_publisher[ - "autoUpdateHealth" - ] = "false" # Unstable projects will be failed. - cobertura_publisher[ - "autoUpdateStability" - ] = "false" # Auto update threshold for health on successful build. - cobertura_publisher[ - "autoUpdateStability" - ] = "false" # Auto update threshold for stability on successful build. - cobertura_publisher[ - "zoomCoverageChart" - ] = "false" # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports. - cobertura_publisher[ - "maxNumberOfBuilds" - ] = "0" # Only graph the most recent N builds in the coverage chart, 0 disables the limit. + cobertura_publisher["onlyStable"] = ( + "false" # Include only stable builds, i.e. exclude unstable and failed ones. + ) + cobertura_publisher["failUnhealthy"] = ( + "true" # fail builds if No coverage reports are found. + ) + cobertura_publisher["failUnstable"] = ( + "true" # Unhealthy projects will be failed. + ) + cobertura_publisher["autoUpdateHealth"] = ( + "false" # Unstable projects will be failed. + ) + cobertura_publisher["autoUpdateStability"] = ( + "false" # Auto update threshold for health on successful build. + ) + cobertura_publisher["autoUpdateStability"] = ( + "false" # Auto update threshold for stability on successful build. + ) + cobertura_publisher["zoomCoverageChart"] = ( + "false" # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports. + ) + cobertura_publisher["maxNumberOfBuilds"] = ( + "0" # Only graph the most recent N builds in the coverage chart, 0 disables the limit. + ) cobertura_publisher["sourceEncoding"] = "UTF_8" # Encoding when showing files. def FormatMetricValue(metric): diff --git a/src/jobs_done10/server_email_templates.py b/src/jobs_done10/server_email_templates.py index 8784129..408d327 100644 --- a/src/jobs_done10/server_email_templates.py +++ b/src/jobs_done10/server_email_templates.py @@ -2,7 +2,6 @@ Email templates used by the flaks server to send an error when jobs_done fails. """ - EMAIL_PLAINTEXT = """ An error happened when processing your push! From e28d431457830b5200bb9f50499c4ceae41a2c48 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Feb 2024 08:30:11 -0300 Subject: [PATCH 3/3] Replace black/reoder-python-imports by ruff/isort --- .pre-commit-config.yaml | 36 +---- pyproject.toml | 17 ++ src/jobs_done10/cli.py | 4 +- src/jobs_done10/generators/jenkins.py | 167 +++++++------------- src/jobs_done10/job_generator.py | 17 +- src/jobs_done10/jobs_done_job.py | 13 +- src/jobs_done10/server/app.py | 42 ++--- src/jobs_done10/xml_factory/_xml_factory.py | 14 +- tests/generators/test_jenkins.py | 66 +++----- tests/server/test_server.py | 38 ++--- tests/test_job_generator.py | 12 +- tests/test_jobs_done_job.py | 13 +- 12 files changed, 146 insertions(+), 293 deletions(-) create mode 100644 pyproject.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed2af5c..19e0d3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,39 +1,15 @@ default_language_version: python: python3.10 repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 - hooks: - - id: trailing-whitespace - exclude: \.data$ - - id: end-of-file-fixer - exclude: \.data$ - - id: debug-statements -- repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 - hooks: - - id: reorder-python-imports - name: "reorder python imports" - args: ['--application-directories=.:src', --py36-plus] -- repo: https://github.com/PyCQA/autoflake - rev: v2.3.0 - hooks: - - id: autoflake - name: autoflake - args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"] - language: python - files: \.py$ -- repo: https://github.com/psf/black - rev: 24.2.0 - hooks: - - id: black - args: [--safe, --quiet] - language_version: python3 -- repo: https://github.com/asottile/blacken-docs +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.2.2" + hooks: + - id: ruff-format +- repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: [black==23.1.0] + additional_dependencies: [black==24.1.1] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..49357ae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools", + "setuptools-scm", +] + + +[tool.ruff] +src = ["src"] +line-length = 100 + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint.isort] +force-single-line = true \ No newline at end of file diff --git a/src/jobs_done10/cli.py b/src/jobs_done10/cli.py index 94674cf..81ee1ff 100644 --- a/src/jobs_done10/cli.py +++ b/src/jobs_done10/cli.py @@ -18,9 +18,7 @@ def jenkins(url, username=None, password=None): repository, jobs = GetJobsFromDirectory() publisher = JenkinsJobPublisher(repository, jobs) - new_jobs, updated_jobs, deleted_jobs = publisher.PublishToUrl( - url, username, password - ) + new_jobs, updated_jobs, deleted_jobs = publisher.PublishToUrl(url, username, password) for job in new_jobs: click.secho("NEW", fg="green", nl=False) diff --git a/src/jobs_done10/generators/jenkins.py b/src/jobs_done10/generators/jenkins.py index a71830e..fa704d6 100644 --- a/src/jobs_done10/generators/jenkins.py +++ b/src/jobs_done10/generators/jenkins.py @@ -144,9 +144,7 @@ def SetAdditionalRepositories(self, repositories): # Add additional repositories for repo in repositories: - self.SetGit( - repo["git"], git_xml=multi_scm["scms/hudson.plugins.git.GitSCM+"] - ) + self.SetGit(repo["git"], git_xml=multi_scm["scms/hudson.plugins.git.GitSCM+"]) def SetAuthToken(self, auth_token): self.xml["authToken"] = auth_token @@ -160,9 +158,7 @@ def SetBuildBatchCommands(self, build_batch_commands): self.SetBuildBatchCommands(command) else: # batch files must have \r\n as EOL or weird bugs happen (jenkins web ui add \r). - self.xml["builders/hudson.tasks.BatchFile+/command"] = command.replace( - "\n", "\r\n" - ) + self.xml["builders/hudson.tasks.BatchFile+/command"] = command.replace("\n", "\r\n") def SetBuildShellCommands(self, build_shell_commands): for command in build_shell_commands: @@ -206,9 +202,7 @@ def SetEmailNotification(self, notification_info): mailer["dontNotifyEveryUnstableBuild"] = xmls(True) else: mailer["dontNotifyEveryUnstableBuild"] = xmls(False) - mailer["sendToIndividuals"] = xmls( - notification_info.pop("notify_individuals", xmls(False)) - ) + mailer["sendToIndividuals"] = xmls(notification_info.pop("notify_individuals", xmls(False))) self._CheckUnknownOptions("email_notification", notification_info) @@ -310,9 +304,7 @@ def SetLabelExpression(self, label_expression): self.xml["assignedNode"] = label_expression def SetNotifyStash(self, args): - notifier = self.xml[ - "publishers/org.jenkinsci.plugins.stashNotifier.StashNotifier" - ] + notifier = self.xml["publishers/org.jenkinsci.plugins.stashNotifier.StashNotifier"] if isinstance(args, str): # Happens when no parameter is given, we just set the URL and assume that @@ -329,21 +321,21 @@ def SetNotifyGithub(self, args): notifier = self.xml[ "publishers/org.jenkinsci.plugins.github.status.GitHubCommitStatusSetter" ] - notifier["commitShaSource@class"] = ( - "org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource" - ) - notifier["reposSource@class"] = ( - "org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource" - ) - notifier["contextSource@class"] = ( - "org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource" - ) - notifier["statusResultSource@class"] = ( - "org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource" - ) - notifier["statusBackrefSource@class"] = ( - "org.jenkinsci.plugins.github.status.sources.BuildRefBackrefSource" - ) + notifier[ + "commitShaSource@class" + ] = "org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource" + notifier[ + "reposSource@class" + ] = "org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource" + notifier[ + "contextSource@class" + ] = "org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource" + notifier[ + "statusResultSource@class" + ] = "org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource" + notifier[ + "statusBackrefSource@class" + ] = "org.jenkinsci.plugins.github.status.sources.BuildRefBackrefSource" notifier["errorHandlers"] = "" def SetParameters(self, parameters): @@ -372,23 +364,17 @@ def SetScmPoll(self, schedule): self.xml["triggers/hudson.triggers.SCMTrigger/spec"] = schedule def SetTimeout(self, timeout): - timeout_xml = self.xml[ - "buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper" - ] + timeout_xml = self.xml["buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper"] timeout_xml["timeoutMinutes"] = str(timeout) timeout_xml["failBuild"] = xmls(True) def SetTimeoutNoActivity(self, timeout): - timeout_xml = self.xml[ - "buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper" - ] - timeout_xml["strategy@class"] = ( - "hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy" - ) - timeout_xml["strategy/timeoutSecondsString"] = timeout + timeout_xml = self.xml["buildWrappers/hudson.plugins.build__timeout.BuildTimeoutWrapper"] timeout_xml[ - "operationList/hudson.plugins.build__timeout.operations.FailOperation" - ] + "strategy@class" + ] = "hudson.plugins.build_timeout.impl.NoActivityTimeOutStrategy" + timeout_xml["strategy/timeoutSecondsString"] = timeout + timeout_xml["operationList/hudson.plugins.build__timeout.operations.FailOperation"] def SetCustomWorkspace(self, custom_workspace): self.xml["customWorkspace"] = custom_workspace @@ -396,9 +382,7 @@ def SetCustomWorkspace(self, custom_workspace): def SetSlack(self, options): room = options.get("room", "general") - properties = self.xml[ - "properties/jenkins.plugins.slack.SlackNotifier_-SlackJobProperty" - ] + properties = self.xml["properties/jenkins.plugins.slack.SlackNotifier_-SlackJobProperty"] properties["@plugin"] = "slack@1.2" properties["room"] = "#" + room properties["startNotification"] = "true" @@ -421,9 +405,7 @@ def SetNotification(self, options): "properties/com.tikal.hudson.plugins.notification.HudsonNotificationProperty" ] properties["@plugin"] = "notification@1.9" - endpoint = properties[ - "endpoints/com.tikal.hudson.plugins.notification.Endpoint" - ] + endpoint = properties["endpoints/com.tikal.hudson.plugins.notification.Endpoint"] endpoint["protocol"] = options.get("protocol", "HTTP") endpoint["format"] = options.get("format", "JSON") endpoint["url"] = options["url"] @@ -450,9 +432,7 @@ def SetConsoleColor(self, option): ansi_color_wrapper["colorMapName"] = option def SetTimestamps(self, ignored): - wrapper_xpath = ( - "buildWrappers/hudson.plugins.timestamper.TimestamperBuildWrapper" - ) + wrapper_xpath = "buildWrappers/hudson.plugins.timestamper.TimestamperBuildWrapper" wrapper = self.xml[wrapper_xpath] wrapper["@plugin"] = "timestamper@1.7.4" @@ -461,9 +441,7 @@ def SetCoverage(self, options): if not report_pattern: raise ValueError("Report pattern is required by coverage") - cobertura_publisher = self.xml[ - "publishers/hudson.plugins.cobertura.CoberturaPublisher" - ] + cobertura_publisher = self.xml["publishers/hudson.plugins.cobertura.CoberturaPublisher"] cobertura_publisher["@plugin"] = "cobertura@1.9.7" # This is a file name pattern that can be used to locate the cobertura xml report files # (for example with Maven2 use **/target/site/cobertura/coverage.xml). The path is relative @@ -472,30 +450,26 @@ def SetCoverage(self, options): # and may not be the same as the workspace root. Cobertura must be configured to generate # XML reports for this plugin to function. cobertura_publisher["coberturaReportFile"] = report_pattern - cobertura_publisher["onlyStable"] = ( - "false" # Include only stable builds, i.e. exclude unstable and failed ones. - ) - cobertura_publisher["failUnhealthy"] = ( - "true" # fail builds if No coverage reports are found. - ) - cobertura_publisher["failUnstable"] = ( - "true" # Unhealthy projects will be failed. - ) - cobertura_publisher["autoUpdateHealth"] = ( - "false" # Unstable projects will be failed. - ) - cobertura_publisher["autoUpdateStability"] = ( - "false" # Auto update threshold for health on successful build. - ) - cobertura_publisher["autoUpdateStability"] = ( - "false" # Auto update threshold for stability on successful build. - ) - cobertura_publisher["zoomCoverageChart"] = ( - "false" # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports. - ) - cobertura_publisher["maxNumberOfBuilds"] = ( - "0" # Only graph the most recent N builds in the coverage chart, 0 disables the limit. - ) + cobertura_publisher[ + "onlyStable" + ] = "false" # Include only stable builds, i.e. exclude unstable and failed ones. + cobertura_publisher[ + "failUnhealthy" + ] = "true" # fail builds if No coverage reports are found. + cobertura_publisher["failUnstable"] = "true" # Unhealthy projects will be failed. + cobertura_publisher["autoUpdateHealth"] = "false" # Unstable projects will be failed. + cobertura_publisher[ + "autoUpdateStability" + ] = "false" # Auto update threshold for health on successful build. + cobertura_publisher[ + "autoUpdateStability" + ] = "false" # Auto update threshold for stability on successful build. + cobertura_publisher[ + "zoomCoverageChart" + ] = "false" # Zoom the coverage chart and crop area below the minimum and above the maximum coverage of the past reports. + cobertura_publisher[ + "maxNumberOfBuilds" + ] = "0" # Only graph the most recent N builds in the coverage chart, 0 disables the limit. cobertura_publisher["sourceEncoding"] = "UTF_8" # Encoding when showing files. def FormatMetricValue(metric): @@ -517,9 +491,7 @@ def WriteMetrics(target_name, metrics_options, default): entry = targets["entry+"] entry["hudson.plugins.cobertura.targets.CoverageMetric"] = "CONDITIONAL" - entry["int"] = FormatMetricValue( - metrics_options.get("conditional", default) - ) + entry["int"] = FormatMetricValue(metrics_options.get("conditional", default)) healthy_options = options.get("healthy", {}) WriteMetrics("healthyTarget", healthy_options, default=80) @@ -585,9 +557,7 @@ def SetWarnings(self, options): file_parsers_xml = warnings_xml["parserConfigurations"] for parser_options in options.get("file", []): - parser_xml = file_parsers_xml[ - "hudson.plugins.warnings.ParserConfiguration+" - ] + parser_xml = file_parsers_xml["hudson.plugins.warnings.ParserConfiguration+"] parser_xml["pattern"] = parser_options.get("file_pattern") parser_xml["parserName"] = parser_options.get("parser") @@ -602,13 +572,9 @@ def SetTriggerJobs(self, options): if condition not in valid_conditions: msg = "Invalid value for condition: {!r}, expected one of {!r}" raise RuntimeError(msg.format(condition, valid_conditions)) - xml_trigger = self.xml[ - "publishers/hudson.plugins.parameterizedtrigger.BuildTrigger" - ] + xml_trigger = self.xml["publishers/hudson.plugins.parameterizedtrigger.BuildTrigger"] xml_trigger["@plugin"] = "parameterized-trigger@2.33" - xml_config = xml_trigger[ - "configs/hudson.plugins.parameterizedtrigger.BuildTriggerConfig" - ] + xml_config = xml_trigger["configs/hudson.plugins.parameterizedtrigger.BuildTriggerConfig"] parameters = options.get("parameters", []) xml_configs = xml_config["configs"] if parameters: @@ -653,8 +619,7 @@ def _SetXunit(self, xunit_type, patterns): def _CheckUnknownOptions(self, configuration_name, options_dict): if len(options_dict) > 0: raise RuntimeError( - "Received unknown %s options: %s" - % (configuration_name, list(options_dict.keys())) + "Received unknown %s options: %s" % (configuration_name, list(options_dict.keys())) ) @@ -775,9 +740,7 @@ def PublishToDirectory(self, output_directory): import os for job in self.jobs.values(): - with open( - os.path.join(output_directory, job.name), "w", encoding="utf-8" - ) as f: + with open(os.path.join(output_directory, job.name), "w", encoding="utf-8") as f: f.write(job.xml) def _GetMatchingJobs(self, jenkins_api): @@ -851,9 +814,7 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job): # Process them all until we find the SCM for the correct repository for scm in scms: - url = scm.find( - "userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url" - ).text.strip() + url = scm.find("userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url").text.strip() checked_urls.append(url) urls = [url.lower()] @@ -862,9 +823,7 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job): urls.append(url.lower() + ".git") if any(url in repo_urls for url in urls): - return scm.find( - "branches/hudson.plugins.git.BranchSpec/name" - ).text.strip() + return scm.find("branches/hudson.plugins.git.BranchSpec/name").text.strip() error_msg = [ 'Could not find SCM for repository "%s" in job "%s"' @@ -882,9 +841,7 @@ def _GetJenkinsJobBranch(self, jenkins_api, jenkins_job): raise RuntimeError("\n".join(error_msg)) -def UploadJobsFromFile( - repository, jobs_done_file_contents, url, username=None, password=None -): +def UploadJobsFromFile(repository, jobs_done_file_contents, url, username=None, password=None): """ :param repository: .. seealso:: GetJobsFromFile @@ -930,15 +887,11 @@ def GetJobsFromDirectory(directory="."): from subprocess import check_output url = ( - check_output( - "git config --local --get remote.origin.url", shell=True, cwd=directory - ) + check_output("git config --local --get remote.origin.url", shell=True, cwd=directory) .strip() .decode("UTF-8") ) - branches = ( - check_output("git branch", shell=True, cwd=directory).strip().decode("UTF-8") - ) + branches = check_output("git branch", shell=True, cwd=directory).strip().decode("UTF-8") for branch in branches.splitlines(): branch = branch.strip() if "*" in branch: # Current branch diff --git a/src/jobs_done10/job_generator.py b/src/jobs_done10/job_generator.py index a68e789..6c7b52e 100644 --- a/src/jobs_done10/job_generator.py +++ b/src/jobs_done10/job_generator.py @@ -82,9 +82,7 @@ def Configure(cls, generator, jobs_done_job): try: generator_function = getattr(generator, generator_function_name) except AttributeError: - raise JobGeneratorAttributeError( - generator, generator_function_name, option - ) + raise JobGeneratorAttributeError(generator, generator_function_name, option) generator_function(option_value) @@ -97,14 +95,11 @@ class JobGeneratorAttributeError(AttributeError): """ def __init__(self, generator, attribute, jobs_done_job_option): - message = ( - '%s "%s" cannot handle option "%s" (could not find function "%s").' - % ( - IJobGenerator.__name__, - generator.__class__.__name__, - jobs_done_job_option, - attribute, - ) + message = '%s "%s" cannot handle option "%s" (could not find function "%s").' % ( + IJobGenerator.__name__, + generator.__class__.__name__, + jobs_done_job_option, + attribute, ) AttributeError.__init__(self, message) diff --git a/src/jobs_done10/jobs_done_job.py b/src/jobs_done10/jobs_done_job.py index 4871c08..bca4061 100644 --- a/src/jobs_done10/jobs_done_job.py +++ b/src/jobs_done10/jobs_done_job.py @@ -355,9 +355,7 @@ def CreateFromYAML(cls, yaml_contents, repository): # Do not create a job if there is no match for this branch branch_patterns = jobs_done_job.branch_patterns or [".*"] - if not any( - [re.match(pattern, repository.branch) for pattern in branch_patterns] - ): + if not any([re.match(pattern, repository.branch) for pattern in branch_patterns]): continue jobs_done_jobs.append(jobs_done_job) @@ -397,9 +395,9 @@ def _CheckAmbiguousConditions( conditions = set(conditions) def _IsAmbiguous(): - return not previous_conditions.issubset( - conditions - ) and not conditions.issubset(previous_conditions) + return not previous_conditions.issubset(conditions) and not conditions.issubset( + previous_conditions + ) if _IsAmbiguous(): import textwrap @@ -630,8 +628,7 @@ def __init__(self, option): ValueError.__init__( self, - 'Condition "%s" can never be matched based on possible matrix rows.' - % option, + 'Condition "%s" can never be matched based on possible matrix rows.' % option, ) diff --git a/src/jobs_done10/server/app.py b/src/jobs_done10/server/app.py index ac4fc3f..c9eb4bf 100644 --- a/src/jobs_done10/server/app.py +++ b/src/jobs_done10/server/app.py @@ -63,7 +63,7 @@ def _handle_end_point( parse_request_callback: Callable[ [dict[str, Any], dict[str, Any], bytes, dict[str, str]], Iterator["JobsDoneRequest"], - ] + ], ) -> str | tuple[str, int]: """Common handling for the jobs-done end-point.""" request = flask.request @@ -76,25 +76,21 @@ def _handle_end_point( payload = request.json json_payload_dump = ( - json.dumps(dict(payload), indent=2, sort_keys=True) - if payload is not None - else "None" + json.dumps(dict(payload), indent=2, sort_keys=True) if payload is not None else "None" ) app.logger.info( "\n" + f"Received {request}\n" - + f"Headers:\n" + + "Headers:\n" + json.dumps(dict(request.headers), indent=2, sort_keys=True) + "\n" - + f"Payload (JSON):\n" + + "Payload (JSON):\n" + json_payload_dump ) jobs_done_requests = [] try: jobs_done_requests = list( - parse_request_callback( - request.headers, payload, request.data, dict(os.environ) - ) + parse_request_callback(request.headers, payload, request.data, dict(os.environ)) ) app.logger.info(f"Parsed {len(jobs_done_requests)} jobs done requests:") for jdr in jobs_done_requests: @@ -106,9 +102,7 @@ def _handle_end_point( app.logger.exception(f"Header signature does not match: {e}") return str(e), HTTPStatus.FORBIDDEN except Exception: - err_message = _process_jobs_done_error( - request.headers, request.data, jobs_done_requests - ) + err_message = _process_jobs_done_error(request.headers, request.data, jobs_done_requests) app.logger.exception("Unexpected exception") return err_message, HTTPStatus.INTERNAL_SERVER_ERROR @@ -261,9 +255,7 @@ def verify_github_signature(headers: dict[str, Any], data: bytes, secret: str) - hash = algorithm.digest().hex() computed_signature = f"sha256={hash}" if not hmac.compare_digest(header_signature, computed_signature): - raise SignatureVerificationError( - f"Computed signature does not match the one in the header" - ) + raise SignatureVerificationError("Computed signature does not match the one in the header") def _process_jobs_done_request(jobs_done_requests: Iterable[JobsDoneRequest]) -> str: @@ -412,25 +404,17 @@ def send_email_with_error( def abbrev_commit(c: str | None) -> str: return c[:7] if c is not None else "(no commit)" - changes_msg = ", ".join( - f"{x.branch} @ {abbrev_commit(x.commit)}" for x in jobs_done_requests - ) - subject = ( - f"JobsDone failure during push to {owner_name}/{repo_name} ({changes_msg})" - ) + changes_msg = ", ".join(f"{x.branch} @ {abbrev_commit(x.commit)}" for x in jobs_done_requests) + subject = f"JobsDone failure during push to {owner_name}/{repo_name} ({changes_msg})" pretty_json = pprint.pformat(payload) - plain = EMAIL_PLAINTEXT.format( - error_traceback=error_traceback, pretty_json=pretty_json - ) + plain = EMAIL_PLAINTEXT.format(error_traceback=error_traceback, pretty_json=pretty_json) style = "colorful" html = EMAIL_HTML.format( error_traceback_html=highlight( error_traceback, PythonTracebackLexer(), HtmlFormatter(style=style) ), - pretty_json_html=highlight( - pretty_json, JsonLexer(), HtmlFormatter(style=style) - ), + pretty_json_html=highlight(pretty_json, JsonLexer(), HtmlFormatter(style=style)), ) msg = MIMEMultipart() @@ -441,9 +425,7 @@ def abbrev_commit(c: str | None) -> str: msg.attach(MIMEText(html, "html")) context = ssl.create_default_context() - with smtplib.SMTP( - os.environ["JD_EMAIL_SERVER"], int(os.environ["JD_EMAIL_PORT"]) - ) as server: + with smtplib.SMTP(os.environ["JD_EMAIL_SERVER"], int(os.environ["JD_EMAIL_PORT"])) as server: server.starttls(context=context) server.login(os.environ["JD_EMAIL_USER"], os.environ["JD_EMAIL_PASSWORD"]) server.sendmail(os.environ["JD_EMAIL_USER"], pusher_email, msg=msg.as_string()) diff --git a/src/jobs_done10/xml_factory/_xml_factory.py b/src/jobs_done10/xml_factory/_xml_factory.py index e31d2a9..9294a5c 100644 --- a/src/jobs_done10/xml_factory/_xml_factory.py +++ b/src/jobs_done10/xml_factory/_xml_factory.py @@ -32,9 +32,7 @@ def __init__(self, root_element): elif isinstance(root_element, ElementTree.Element): self.root = root_element else: - raise TypeError( - "Unknown root_element parameter type: %s" % type(root_element) - ) + raise TypeError("Unknown root_element parameter type: %s" % type(root_element)) def __setitem__(self, name, value): """ @@ -169,22 +167,18 @@ def _elem2list(elem, return_children=False): # create meaningful lists scalar = False try: - if ( - elem[0].tag != elem[1].tag - ): # [{a: 1}, {b: 2}, {c: 3}] => {a: 1, b: 2, c: 3} + if elem[0].tag != elem[1].tag: # [{a: 1}, {b: 2}, {c: 3}] => {a: 1, b: 2, c: 3} cur = dict(chain(*(d.items() for d in cur))) else: scalar = True - except Exception as e: # [{a: 1}, {a: 2}, {a: 3}] => {a: [1, 2, 3]} + except Exception: # [{a: 1}, {a: 2}, {a: 3}] => {a: [1, 2, 3]} scalar = True if scalar: if len(cur) > 1: cur = { elem[0].tag: [ - list(e.values())[0] - for e in cur - if list(e.values())[0] is not None + list(e.values())[0] for e in cur if list(e.values())[0] is not None ] } else: diff --git a/tests/generators/test_jenkins.py b/tests/generators/test_jenkins.py index c724ceb..8d31b08 100644 --- a/tests/generators/test_jenkins.py +++ b/tests/generators/test_jenkins.py @@ -158,15 +158,17 @@ def testTimeoutNoActivity(self, file_regression): ) def testCustomWorkspace(self, file_regression): - self.Check( - file_regression, - yaml_contents=dedent( - """ + ( + self.Check( + file_regression, + yaml_contents=dedent( + """ custom_workspace: workspace/WS """ + ), + boundary_tags="project", ), - boundary_tags="project", - ), + ) def testAuthToken(self, file_regression): self.Check( @@ -226,9 +228,7 @@ def testMultipleTestResults(self, file_regression): ("build_python_commands", "hudson.plugins.python.Python"), ], ) - def testBuildCommandsExpandNestedLists( - self, file_regression, job_done_key, xml_key - ): + def testBuildCommandsExpandNestedLists(self, file_regression, job_done_key, xml_key): # sanity: no ref self.Check( file_regression, @@ -821,9 +821,7 @@ def testTriggerJobParameters(self, file_regression): ) def testAnsiColorUnknowOption(self): - with pytest.raises( - RuntimeError, match="Received unknown console_color option." - ): + with pytest.raises(RuntimeError, match="Received unknown console_color option."): self._GenerateJob( yaml_contents=dedent( """ @@ -909,9 +907,7 @@ def testCoverage(self, scenario, file_regression): ) def testCoverageFailWhenMissingReportPattern(self): - with pytest.raises( - ValueError, match="Report pattern is required by coverage" - ) as e: + with pytest.raises(ValueError, match="Report pattern is required by coverage") as e: self._GenerateJob( yaml_contents=dedent( r""" @@ -949,9 +945,7 @@ def testWarningsEmpty(self): self._GenerateJob(yaml_contents="warnings: {}") def testWarningsWrongOption(self): - with pytest.raises( - ValueError, match="Received unknown 'warnings' options: zucchini." - ): + with pytest.raises(ValueError, match="Received unknown 'warnings' options: zucchini."): self._GenerateJob( yaml_contents=dedent( """\ @@ -983,9 +977,7 @@ def Check(self, file_regression, yaml_contents, boundary_tags, *, basename=None) new_content = self.ExtractTagBoundaries(jenkins_job.xml, boundary_tags) file_regression.check(new_content, extension=".xml", basename=basename) - def ExtractTagBoundaries( - self, text: str, boundary_tags: str | tuple[str, ...] - ) -> str: + def ExtractTagBoundaries(self, text: str, boundary_tags: str | tuple[str, ...]) -> str: """Given a XML text, extract the lines containing between the given tags (including them).""" if isinstance(boundary_tags, str): boundary_tags = (boundary_tags,) @@ -1130,14 +1122,8 @@ def testPublishToUrl(self, monkeypatch): == mock_jenkins.NEW_JOBS == {"space-milky_way-venus", "space-milky_way-jupiter"} ) - assert ( - set(updated_jobs) - == mock_jenkins.UPDATED_JOBS - == {"space-milky_way-mercury"} - ) - assert ( - set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"} - ) + assert set(updated_jobs) == mock_jenkins.UPDATED_JOBS == {"space-milky_way-mercury"} + assert set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"} def testPublishToUrlProxyErrorOnce(self, monkeypatch): # Do not actually sleep during tests @@ -1155,14 +1141,8 @@ def testPublishToUrlProxyErrorOnce(self, monkeypatch): == mock_jenkins.NEW_JOBS == {"space-milky_way-venus", "space-milky_way-jupiter"} ) - assert ( - set(updated_jobs) - == mock_jenkins.UPDATED_JOBS - == {"space-milky_way-mercury"} - ) - assert ( - set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"} - ) + assert set(updated_jobs) == mock_jenkins.UPDATED_JOBS == {"space-milky_way-mercury"} + assert set(deleted_jobs) == mock_jenkins.DELETED_JOBS == {"space-milky_way-saturn"} def testPublishToUrlProxyErrorTooManyTimes(self, monkeypatch): # Do not actually sleep during tests @@ -1203,15 +1183,9 @@ def testPublishToUrl2(self, monkeypatch): def _GetPublisher(self): repository = Repository(url="http://server/space.git", branch="milky_way") jobs = [ - JenkinsJob( - name="space-milky_way-jupiter", xml="jupiter", repository=repository - ), - JenkinsJob( - name="space-milky_way-mercury", xml="mercury", repository=repository - ), - JenkinsJob( - name="space-milky_way-venus", xml="venus", repository=repository - ), + JenkinsJob(name="space-milky_way-jupiter", xml="jupiter", repository=repository), + JenkinsJob(name="space-milky_way-mercury", xml="mercury", repository=repository), + JenkinsJob(name="space-milky_way-venus", xml="venus", repository=repository), ] return JenkinsJobPublisher(repository, jobs) diff --git a/tests/server/test_server.py b/tests/server/test_server.py index 9d0e407..58e63d2 100644 --- a/tests/server/test_server.py +++ b/tests/server/test_server.py @@ -69,9 +69,7 @@ def stash_repo_info_data_(datadir: Path) -> dict[str, Any]: Taken from manually doing the query on our live server. """ - return json.loads( - datadir.joinpath("stash-repo-info.json").read_text(encoding="UTF-8") - ) + return json.loads(datadir.joinpath("stash-repo-info.json").read_text(encoding="UTF-8")) @pytest.fixture(name="github_post_data") @@ -95,9 +93,7 @@ def github_post_headers_(datadir: Path) -> dict[str, Any]: Same docs as in github_post_payload. """ - return json.loads( - datadir.joinpath("github-post.headers.json").read_text(encoding="UTF-8") - ) + return json.loads(datadir.joinpath("github-post.headers.json").read_text(encoding="UTF-8")) @pytest.fixture(name="github_post_del_branch_data") @@ -114,9 +110,7 @@ def github_post_del_branch_headers_(datadir: Path) -> dict[str, Any]: Same as git_hub_post_headers, but for post about a branch being deleted. """ return json.loads( - datadir.joinpath("github-post-del-branch.headers.json").read_text( - encoding="UTF-8" - ) + datadir.joinpath("github-post-del-branch.headers.json").read_text(encoding="UTF-8") ) @@ -162,10 +156,7 @@ def mock_github_repo_requests( # is encoded as "{username}:{password}" as a base64 string. username = "" basic_auth = b64encode(f"{username}:{token}".encode()) - assert ( - history.headers.get("Authorization") - == f"Basic {basic_auth.decode('ASCII')}" - ) + assert history.headers.get("Authorization") == f"Basic {basic_auth.decode('ASCII')}" @pytest.mark.parametrize("post_url", ["/", "/stash"]) @@ -235,12 +226,8 @@ def test_github_post( ) file_contents = "github jobs done contents" - with mock_github_repo_requests( - file_contents, status_code=200, settings=dict(os.environ) - ): - response = client.post( - "/github", data=github_post_data, headers=github_post_headers - ) + with mock_github_repo_requests(file_contents, status_code=200, settings=dict(os.environ)): + response = client.post("/github", data=github_post_data, headers=github_post_headers) assert response.status_code == 200 assert response.mimetype == "text/html" @@ -262,9 +249,7 @@ def test_github_post( args, kwargs = upload_mock.call_args assert args == () assert kwargs == dict( - repository=Repository( - "git@github.com:ESSS/test-webhooks.git", "fb-add-jobs-done" - ), + repository=Repository("git@github.com:ESSS/test-webhooks.git", "fb-add-jobs-done"), jobs_done_file_contents=file_contents, url="https://example.com/jenkins", username="jenkins_user", @@ -317,10 +302,7 @@ def test_error_handling( assert response.status_code == 500 assert response.mimetype == "text/html" obtained_message = response.data.decode("UTF-8") - assert ( - "ERROR processing request: " - in obtained_message - ) + assert "ERROR processing request: " in obtained_message assert "JSON data:" in obtained_message assert "Traceback (most recent call last):" in obtained_message assert "Email sent to bugreport+jenkins@esss.co" in obtained_message @@ -418,9 +400,7 @@ def test_verify_github_signature( github_post_headers, github_post_data, os.environ["JD_GH_WEBHOOK_SECRET"] ) - with pytest.raises( - SignatureVerificationError, match="Computed signature does not match.*" - ): + with pytest.raises(SignatureVerificationError, match="Computed signature does not match.*"): tampered_data = github_post_data + b"\n" verify_github_signature( github_post_headers, tampered_data, os.environ["JD_GH_WEBHOOK_SECRET"] diff --git a/tests/test_job_generator.py b/tests/test_job_generator.py index 601beac..128a8c3 100644 --- a/tests/test_job_generator.py +++ b/tests/test_job_generator.py @@ -31,16 +31,12 @@ def Reset(self): generator = MyGenerator() # Test basic calls - with ExpectedCalls( - generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=0 - ): + with ExpectedCalls(generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=0): JobGeneratorConfigurator.Configure(generator, jobs_done_job) # Set some more values to jobs_done_job, and make sure it is called jobs_done_job.build_batch_commands = ["command"] - with ExpectedCalls( - generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=1 - ): + with ExpectedCalls(generator, Reset=1, SetRepository=1, SetMatrix=1, SetBuildBatchCommands=1): JobGeneratorConfigurator.Configure(generator, jobs_done_job) # Try calling a missing option @@ -80,9 +76,7 @@ def Wrapped(*args, **kwargs): # __exit__ try: for (_, function_name), (obtained, expected, _) in list(calls.items()): - assert ( - obtained == expected - ), 'Expected "%d" calls for function "%s", but got "%d"' % ( + assert obtained == expected, 'Expected "%d" calls for function "%s", but got "%d"' % ( expected, function_name, obtained, diff --git a/tests/test_jobs_done_job.py b/tests/test_jobs_done_job.py index d6152b2..cf7fed7 100644 --- a/tests/test_jobs_done_job.py +++ b/tests/test_jobs_done_job.py @@ -107,9 +107,7 @@ def CheckCommonValues(jobs_done_job): def testExclude(): - key = lambda job: ":".join( - j + "-" + job.matrix_row[j] for j in sorted(job.matrix_row.keys()) - ) + key = lambda job: ":".join(j + "-" + job.matrix_row[j] for j in sorted(job.matrix_row.keys())) # Base case ------------------------------------------------------------------------------------ yaml_contents = dedent( """ @@ -409,10 +407,7 @@ def testIgnoreAmbiguousConditionsWithEqualValues(): """ ) for jd_file in JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY): - if ( - jd_file.matrix_row["platform"] == "linux" - or jd_file.matrix_row["slave"] == "slave2" - ): + if jd_file.matrix_row["platform"] == "linux" or jd_file.matrix_row["slave"] == "slave2": assert jd_file.display_name == "Foo job" else: assert jd_file.display_name is None @@ -541,9 +536,7 @@ def testTypeChecking(): JobsDoneJob.CreateFromYAML(yaml_contents, repository=_REPOSITORY) assert e.value.option_name == "build_batch_commands" - assert e.value.accepted_types == [ - JobsDoneJob.PARSEABLE_OPTIONS["build_batch_commands"] - ] + assert e.value.accepted_types == [JobsDoneJob.PARSEABLE_OPTIONS["build_batch_commands"]] assert e.value.obtained_type == str assert e.value.option_value == "string item"