qontract-reconcile 0.10.1rc884__py3-none-any.whl → 0.10.1rc886__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. {qontract_reconcile-0.10.1rc884.dist-info → qontract_reconcile-0.10.1rc886.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc884.dist-info → qontract_reconcile-0.10.1rc886.dist-info}/RECORD +279 -276
  3. reconcile/acs_rbac.py +1 -2
  4. reconcile/aus/advanced_upgrade_service.py +14 -14
  5. reconcile/aus/aus_label_source.py +1 -2
  6. reconcile/aus/base.py +23 -26
  7. reconcile/aus/cluster_version_data.py +4 -4
  8. reconcile/aus/models.py +2 -3
  9. reconcile/aus/version_gate_approver.py +2 -6
  10. reconcile/aus/version_gates/__init__.py +1 -3
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +2 -3
  12. reconcile/aws_account_manager/integration.py +2 -2
  13. reconcile/aws_ami_cleanup/integration.py +3 -4
  14. reconcile/aws_iam_password_reset.py +2 -5
  15. reconcile/aws_version_sync/integration.py +2 -2
  16. reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
  17. reconcile/change_owners/approver.py +4 -5
  18. reconcile/change_owners/bundle.py +20 -22
  19. reconcile/change_owners/change_types.py +23 -24
  20. reconcile/change_owners/changes.py +13 -16
  21. reconcile/change_owners/decision.py +2 -5
  22. reconcile/change_owners/diff.py +11 -15
  23. reconcile/change_owners/self_service_roles.py +1 -2
  24. reconcile/change_owners/tester.py +7 -10
  25. reconcile/checkpoint.py +2 -5
  26. reconcile/cli.py +9 -12
  27. reconcile/closedbox_endpoint_monitoring_base.py +8 -11
  28. reconcile/cluster_deployment_mapper.py +2 -5
  29. reconcile/cna/assets/asset.py +4 -7
  30. reconcile/cna/assets/null.py +2 -5
  31. reconcile/cna/integration.py +2 -3
  32. reconcile/cna/state.py +2 -5
  33. reconcile/dashdotdb_base.py +8 -11
  34. reconcile/dashdotdb_cso.py +3 -6
  35. reconcile/dashdotdb_dora.py +10 -14
  36. reconcile/dashdotdb_dvo.py +10 -13
  37. reconcile/dashdotdb_slo.py +5 -8
  38. reconcile/database_access_manager.py +5 -6
  39. reconcile/dynatrace_token_provider/integration.py +2 -5
  40. reconcile/external_resources/integration.py +1 -1
  41. reconcile/external_resources/manager.py +4 -4
  42. reconcile/external_resources/model.py +3 -3
  43. reconcile/external_resources/secrets_sync.py +5 -5
  44. reconcile/external_resources/state.py +5 -5
  45. reconcile/gabi_authorized_users.py +3 -6
  46. reconcile/gcr_mirror.py +1 -1
  47. reconcile/github_org.py +1 -3
  48. reconcile/github_repo_invites.py +2 -5
  49. reconcile/gitlab_housekeeping.py +7 -11
  50. reconcile/gitlab_labeler.py +1 -2
  51. reconcile/gitlab_members.py +2 -5
  52. reconcile/gitlab_permissions.py +1 -3
  53. reconcile/glitchtip/integration.py +2 -5
  54. reconcile/glitchtip_project_alerts/integration.py +3 -6
  55. reconcile/glitchtip_project_dsn/integration.py +4 -7
  56. reconcile/integrations_manager.py +5 -8
  57. reconcile/jenkins/types.py +5 -6
  58. reconcile/jenkins_job_builder.py +9 -12
  59. reconcile/jenkins_roles.py +1 -1
  60. reconcile/jira_watcher.py +2 -2
  61. reconcile/ldap_groups/integration.py +2 -5
  62. reconcile/ocm/types.py +21 -26
  63. reconcile/ocm_addons_upgrade_tests_trigger.py +3 -6
  64. reconcile/ocm_clusters.py +8 -8
  65. reconcile/ocm_internal_notifications/integration.py +1 -2
  66. reconcile/ocm_labels/integration.py +2 -5
  67. reconcile/ocm_machine_pools.py +11 -15
  68. reconcile/ocm_upgrade_scheduler_org_updater.py +2 -5
  69. reconcile/openshift_base.py +27 -29
  70. reconcile/openshift_groups.py +15 -20
  71. reconcile/openshift_namespace_labels.py +8 -14
  72. reconcile/openshift_namespaces.py +5 -8
  73. reconcile/openshift_network_policies.py +2 -4
  74. reconcile/openshift_resources_base.py +19 -29
  75. reconcile/openshift_saas_deploy.py +9 -10
  76. reconcile/openshift_saas_deploy_change_tester.py +7 -10
  77. reconcile/openshift_saas_deploy_trigger_base.py +4 -7
  78. reconcile/openshift_saas_deploy_trigger_cleaner.py +5 -8
  79. reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
  80. reconcile/openshift_saas_deploy_trigger_images.py +1 -2
  81. reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
  82. reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
  83. reconcile/openshift_tekton_resources.py +7 -11
  84. reconcile/openshift_upgrade_watcher.py +10 -13
  85. reconcile/openshift_users.py +8 -11
  86. reconcile/oum/base.py +3 -4
  87. reconcile/oum/labelset.py +1 -2
  88. reconcile/oum/metrics.py +2 -2
  89. reconcile/oum/models.py +1 -2
  90. reconcile/oum/standalone.py +2 -3
  91. reconcile/prometheus_rules_tester/integration.py +6 -9
  92. reconcile/quay_membership.py +1 -2
  93. reconcile/quay_mirror.py +12 -13
  94. reconcile/quay_mirror_org.py +10 -10
  95. reconcile/queries.py +4 -7
  96. reconcile/resource_scraper.py +3 -4
  97. reconcile/rhidp/common.py +2 -2
  98. reconcile/saas_auto_promotions_manager/integration.py +5 -6
  99. reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +1 -2
  100. reconcile/saas_auto_promotions_manager/publisher.py +5 -6
  101. reconcile/saas_auto_promotions_manager/subscriber.py +3 -4
  102. reconcile/saas_file_validator.py +2 -5
  103. reconcile/signalfx_endpoint_monitoring.py +2 -5
  104. reconcile/skupper_network/integration.py +3 -6
  105. reconcile/skupper_network/models.py +3 -5
  106. reconcile/slack_base.py +4 -7
  107. reconcile/slack_usergroups.py +15 -17
  108. reconcile/sql_query.py +5 -9
  109. reconcile/status_board.py +4 -5
  110. reconcile/statuspage/atlassian.py +14 -15
  111. reconcile/statuspage/integrations/maintenances.py +3 -3
  112. reconcile/statuspage/page.py +8 -8
  113. reconcile/statuspage/state.py +4 -5
  114. reconcile/statuspage/status.py +7 -8
  115. reconcile/templating/lib/rendering.py +8 -8
  116. reconcile/templating/renderer.py +10 -11
  117. reconcile/templating/validator.py +4 -4
  118. reconcile/terraform_aws_route53.py +3 -6
  119. reconcile/terraform_cloudflare_dns.py +9 -12
  120. reconcile/terraform_cloudflare_resources.py +9 -11
  121. reconcile/terraform_cloudflare_users.py +8 -11
  122. reconcile/terraform_init/integration.py +2 -2
  123. reconcile/terraform_repo.py +11 -14
  124. reconcile/terraform_resources.py +20 -21
  125. reconcile/terraform_tgw_attachments.py +32 -36
  126. reconcile/terraform_users.py +6 -7
  127. reconcile/terraform_vpc_resources/integration.py +5 -5
  128. reconcile/test/conftest.py +7 -10
  129. reconcile/test/fixtures.py +1 -1
  130. reconcile/test/saas_auto_promotions_manager/conftest.py +2 -2
  131. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +2 -2
  132. reconcile/test/test_database_access_manager.py +3 -6
  133. reconcile/test/test_gitlab_labeler.py +2 -5
  134. reconcile/test/test_jump_host.py +5 -8
  135. reconcile/test/test_ocm_machine_pools.py +1 -4
  136. reconcile/test/test_openshift_base.py +3 -6
  137. reconcile/test/test_openshift_cluster_bots.py +5 -5
  138. reconcile/test/test_openshift_namespace_labels.py +2 -3
  139. reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +2 -2
  140. reconcile/test/test_saasherder.py +9 -12
  141. reconcile/test/test_slack_base.py +4 -6
  142. reconcile/test/test_status_board.py +4 -7
  143. reconcile/test/test_terraform_tgw_attachments.py +14 -20
  144. reconcile/typed_queries/alerting_services_settings.py +1 -2
  145. reconcile/typed_queries/app_interface_custom_messages.py +2 -3
  146. reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +1 -3
  147. reconcile/typed_queries/app_interface_repo_url.py +1 -2
  148. reconcile/typed_queries/app_interface_state_settings.py +1 -3
  149. reconcile/typed_queries/app_interface_vault_settings.py +1 -2
  150. reconcile/typed_queries/aws_vpc_requests.py +1 -3
  151. reconcile/typed_queries/aws_vpcs.py +1 -3
  152. reconcile/typed_queries/clusters.py +2 -4
  153. reconcile/typed_queries/clusters_minimal.py +1 -3
  154. reconcile/typed_queries/clusters_with_dms.py +1 -3
  155. reconcile/typed_queries/external_resources.py +3 -4
  156. reconcile/typed_queries/pagerduty_instances.py +1 -2
  157. reconcile/typed_queries/repos.py +2 -3
  158. reconcile/typed_queries/reserved_networks.py +1 -3
  159. reconcile/typed_queries/saas_files.py +49 -59
  160. reconcile/typed_queries/slo_documents.py +1 -3
  161. reconcile/typed_queries/status_board.py +3 -7
  162. reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
  163. reconcile/typed_queries/terraform_namespaces.py +1 -2
  164. reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +1 -3
  165. reconcile/utils/acs/base.py +2 -3
  166. reconcile/utils/acs/notifiers.py +3 -3
  167. reconcile/utils/acs/policies.py +3 -3
  168. reconcile/utils/aggregated_list.py +1 -1
  169. reconcile/utils/amtool.py +1 -2
  170. reconcile/utils/aws_api.py +28 -31
  171. reconcile/utils/binary.py +1 -3
  172. reconcile/utils/clusterhealth/providerbase.py +1 -2
  173. reconcile/utils/clusterhealth/telemeter.py +2 -2
  174. reconcile/utils/deadmanssnitch_api.py +1 -2
  175. reconcile/utils/disabled_integrations.py +4 -6
  176. reconcile/utils/environ.py +1 -1
  177. reconcile/utils/expiration.py +3 -7
  178. reconcile/utils/external_resource_spec.py +3 -4
  179. reconcile/utils/external_resources.py +4 -7
  180. reconcile/utils/filtering.py +1 -2
  181. reconcile/utils/git.py +3 -9
  182. reconcile/utils/git_secrets.py +5 -5
  183. reconcile/utils/github_api.py +5 -9
  184. reconcile/utils/gitlab_api.py +2 -3
  185. reconcile/utils/glitchtip/client.py +2 -4
  186. reconcile/utils/glitchtip/models.py +8 -11
  187. reconcile/utils/gql.py +26 -35
  188. reconcile/utils/grouping.py +1 -3
  189. reconcile/utils/imap_client.py +2 -5
  190. reconcile/utils/internal_groups/client.py +1 -2
  191. reconcile/utils/internal_groups/models.py +8 -9
  192. reconcile/utils/jenkins_api.py +4 -4
  193. reconcile/utils/jinja2/extensions.py +1 -1
  194. reconcile/utils/jinja2/filters.py +4 -4
  195. reconcile/utils/jinja2/utils.py +16 -16
  196. reconcile/utils/jira_client.py +10 -11
  197. reconcile/utils/jjb_client.py +14 -17
  198. reconcile/utils/jobcontroller/controller.py +5 -5
  199. reconcile/utils/jobcontroller/models.py +2 -2
  200. reconcile/utils/jsonpath.py +4 -5
  201. reconcile/utils/jump_host.py +7 -8
  202. reconcile/utils/keycloak.py +3 -7
  203. reconcile/utils/ldap_client.py +2 -3
  204. reconcile/utils/lean_terraform_client.py +13 -17
  205. reconcile/utils/membershipsources/app_interface_resolver.py +1 -1
  206. reconcile/utils/membershipsources/models.py +19 -22
  207. reconcile/utils/metrics.py +13 -15
  208. reconcile/utils/mr/base.py +7 -11
  209. reconcile/utils/mr/glitchtip_access_reporter.py +2 -2
  210. reconcile/utils/mr/notificator.py +1 -2
  211. reconcile/utils/oc.py +32 -38
  212. reconcile/utils/oc_connection_parameters.py +24 -25
  213. reconcile/utils/oc_filters.py +2 -3
  214. reconcile/utils/oc_map.py +9 -15
  215. reconcile/utils/ocm/addons.py +7 -10
  216. reconcile/utils/ocm/base.py +38 -39
  217. reconcile/utils/ocm/clusters.py +6 -9
  218. reconcile/utils/ocm/label_sources.py +1 -2
  219. reconcile/utils/ocm/labels.py +3 -6
  220. reconcile/utils/ocm/ocm.py +11 -14
  221. reconcile/utils/ocm/products.py +1 -3
  222. reconcile/utils/ocm/search_filters.py +16 -17
  223. reconcile/utils/ocm/service_log.py +2 -3
  224. reconcile/utils/ocm/sre_capability_labels.py +4 -8
  225. reconcile/utils/ocm/subscriptions.py +1 -3
  226. reconcile/utils/ocm/syncsets.py +2 -4
  227. reconcile/utils/ocm/upgrades.py +5 -9
  228. reconcile/utils/ocm_base_client.py +13 -16
  229. reconcile/utils/openshift_resource.py +5 -11
  230. reconcile/utils/output.py +2 -3
  231. reconcile/utils/pagerduty_api.py +4 -5
  232. reconcile/utils/prometheus.py +2 -2
  233. reconcile/utils/promotion_state.py +4 -5
  234. reconcile/utils/promtool.py +2 -8
  235. reconcile/utils/quay_api.py +12 -22
  236. reconcile/utils/raw_github_api.py +3 -5
  237. reconcile/utils/rosa/rosa_cli.py +6 -6
  238. reconcile/utils/rosa/session.py +6 -7
  239. reconcile/utils/runtime/desired_state_diff.py +3 -8
  240. reconcile/utils/runtime/environment.py +4 -7
  241. reconcile/utils/runtime/integration.py +4 -4
  242. reconcile/utils/runtime/meta.py +1 -2
  243. reconcile/utils/runtime/runner.py +7 -10
  244. reconcile/utils/runtime/sharding.py +22 -27
  245. reconcile/utils/saasherder/interfaces.py +63 -69
  246. reconcile/utils/saasherder/models.py +30 -35
  247. reconcile/utils/saasherder/saasherder.py +37 -53
  248. reconcile/utils/secret_reader.py +17 -19
  249. reconcile/utils/slack_api.py +15 -17
  250. reconcile/utils/smtp_client.py +1 -2
  251. reconcile/utils/sqs_gateway.py +1 -3
  252. reconcile/utils/state.py +1 -2
  253. reconcile/utils/terraform/config_client.py +4 -5
  254. reconcile/utils/terraform_client.py +3 -8
  255. reconcile/utils/terrascript/cloudflare_client.py +4 -10
  256. reconcile/utils/terrascript/cloudflare_resources.py +10 -13
  257. reconcile/utils/terrascript/models.py +2 -3
  258. reconcile/utils/terrascript/resources.py +1 -2
  259. reconcile/utils/terrascript_aws_client.py +30 -38
  260. reconcile/utils/unleash/client.py +4 -7
  261. reconcile/utils/unleash/server.py +2 -2
  262. reconcile/utils/vault.py +8 -11
  263. reconcile/utils/vaultsecretref.py +2 -3
  264. reconcile/utils/vcs.py +7 -8
  265. reconcile/vault_replication.py +4 -8
  266. reconcile/vpc_peerings_validator.py +4 -9
  267. release/version.py +6 -7
  268. tools/app_interface_reporter.py +2 -2
  269. tools/cli_commands/gpg_encrypt.py +3 -6
  270. tools/cli_commands/systems_and_tools.py +4 -7
  271. tools/qontract_cli.py +31 -17
  272. tools/saas_promotion_state/__init__.py +0 -0
  273. tools/saas_promotion_state/saas_promotion_state.py +72 -0
  274. tools/template_validation.py +1 -1
  275. tools/test/conftest.py +45 -6
  276. tools/test/test_saas_promotion_state.py +86 -0
  277. {qontract_reconcile-0.10.1rc884.dist-info → qontract_reconcile-0.10.1rc886.dist-info}/WHEEL +0 -0
  278. {qontract_reconcile-0.10.1rc884.dist-info → qontract_reconcile-0.10.1rc886.dist-info}/entry_points.txt +0 -0
  279. {qontract_reconcile-0.10.1rc884.dist-info → qontract_reconcile-0.10.1rc886.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from reconcile.utils.glitchtip.models import (
4
2
  Organization,
5
3
  Project,
@@ -60,7 +58,7 @@ class GlitchtipClient(ApiBase):
60
58
  ]
61
59
 
62
60
  def create_project(
63
- self, organization_slug: str, team_slug: str, name: str, platform: Optional[str]
61
+ self, organization_slug: str, team_slug: str, name: str, platform: str | None
64
62
  ) -> Project:
65
63
  """Create a project."""
66
64
  return Project(
@@ -75,7 +73,7 @@ class GlitchtipClient(ApiBase):
75
73
  organization_slug: str,
76
74
  slug: str,
77
75
  name: str,
78
- platform: Optional[str],
76
+ platform: str | None,
79
77
  event_throttle_rate: int,
80
78
  ) -> Project:
81
79
  """Update a project."""
@@ -3,10 +3,7 @@ from __future__ import annotations
3
3
  import re
4
4
  from collections.abc import MutableMapping
5
5
  from enum import Enum
6
- from typing import (
7
- Any,
8
- Optional,
9
- )
6
+ from typing import Any
10
7
 
11
8
  from pydantic import (
12
9
  BaseModel,
@@ -26,7 +23,7 @@ def slugify(value: str) -> str:
26
23
 
27
24
 
28
25
  class User(BaseModel):
29
- pk: Optional[int] = Field(None, alias="id")
26
+ pk: int | None = Field(None, alias="id")
30
27
  email: str
31
28
  role: str
32
29
  pending: bool = False
@@ -44,7 +41,7 @@ class User(BaseModel):
44
41
 
45
42
 
46
43
  class Team(BaseModel):
47
- pk: Optional[int] = Field(None, alias="id")
44
+ pk: int | None = Field(None, alias="id")
48
45
  name: str = ""
49
46
  slug: str = ""
50
47
  users: list[User] = []
@@ -89,7 +86,7 @@ class RecipientType(Enum):
89
86
 
90
87
 
91
88
  class ProjectAlertRecipient(BaseModel):
92
- pk: Optional[int]
89
+ pk: int | None
93
90
  recipient_type: RecipientType = Field(..., alias="recipientType")
94
91
  url: str = ""
95
92
 
@@ -118,7 +115,7 @@ class ProjectAlertRecipient(BaseModel):
118
115
 
119
116
 
120
117
  class ProjectAlert(BaseModel):
121
- pk: Optional[int]
118
+ pk: int | None
122
119
  name: str
123
120
  timespan_minutes: int
124
121
  quantity: int
@@ -148,10 +145,10 @@ class ProjectAlert(BaseModel):
148
145
 
149
146
 
150
147
  class Project(BaseModel):
151
- pk: Optional[int] = Field(None, alias="id")
148
+ pk: int | None = Field(None, alias="id")
152
149
  name: str
153
150
  slug: str = ""
154
- platform: Optional[str]
151
+ platform: str | None
155
152
  teams: list[Team] = []
156
153
  alerts: list[ProjectAlert] = []
157
154
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
@@ -189,7 +186,7 @@ class Project(BaseModel):
189
186
 
190
187
 
191
188
  class Organization(BaseModel):
192
- pk: Optional[int] = Field(None, alias="id")
189
+ pk: int | None = Field(None, alias="id")
193
190
  name: str
194
191
  slug: str = ""
195
192
  projects: list[Project] = []
reconcile/utils/gql.py CHANGED
@@ -2,15 +2,10 @@ import logging
2
2
  import textwrap
3
3
  import threading
4
4
  from datetime import (
5
+ UTC,
5
6
  datetime,
6
- timezone,
7
- )
8
- from typing import (
9
- Any,
10
- Dict,
11
- Optional,
12
- Union,
13
7
  )
8
+ from typing import Any
14
9
  from urllib.parse import urlparse
15
10
 
16
11
  import requests
@@ -83,9 +78,7 @@ class GqlApiErrorForbiddenSchema(Exception):
83
78
 
84
79
  class GqlGetResourceError(Exception):
85
80
  def __init__(self, path, msg):
86
- super().__init__(
87
- "Error getting resource from path {}: {}".format(path, str(msg))
88
- )
81
+ super().__init__(f"Error getting resource from path {path}: {str(msg)}")
89
82
 
90
83
 
91
84
  class GqlApi:
@@ -95,11 +88,11 @@ class GqlApi:
95
88
  def __init__(
96
89
  self,
97
90
  url: str,
98
- token: Optional[str] = None,
91
+ token: str | None = None,
99
92
  int_name=None,
100
93
  validate_schemas=False,
101
- commit: Optional[str] = None,
102
- commit_timestamp: Optional[str] = None,
94
+ commit: str | None = None,
95
+ commit_timestamp: str | None = None,
103
96
  ) -> None:
104
97
  self.url = url
105
98
  self.token = token
@@ -143,15 +136,15 @@ class GqlApi:
143
136
  @retry(exceptions=GqlApiError, max_attempts=5, hook=capture_and_forget)
144
137
  def query(
145
138
  self, query: str, variables=None, skip_validation=False
146
- ) -> Optional[dict[str, Any]]:
139
+ ) -> dict[str, Any] | None:
147
140
  try:
148
141
  result = self.client.execute(
149
142
  gql(query), variables, get_execution_result=True
150
143
  ).formatted
151
144
  except requests.exceptions.ConnectionError as e:
152
- raise GqlApiError("Could not connect to GraphQL server ({})".format(e))
145
+ raise GqlApiError(f"Could not connect to GraphQL server ({e})")
153
146
  except TransportQueryError as e:
154
- raise GqlApiError("`error` returned with GraphQL response {}".format(e))
147
+ raise GqlApiError(f"`error` returned with GraphQL response {e}")
155
148
  except AssertionError:
156
149
  raise GqlApiError("`data` field missing from GraphQL response payload")
157
150
  except Exception as e:
@@ -247,16 +240,14 @@ class GqlApi:
247
240
  return list(self._queried_schemas)
248
241
 
249
242
  @property
250
- def commit_timestamp_utc(self) -> Optional[str]:
243
+ def commit_timestamp_utc(self) -> str | None:
251
244
  if self.commit_timestamp:
252
- return datetime.fromtimestamp(
253
- int(self.commit_timestamp), timezone.utc
254
- ).isoformat()
245
+ return datetime.fromtimestamp(int(self.commit_timestamp), UTC).isoformat()
255
246
  return None
256
247
 
257
248
 
258
249
  class GqlApiSingleton:
259
- gql_api: Optional[GqlApi] = None
250
+ gql_api: GqlApi | None = None
260
251
  gqlapi_lock = threading.Lock()
261
252
 
262
253
  @classmethod
@@ -288,11 +279,11 @@ class GqlApiSingleton:
288
279
 
289
280
  def init(
290
281
  url: str,
291
- token: Optional[str] = None,
282
+ token: str | None = None,
292
283
  integration=None,
293
284
  validate_schemas=False,
294
- commit: Optional[str] = None,
295
- commit_timestamp: Optional[str] = None,
285
+ commit: str | None = None,
286
+ commit_timestamp: str | None = None,
296
287
  ):
297
288
  return GqlApiSingleton.create(
298
289
  url,
@@ -319,12 +310,12 @@ class PersistentRequestsHTTPTransport(RequestsHTTPTransport):
319
310
  self,
320
311
  session: requests.Session,
321
312
  url: str,
322
- headers: Optional[Dict[str, Any]] = None,
323
- cookies: Optional[Union[Dict[str, Any], RequestsCookieJar]] = None,
324
- auth: Optional[AuthBase] = None,
313
+ headers: dict[str, Any] | None = None,
314
+ cookies: dict[str, Any] | RequestsCookieJar | None = None,
315
+ auth: AuthBase | None = None,
325
316
  use_json: bool = True,
326
- timeout: Optional[int] = None,
327
- verify: Union[bool, str] = True,
317
+ timeout: int | None = None,
318
+ verify: bool | str = True,
328
319
  retries: int = 0,
329
320
  method: str = "POST",
330
321
  **kwargs: Any,
@@ -398,8 +389,8 @@ def init_from_config(
398
389
 
399
390
 
400
391
  def _get_gql_server_and_token(
401
- autodetect_sha: bool = False, sha: Optional[str] = None
402
- ) -> tuple[str, str, Optional[str], Optional[str]]:
392
+ autodetect_sha: bool = False, sha: str | None = None
393
+ ) -> tuple[str, str, str | None, str | None]:
403
394
  config = get_config()
404
395
 
405
396
  server_url = urlparse(config["graphql"]["server"])
@@ -425,7 +416,7 @@ def get_api() -> GqlApi:
425
416
 
426
417
 
427
418
  def get_api_for_sha(
428
- sha: str, integration: Optional[str] = None, validate_schemas: bool = True
419
+ sha: str, integration: str | None = None, validate_schemas: bool = True
429
420
  ) -> GqlApi:
430
421
  server, token, commit, timestamp = _get_gql_server_and_token(
431
422
  autodetect_sha=False, sha=sha
@@ -442,8 +433,8 @@ def get_api_for_sha(
442
433
 
443
434
  def get_api_for_server(
444
435
  server: str,
445
- token: Optional[str],
446
- integration: Optional[str] = None,
436
+ token: str | None,
437
+ integration: str | None = None,
447
438
  validate_schemas: bool = True,
448
439
  ) -> GqlApi:
449
440
  return GqlApi(
@@ -458,7 +449,7 @@ def get_api_for_server(
458
449
 
459
450
  @retry(exceptions=requests.exceptions.HTTPError, max_attempts=5)
460
451
  def get_diff(
461
- old_sha: str, file_type: Optional[str] = None, file_path: Optional[str] = None
452
+ old_sha: str, file_type: str | None = None, file_path: str | None = None
462
453
  ) -> dict[str, Any]:
463
454
  config = get_config()
464
455
 
@@ -1,8 +1,6 @@
1
1
  from collections import defaultdict
2
+ from collections.abc import Callable, Hashable, Iterable
2
3
  from typing import (
3
- Callable,
4
- Hashable,
5
- Iterable,
6
4
  TypeVar,
7
5
  )
8
6
 
@@ -1,8 +1,5 @@
1
1
  import imaplib
2
- from typing import (
3
- Any,
4
- Union,
5
- )
2
+ from typing import Any
6
3
 
7
4
  from reconcile.utils.secret_reader import SecretReader
8
5
 
@@ -15,7 +12,7 @@ class ImapClient:
15
12
  self.password: str = imap_config["password"]
16
13
  self.port: int = int(imap_config["port"])
17
14
  self.timeout: int = settings["imap"].get("timeout", 30)
18
- self._server: Union[imaplib.IMAP4_SSL, None] = None
15
+ self._server: imaplib.IMAP4_SSL | None = None
19
16
 
20
17
  def __enter__(self) -> "ImapClient":
21
18
  self._server = imaplib.IMAP4_SSL(
@@ -1,6 +1,5 @@
1
1
  from typing import (
2
2
  Any,
3
- Optional,
4
3
  Self,
5
4
  )
6
5
 
@@ -61,7 +60,7 @@ class InternalGroupsApi:
61
60
 
62
61
  @retry(exceptions=(TokenExpiredError,), max_attempts=2)
63
62
  def _request(
64
- self, method: str, url: str, json: Optional[dict[Any, Any]] = None
63
+ self, method: str, url: str, json: dict[Any, Any] | None = None
65
64
  ) -> Response:
66
65
  try:
67
66
  return self._client.request(
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from enum import Enum
4
- from typing import Optional
3
+ from enum import StrEnum
5
4
 
6
5
  from pydantic import (
7
6
  BaseModel,
@@ -9,7 +8,7 @@ from pydantic import (
9
8
  )
10
9
 
11
10
 
12
- class EntityType(str, Enum):
11
+ class EntityType(StrEnum):
13
12
  USER = "user"
14
13
  SERVICE_ACCOUNT = "serviceaccount"
15
14
  DELETED_USER = "deleteduser"
@@ -35,17 +34,17 @@ class Group(BaseModel):
35
34
  contact_list: str = Field(..., alias="contactList")
36
35
  owners: list[Entity]
37
36
  display_name: str = Field(..., alias="displayName")
38
- notes: Optional[str] = None
39
- rover_group_member_query: Optional[str] = Field(None, alias="roverGroupMemberQuery")
40
- rover_group_inclusions: Optional[list[Entity]] = Field(
37
+ notes: str | None = None
38
+ rover_group_member_query: str | None = Field(None, alias="roverGroupMemberQuery")
39
+ rover_group_inclusions: list[Entity] | None = Field(
41
40
  None, alias="roverGroupInclusions"
42
41
  )
43
- rover_group_exclusions: Optional[list[Entity]] = Field(
42
+ rover_group_exclusions: list[Entity] | None = Field(
44
43
  None, alias="roverGroupExclusions"
45
44
  )
46
45
  members: list[Entity] = []
47
- member_of: Optional[list[str]] = Field(None, alias="memberOf")
48
- namespace: Optional[str] = None
46
+ member_of: list[str] | None = Field(None, alias="memberOf")
47
+ namespace: str | None = None
49
48
 
50
49
  def __eq__(self, other: object) -> bool:
51
50
  if not isinstance(other, Group):
@@ -108,7 +108,7 @@ class JenkinsApi:
108
108
  res.raise_for_status()
109
109
 
110
110
  def get_all_roles(self):
111
- url = "{}/role-strategy/strategy/getAllRoles".format(self.url)
111
+ url = f"{self.url}/role-strategy/strategy/getAllRoles"
112
112
  res = requests.get(
113
113
  url, verify=self.ssl_verify, auth=(self.user, self.password), timeout=60
114
114
  )
@@ -117,7 +117,7 @@ class JenkinsApi:
117
117
  return res.json()
118
118
 
119
119
  def assign_role_to_user(self, role, user):
120
- url = "{}/role-strategy/strategy/assignRole".format(self.url)
120
+ url = f"{self.url}/role-strategy/strategy/assignRole"
121
121
  data = {"type": "globalRoles", "roleName": role, "sid": user}
122
122
  res = requests.post(
123
123
  url,
@@ -130,7 +130,7 @@ class JenkinsApi:
130
130
  res.raise_for_status()
131
131
 
132
132
  def unassign_role_from_user(self, role, user):
133
- url = "{}/role-strategy/strategy/unassignRole".format(self.url)
133
+ url = f"{self.url}/role-strategy/strategy/unassignRole"
134
134
  data = {"type": "globalRoles", "roleName": role, "sid": user}
135
135
  res = requests.post(
136
136
  url,
@@ -143,7 +143,7 @@ class JenkinsApi:
143
143
  res.raise_for_status()
144
144
 
145
145
  def safe_restart(self, force_restart=False):
146
- url = "{}/safeRestart".format(self.url)
146
+ url = f"{self.url}/safeRestart"
147
147
  if self.should_restart or force_restart:
148
148
  logging.debug(
149
149
  "performing safe restart. "
@@ -1,6 +1,6 @@
1
1
  import base64
2
2
  import textwrap
3
- from typing import Callable
3
+ from collections.abc import Callable
4
4
 
5
5
  from jinja2 import nodes
6
6
  from jinja2.exceptions import TemplateRuntimeError
@@ -2,7 +2,7 @@ import hashlib
2
2
  import json
3
3
  import re
4
4
  from collections.abc import Iterable
5
- from typing import Any, Optional
5
+ from typing import Any
6
6
  from urllib import parse
7
7
 
8
8
  import jinja2
@@ -20,7 +20,7 @@ def json_to_dict(input: str) -> Any:
20
20
  return data
21
21
 
22
22
 
23
- def urlescape(string: str, safe: str = "/", encoding: Optional[str] = None) -> str:
23
+ def urlescape(string: str, safe: str = "/", encoding: str | None = None) -> str:
24
24
  """Jinja2 filter that is a simple wrapper around urllib's URL quoting
25
25
  functions that takes a string value and makes it safe for use as URL
26
26
  components escaping any reserved characters using URL encoding. See:
@@ -37,7 +37,7 @@ def urlescape(string: str, safe: str = "/", encoding: Optional[str] = None) -> s
37
37
  return parse.quote(string, safe=safe, encoding=encoding)
38
38
 
39
39
 
40
- def urlunescape(string: str, encoding: Optional[str] = None) -> str:
40
+ def urlunescape(string: str, encoding: str | None = None) -> str:
41
41
  """Jinja2 filter that is a simple wrapper around urllib's URL unquoting
42
42
  functions that takes an URL-encoded string value and unescapes it
43
43
  replacing any URL-encoded values with their character equivalent. See:
@@ -78,7 +78,7 @@ def hash_list(input: Iterable) -> str:
78
78
  lst = list(input)
79
79
  str_lst = []
80
80
  for el in lst:
81
- if isinstance(el, (list, dict)):
81
+ if isinstance(el, list | dict):
82
82
  raise RuntimeError(
83
83
  f"jinja2 hash_list function received non-primitive value {el}. All values received {lst}"
84
84
  )
@@ -1,5 +1,5 @@
1
1
  from functools import cache
2
- from typing import Any, Optional
2
+ from typing import Any
3
3
 
4
4
  import jinja2
5
5
  from jinja2.sandbox import SandboxedEnvironment
@@ -66,9 +66,9 @@ def lookup_github_file_content(
66
66
  repo: str,
67
67
  path: str,
68
68
  ref: str,
69
- tvars: Optional[dict[str, Any]] = None,
70
- settings: Optional[dict[str, Any]] = None,
71
- secret_reader: Optional[SecretReaderBase] = None,
69
+ tvars: dict[str, Any] | None = None,
70
+ settings: dict[str, Any] | None = None,
71
+ secret_reader: SecretReaderBase | None = None,
72
72
  ) -> str:
73
73
  if tvars is not None:
74
74
  repo = process_jinja2_template(
@@ -98,7 +98,7 @@ def lookup_s3_object(
98
98
  account_name: str,
99
99
  bucket_name: str,
100
100
  path: str,
101
- region_name: Optional[str] = None,
101
+ region_name: str | None = None,
102
102
  ) -> str:
103
103
  settings = queries.get_app_interface_settings()
104
104
  accounts = queries.get_aws_accounts(name=account_name)
@@ -118,12 +118,12 @@ def lookup_s3_object(
118
118
  def lookup_secret(
119
119
  path: str,
120
120
  key: str,
121
- version: Optional[str] = None,
122
- tvars: Optional[dict[str, Any]] = None,
121
+ version: str | None = None,
122
+ tvars: dict[str, Any] | None = None,
123
123
  allow_not_found: bool = False,
124
- settings: Optional[dict[str, Any]] = None,
125
- secret_reader: Optional[SecretReaderBase] = None,
126
- ) -> Optional[str]:
124
+ settings: dict[str, Any] | None = None,
125
+ secret_reader: SecretReaderBase | None = None,
126
+ ) -> str | None:
127
127
  if tvars is not None:
128
128
  path = process_jinja2_template(
129
129
  body=path, vars=tvars, settings=settings, secret_reader=secret_reader
@@ -150,10 +150,10 @@ def lookup_secret(
150
150
 
151
151
  def process_jinja2_template(
152
152
  body: str,
153
- vars: Optional[dict[str, Any]] = None,
153
+ vars: dict[str, Any] | None = None,
154
154
  extra_curly: bool = False,
155
- settings: Optional[dict[str, Any]] = None,
156
- secret_reader: Optional[SecretReaderBase] = None,
155
+ settings: dict[str, Any] | None = None,
156
+ secret_reader: SecretReaderBase | None = None,
157
157
  ) -> Any:
158
158
  if vars is None:
159
159
  vars = {}
@@ -196,10 +196,10 @@ def process_jinja2_template(
196
196
 
197
197
  def process_extracurlyjinja2_template(
198
198
  body: str,
199
- vars: Optional[dict[str, Any]] = None,
199
+ vars: dict[str, Any] | None = None,
200
200
  extra_curly: bool = True,
201
- settings: Optional[dict[str, Any]] = None,
202
- secret_reader: Optional[SecretReaderBase] = None,
201
+ settings: dict[str, Any] | None = None,
202
+ secret_reader: SecretReaderBase | None = None,
203
203
  ) -> Any:
204
204
  if vars is None:
205
205
  vars = {}
@@ -8,7 +8,6 @@ from collections.abc import (
8
8
  )
9
9
  from typing import (
10
10
  Any,
11
- Optional,
12
11
  Protocol,
13
12
  )
14
13
 
@@ -57,11 +56,11 @@ class JiraClient:
57
56
 
58
57
  def __init__(
59
58
  self,
60
- jira_board: Optional[Mapping[str, Any]] = None,
61
- settings: Optional[Mapping] = None,
62
- jira_api: Optional[JIRA] = None,
63
- project: Optional[str] = None,
64
- server: Optional[str] = None,
59
+ jira_board: Mapping[str, Any] | None = None,
60
+ settings: Mapping | None = None,
61
+ jira_api: JIRA | None = None,
62
+ project: str | None = None,
63
+ server: str | None = None,
65
64
  ):
66
65
  """
67
66
  Note: jira_board and settings is to be deprecated. Use JiraClient.create() instead.
@@ -89,7 +88,7 @@ class JiraClient:
89
88
  self.my_permissions = functools.lru_cache(maxsize=None)(self._my_permissions)
90
89
 
91
90
  def _deprecated_init(
92
- self, jira_board: Mapping[str, Any], settings: Optional[Mapping]
91
+ self, jira_board: Mapping[str, Any], settings: Mapping | None
93
92
  ) -> None:
94
93
  secret_reader = SecretReader(settings=settings)
95
94
  self.project = jira_board["name"]
@@ -114,7 +113,7 @@ class JiraClient:
114
113
  project_name: str,
115
114
  token: str,
116
115
  server_url: str,
117
- jira_watcher_settings: Optional[JiraWatcherSettings] = None,
116
+ jira_watcher_settings: JiraWatcherSettings | None = None,
118
117
  ) -> JiraClient:
119
118
  read_timeout = JiraClient.DEFAULT_READ_TIMEOUT
120
119
  connect_timeout = JiraClient.DEFAULT_CONNECT_TIMEOUT
@@ -130,12 +129,12 @@ class JiraClient:
130
129
  server=server_url,
131
130
  )
132
131
 
133
- def get_issues(self, fields: Optional[Mapping] = None) -> list[Issue]:
132
+ def get_issues(self, fields: Mapping | None = None) -> list[Issue]:
134
133
  block_size = 100
135
134
  block_num = 0
136
135
 
137
136
  all_issues: list[Issue] = []
138
- jql = "project={}".format(self.project)
137
+ jql = f"project={self.project}"
139
138
  kwargs: dict[str, Any] = {}
140
139
  if fields:
141
140
  kwargs["fields"] = ",".join(fields)
@@ -163,7 +162,7 @@ class JiraClient:
163
162
  self,
164
163
  summary: str,
165
164
  body: str,
166
- labels: Optional[Iterable[str]] = None,
165
+ labels: Iterable[str] | None = None,
167
166
  links: Iterable[str] = (),
168
167
  ) -> Issue:
169
168
  """Create an issue in our project with the given labels."""
@@ -14,10 +14,7 @@ from subprocess import (
14
14
  STDOUT,
15
15
  CalledProcessError,
16
16
  )
17
- from typing import (
18
- Any,
19
- Optional,
20
- )
17
+ from typing import Any
21
18
 
22
19
  import yaml
23
20
  from jenkins_jobs.builder import JenkinsManager
@@ -63,7 +60,7 @@ class JJB: # pylint: disable=too-many-public-methods
63
60
  ini = self.secret_reader.read(token)
64
61
  ini = ini.replace('"', "")
65
62
  ini = ini.replace("false", "False")
66
- ini_file_path = "{}/{}.ini".format(wd, name)
63
+ ini_file_path = f"{wd}/{name}.ini"
67
64
  with open(ini_file_path, "w", encoding="locale") as f:
68
65
  f.write(ini)
69
66
  f.write("\n")
@@ -75,7 +72,7 @@ class JJB: # pylint: disable=too-many-public-methods
75
72
  for c in configs:
76
73
  instance_name = c["instance"]["name"]
77
74
  config = c["config"]
78
- config_file_path = "{}/config.yaml".format(working_dirs[instance_name])
75
+ config_file_path = f"{working_dirs[instance_name]}/config.yaml"
79
76
  if config:
80
77
  content = yaml.load(config, Loader=yaml.FullLoader)
81
78
  if c["type"] == "jobs":
@@ -99,7 +96,7 @@ class JJB: # pylint: disable=too-many-public-methods
99
96
  config files in the working directories with
100
97
  the supplied configs"""
101
98
  for name, wd in self.working_dirs.items():
102
- config_path = "{}/config.yaml".format(wd)
99
+ config_path = f"{wd}/config.yaml"
103
100
  with open(config_path, "w", encoding="locale") as f:
104
101
  f.write(configs[name])
105
102
 
@@ -135,8 +132,8 @@ class JJB: # pylint: disable=too-many-public-methods
135
132
  working directories"""
136
133
  configs = {}
137
134
  for name, wd in self.working_dirs.items():
138
- config_path = "{}/config.yaml".format(wd)
139
- with open(config_path, "r", encoding="locale") as f:
135
+ config_path = f"{wd}/config.yaml"
136
+ with open(config_path, encoding="locale") as f:
140
137
  configs[name] = f.read()
141
138
 
142
139
  return configs
@@ -149,8 +146,8 @@ class JJB: # pylint: disable=too-many-public-methods
149
146
  :param fetch_state: subdirectory to use ('desired' or 'current')
150
147
  """
151
148
  for name, wd in self.working_dirs.items():
152
- ini_path = "{}/{}.ini".format(wd, name)
153
- config_path = "{}/config.yaml".format(wd)
149
+ ini_path = f"{wd}/{name}.ini"
150
+ config_path = f"{wd}/config.yaml"
154
151
 
155
152
  output_dir = path.join(io_dir, "jjb", fetch_state, name)
156
153
  args = [
@@ -227,8 +224,8 @@ class JJB: # pylint: disable=too-many-public-methods
227
224
 
228
225
  def update(self) -> None:
229
226
  for name, wd in self.working_dirs.items():
230
- ini_path = "{}/{}.ini".format(wd, name)
231
- config_path = "{}/config.yaml".format(wd)
227
+ ini_path = f"{wd}/{name}.ini"
228
+ config_path = f"{wd}/config.yaml"
232
229
 
233
230
  os.environ["PYTHONHTTPSVERIFY"] = self.python_https_verify
234
231
  cmd = ["jenkins-jobs", "--conf", ini_path, "update", config_path]
@@ -268,8 +265,8 @@ class JJB: # pylint: disable=too-many-public-methods
268
265
 
269
266
  @retry(exceptions=(JenkinsJobsException))
270
267
  def get_jobs(self, wd, name):
271
- ini_path = "{}/{}.ini".format(wd, name)
272
- config_path = "{}/config.yaml".format(wd)
268
+ ini_path = f"{wd}/{name}.ini"
269
+ config_path = f"{wd}/config.yaml"
273
270
 
274
271
  args = ["--conf", ini_path, "test", config_path]
275
272
  jjb = self.get_jjb(args)
@@ -321,7 +318,7 @@ class JJB: # pylint: disable=too-many-public-methods
321
318
  try:
322
319
  repos.add(self.get_repo_url(job))
323
320
  except KeyError:
324
- logging.debug("missing github url: {}".format(job_name))
321
+ logging.debug(f"missing github url: {job_name}")
325
322
  return repos
326
323
 
327
324
  def get_admins(self):
@@ -393,7 +390,7 @@ class JJB: # pylint: disable=too-many-public-methods
393
390
  raise ValueError(f"job with {job_type=} and {repo_url=} not found")
394
391
 
395
392
  @staticmethod
396
- def get_trigger_phrases_regex(job: dict) -> Optional[str]:
393
+ def get_trigger_phrases_regex(job: dict) -> str | None:
397
394
  for trigger in job["triggers"]:
398
395
  if "gitlab" in trigger:
399
396
  return trigger["gitlab"].get("note-regex")