qontract-reconcile 0.10.2.dev394__py3-none-any.whl → 0.10.2.dev427__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 (316) hide show
  1. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/METADATA +5 -4
  2. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/RECORD +316 -315
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +117 -18
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +6 -6
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +2 -5
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_saml_idp/integration.py +5 -3
  18. reconcile/aws_saml_roles/integration.py +23 -22
  19. reconcile/aws_version_sync/integration.py +6 -12
  20. reconcile/change_owners/bundle.py +3 -3
  21. reconcile/change_owners/change_log_tracking.py +3 -2
  22. reconcile/change_owners/change_owners.py +1 -1
  23. reconcile/cli.py +62 -4
  24. reconcile/dashdotdb_dora.py +1 -1
  25. reconcile/dashdotdb_slo.py +1 -1
  26. reconcile/database_access_manager.py +8 -9
  27. reconcile/dynatrace_token_provider/integration.py +1 -1
  28. reconcile/endpoints_discovery/integration.py +4 -1
  29. reconcile/endpoints_discovery/merge_request.py +1 -1
  30. reconcile/endpoints_discovery/merge_request_manager.py +1 -1
  31. reconcile/external_resources/integration.py +1 -1
  32. reconcile/external_resources/manager.py +3 -2
  33. reconcile/external_resources/metrics.py +1 -1
  34. reconcile/external_resources/model.py +13 -13
  35. reconcile/external_resources/reconciler.py +7 -4
  36. reconcile/external_resources/secrets_sync.py +2 -2
  37. reconcile/external_resources/state.py +22 -13
  38. reconcile/fleet_labeler/integration.py +1 -1
  39. reconcile/gcp_image_mirror.py +2 -2
  40. reconcile/github_org.py +1 -1
  41. reconcile/github_owners.py +4 -0
  42. reconcile/gitlab_members.py +6 -12
  43. reconcile/gitlab_permissions.py +8 -12
  44. reconcile/glitchtip_project_alerts/integration.py +3 -1
  45. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  46. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  47. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  48. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  49. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  50. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  51. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  52. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  53. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  54. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  55. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +5 -5
  56. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +5 -5
  57. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +5 -5
  58. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +5 -5
  59. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  60. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  61. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  62. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  63. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  64. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  65. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  66. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  67. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  68. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  69. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  70. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  71. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  72. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  74. reconcile/gql_definitions/common/apps.py +5 -5
  75. reconcile/gql_definitions/common/aws_vpc_requests.py +5 -5
  76. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  77. reconcile/gql_definitions/common/clusters.py +5 -5
  78. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  79. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  80. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  81. reconcile/gql_definitions/common/github_orgs.py +5 -5
  82. reconcile/gql_definitions/common/jira_settings.py +5 -5
  83. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  84. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  85. reconcile/gql_definitions/common/namespaces.py +5 -5
  86. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  87. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  88. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  89. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  90. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  91. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  92. reconcile/gql_definitions/common/quay_instances.py +5 -5
  93. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  94. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  95. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  96. reconcile/gql_definitions/common/saas_files.py +5 -5
  97. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  98. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  99. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  100. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  101. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  102. reconcile/gql_definitions/common/users.py +5 -5
  103. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  104. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  105. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  106. reconcile/gql_definitions/cost_report/settings.py +5 -5
  107. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  108. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  109. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  110. reconcile/gql_definitions/email_sender/apps.py +5 -5
  111. reconcile/gql_definitions/email_sender/emails.py +5 -5
  112. reconcile/gql_definitions/email_sender/users.py +5 -5
  113. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  114. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  115. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  116. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +5 -5
  117. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  118. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  119. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  120. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  121. reconcile/gql_definitions/fragments/aws_account_common.py +5 -5
  122. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  123. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  124. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  125. reconcile/gql_definitions/fragments/aws_organization.py +5 -5
  126. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  127. reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -5
  128. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  129. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  130. reconcile/gql_definitions/fragments/disable.py +5 -5
  131. reconcile/gql_definitions/fragments/email_service.py +5 -5
  132. reconcile/gql_definitions/fragments/email_user.py +5 -5
  133. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  134. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  135. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  136. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  137. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  138. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  139. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  140. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  141. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  142. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  143. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  144. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  145. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  146. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  147. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  148. reconcile/gql_definitions/fragments/user.py +5 -5
  149. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  150. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  151. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  152. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  153. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  154. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  155. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  156. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  157. reconcile/gql_definitions/integrations/integrations.py +5 -5
  158. reconcile/gql_definitions/introspection.json +231 -0
  159. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  160. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  161. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  162. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +5 -5
  163. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  164. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  165. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  166. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  167. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  168. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  169. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  170. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  171. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  172. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  173. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  174. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  175. reconcile/gql_definitions/rhcs/certs.py +24 -79
  176. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +42 -0
  177. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  178. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  179. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  180. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  181. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  182. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  183. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  184. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  185. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  186. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  187. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  188. reconcile/gql_definitions/status_board/status_board.py +5 -5
  189. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  190. reconcile/gql_definitions/templating/template_collection.py +5 -5
  191. reconcile/gql_definitions/templating/templates.py +5 -5
  192. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  193. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  194. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  195. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  196. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  197. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  198. reconcile/gql_definitions/terraform_init/aws_accounts.py +5 -5
  199. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  200. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  201. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +5 -5
  202. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +5 -5
  203. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  204. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  205. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  206. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  207. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  208. reconcile/integrations_manager.py +3 -3
  209. reconcile/jenkins_worker_fleets.py +9 -8
  210. reconcile/jira_permissions_validator.py +2 -2
  211. reconcile/ldap_groups/integration.py +1 -1
  212. reconcile/ocm/types.py +35 -57
  213. reconcile/ocm_aws_infrastructure_access.py +1 -1
  214. reconcile/ocm_clusters.py +4 -4
  215. reconcile/ocm_labels/integration.py +3 -2
  216. reconcile/ocm_machine_pools.py +33 -27
  217. reconcile/openshift_base.py +113 -4
  218. reconcile/openshift_cluster_bots.py +1 -1
  219. reconcile/openshift_namespace_labels.py +1 -1
  220. reconcile/openshift_namespaces.py +97 -101
  221. reconcile/openshift_resources_base.py +6 -2
  222. reconcile/openshift_rhcs_certs.py +27 -29
  223. reconcile/openshift_rolebindings.py +7 -11
  224. reconcile/openshift_saas_deploy.py +4 -5
  225. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  226. reconcile/openshift_serviceaccount_tokens.py +2 -2
  227. reconcile/openshift_upgrade_watcher.py +1 -1
  228. reconcile/oum/labelset.py +5 -3
  229. reconcile/oum/models.py +1 -4
  230. reconcile/prometheus_rules_tester/integration.py +3 -3
  231. reconcile/quay_mirror.py +1 -1
  232. reconcile/queries.py +6 -0
  233. reconcile/rhidp/common.py +3 -5
  234. reconcile/rhidp/sso_client/base.py +16 -5
  235. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  236. reconcile/skupper_network/integration.py +2 -2
  237. reconcile/slack_usergroups.py +31 -11
  238. reconcile/status_board.py +6 -6
  239. reconcile/statuspage/atlassian.py +7 -7
  240. reconcile/statuspage/page.py +4 -9
  241. reconcile/templating/lib/rendering.py +3 -3
  242. reconcile/templating/renderer.py +2 -2
  243. reconcile/terraform_cloudflare_dns.py +3 -3
  244. reconcile/terraform_cloudflare_resources.py +5 -5
  245. reconcile/terraform_cloudflare_users.py +3 -2
  246. reconcile/terraform_init/integration.py +2 -2
  247. reconcile/terraform_repo.py +16 -12
  248. reconcile/terraform_resources.py +6 -6
  249. reconcile/terraform_tgw_attachments.py +20 -18
  250. reconcile/terraform_vpc_resources/integration.py +3 -1
  251. reconcile/typed_queries/cost_report/app_names.py +1 -1
  252. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  253. reconcile/typed_queries/saas_files.py +11 -11
  254. reconcile/typed_queries/status_board.py +2 -2
  255. reconcile/unleash_feature_toggles/integration.py +4 -2
  256. reconcile/utils/acs/base.py +6 -3
  257. reconcile/utils/acs/policies.py +2 -2
  258. reconcile/utils/aws_api.py +51 -20
  259. reconcile/utils/aws_api_typed/organization.py +4 -2
  260. reconcile/utils/binary.py +7 -12
  261. reconcile/utils/deadmanssnitch_api.py +1 -1
  262. reconcile/utils/early_exit_cache.py +8 -10
  263. reconcile/utils/gitlab_api.py +7 -5
  264. reconcile/utils/glitchtip/client.py +6 -2
  265. reconcile/utils/glitchtip/models.py +25 -28
  266. reconcile/utils/gql.py +4 -7
  267. reconcile/utils/instrumented_wrappers.py +1 -1
  268. reconcile/utils/internal_groups/client.py +2 -2
  269. reconcile/utils/internal_groups/models.py +8 -17
  270. reconcile/utils/jinja2/utils.py +2 -5
  271. reconcile/utils/jobcontroller/controller.py +2 -2
  272. reconcile/utils/jobcontroller/models.py +17 -1
  273. reconcile/utils/json.py +43 -1
  274. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  275. reconcile/utils/membershipsources/models.py +16 -23
  276. reconcile/utils/membershipsources/resolver.py +4 -2
  277. reconcile/utils/merge_request_manager/merge_request_manager.py +1 -1
  278. reconcile/utils/merge_request_manager/parser.py +4 -4
  279. reconcile/utils/metrics.py +5 -5
  280. reconcile/utils/models.py +304 -82
  281. reconcile/utils/mr/notificator.py +1 -1
  282. reconcile/utils/mr/user_maintenance.py +3 -2
  283. reconcile/utils/oc.py +246 -201
  284. reconcile/utils/ocm/addons.py +0 -1
  285. reconcile/utils/ocm/base.py +17 -20
  286. reconcile/utils/ocm/cluster_groups.py +1 -1
  287. reconcile/utils/ocm/identity_providers.py +2 -2
  288. reconcile/utils/ocm/labels.py +1 -1
  289. reconcile/utils/ocm/products.py +8 -8
  290. reconcile/utils/ocm/service_log.py +1 -1
  291. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  292. reconcile/utils/openshift_resource.py +5 -0
  293. reconcile/utils/pagerduty_api.py +5 -2
  294. reconcile/utils/promotion_state.py +6 -11
  295. reconcile/utils/raw_github_api.py +1 -1
  296. reconcile/utils/rhcsv2_certs.py +1 -4
  297. reconcile/utils/rosa/session.py +16 -0
  298. reconcile/utils/runtime/integration.py +1 -1
  299. reconcile/utils/saasherder/interfaces.py +13 -20
  300. reconcile/utils/saasherder/models.py +23 -20
  301. reconcile/utils/saasherder/saasherder.py +46 -24
  302. reconcile/utils/slack_api.py +2 -2
  303. reconcile/utils/structs.py +1 -1
  304. reconcile/utils/terraform_client.py +1 -1
  305. reconcile/utils/terrascript_aws_client.py +47 -43
  306. reconcile/utils/unleash/server.py +2 -8
  307. reconcile/utils/vault.py +5 -12
  308. reconcile/utils/vcs.py +8 -8
  309. reconcile/vault_replication.py +1 -1
  310. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  311. tools/cli_commands/cost_report/view.py +7 -6
  312. tools/cli_commands/erv2.py +1 -1
  313. tools/qontract_cli.py +6 -5
  314. tools/template_validation.py +3 -1
  315. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/WHEEL +0 -0
  316. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/entry_points.txt +0 -0
@@ -69,7 +69,7 @@ class ValidationError(Exception):
69
69
  class TGWAccountProviderInfo(BaseModel):
70
70
  name: str
71
71
  uid: str
72
- assume_role: str | None
72
+ assume_role: str | None = None
73
73
  assume_region: str
74
74
 
75
75
 
@@ -81,10 +81,10 @@ class Requester(BaseModel):
81
81
  tgw_id: str
82
82
  tgw_arn: str
83
83
  region: str
84
- routes: list[dict] | None
85
- rules: list[dict] | None
86
- hostedzones: list[str] | None
87
- cidr_block: str | None
84
+ routes: list[dict] | None = None
85
+ rules: list[dict] | None = None
86
+ hostedzones: list[str] | None = None
87
+ cidr_block: str | None = None
88
88
  cidr_blocks: list[str]
89
89
  account: TGWAccountProviderInfo
90
90
 
@@ -92,11 +92,11 @@ class Requester(BaseModel):
92
92
  class Accepter(BaseModel):
93
93
  cidr_block: str
94
94
  region: str
95
- vpc_id: str | None
96
- route_table_ids: list[str] | None
97
- subnets_id_az: list[dict[str, str]] | None
95
+ vpc_id: str | None = None
96
+ route_table_ids: list[str] | None = None
97
+ subnets_id_az: list[dict[str, str]] | None = None
98
98
  account: ClusterAccountProviderInfo
99
- api_security_group_id: str | None
99
+ api_security_group_id: str | None = None
100
100
 
101
101
 
102
102
  class DesiredStateItem(BaseModel):
@@ -193,7 +193,7 @@ def _build_desired_state_tgw_connection(
193
193
  yield None
194
194
 
195
195
  account_tgws = awsapi.get_tgws_details(
196
- peer_connection.account.dict(by_alias=True),
196
+ peer_connection.account.model_dump(by_alias=True),
197
197
  cluster_region,
198
198
  cluster_cidr_block,
199
199
  tags=peer_connection.tags or {},
@@ -275,7 +275,7 @@ def _build_accepter(
275
275
  )
276
276
  (vpc_id, route_table_ids, subnets_id_az, api_security_group_id) = (
277
277
  awsapi.get_cluster_vpc_details(
278
- account.dict(by_alias=True),
278
+ account.model_dump(by_alias=True),
279
279
  route_tables=bool(peer_connection.manage_routes),
280
280
  subnets=True,
281
281
  hcp_vpc_endpoint_sg=allow_hcp_private_api_access,
@@ -318,12 +318,12 @@ def _build_ocm_map(
318
318
  clusters: Iterable[ClusterV1],
319
319
  vault_settings: AppInterfaceSettingsV1,
320
320
  ) -> OCMMap | None:
321
- ocm_clusters = [c.dict(by_alias=True) for c in clusters if c.ocm]
321
+ ocm_clusters = [c.model_dump(by_alias=True) for c in clusters if c.ocm]
322
322
  return (
323
323
  OCMMap(
324
324
  clusters=ocm_clusters,
325
325
  integration=QONTRACT_INTEGRATION,
326
- settings=vault_settings.dict(by_alias=True),
326
+ settings=vault_settings.model_dump(by_alias=True),
327
327
  )
328
328
  if ocm_clusters
329
329
  # this is a case for an OCP cluster which is not provisioned
@@ -347,7 +347,7 @@ def _populate_tgw_attachments_working_dirs(
347
347
  accounts_by_infra_account_name: dict[str, list[dict[str, Any]]] = {}
348
348
  for item in desired_state:
349
349
  accounts_by_infra_account_name.setdefault(item.infra_acount_name, []).append(
350
- item.accepter.account.dict(by_alias=True)
350
+ item.accepter.account.model_dump(by_alias=True)
351
351
  )
352
352
  for infra_account_name, accounts in accounts_by_infra_account_name.items():
353
353
  ts.populate_additional_providers(infra_account_name, accounts)
@@ -429,7 +429,9 @@ def setup(
429
429
  print_to_file: str | None = None,
430
430
  ) -> tuple[SecretReaderBase, AWSApi, Terraform, Terrascript]:
431
431
  tgw_clusters = desired_state_data_source.clusters
432
- all_accounts = [a.dict(by_alias=True) for a in desired_state_data_source.accounts]
432
+ all_accounts = [
433
+ a.model_dump(by_alias=True) for a in desired_state_data_source.accounts
434
+ ]
433
435
  account_by_name = {a["name"]: a for a in all_accounts}
434
436
  vault_settings = get_app_interface_vault_settings()
435
437
  secret_reader = create_secret_reader(vault_settings.vault)
@@ -455,7 +457,7 @@ def setup(
455
457
  "",
456
458
  thread_pool_size,
457
459
  tgw_accounts,
458
- settings=vault_settings.dict(by_alias=True),
460
+ settings=vault_settings.model_dump(by_alias=True),
459
461
  default_tags=default_tags,
460
462
  )
461
463
  tgw_rosa_cluster_accounts = [
@@ -516,7 +518,7 @@ def run(
516
518
  ) -> None:
517
519
  desired_state_data_source = _fetch_desired_state_data_source(account_name)
518
520
  tgw_accounts = [
519
- a.dict(by_alias=True)
521
+ a.model_dump(by_alias=True)
520
522
  for a in _filter_tgw_accounts(
521
523
  desired_state_data_source.accounts, desired_state_data_source.clusters
522
524
  )
@@ -573,7 +575,7 @@ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
573
575
  desired_state = _fetch_desired_state_data_source()
574
576
  for a in desired_state.accounts:
575
577
  a.deletion_approvals = []
576
- return desired_state.dict(by_alias=True)
578
+ return desired_state.model_dump(by_alias=True)
577
579
 
578
580
 
579
581
  def desired_state_shard_config() -> DesiredStateShardConfig:
@@ -162,7 +162,9 @@ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesPa
162
162
  logging.debug("No VPC requests found, nothing to do.")
163
163
  sys.exit(ExitCodes.SUCCESS)
164
164
 
165
- accounts_untyped: list[dict] = [acc.dict(by_alias=True) for acc in accounts]
165
+ accounts_untyped: list[dict] = [
166
+ acc.model_dump(by_alias=True) for acc in accounts
167
+ ]
166
168
  try:
167
169
  default_tags = get_settings().default_tags
168
170
  except ValueError:
@@ -6,7 +6,7 @@ from reconcile.utils.gql import GqlApi
6
6
 
7
7
  class App(BaseModel):
8
8
  name: str
9
- parent_app_name: str | None
9
+ parent_app_name: str | None = None
10
10
 
11
11
 
12
12
  def get_app_names(
@@ -13,7 +13,7 @@ class CostNamespace(BaseModel, frozen=True):
13
13
  labels: CostNamespaceLabels
14
14
  app_name: str
15
15
  cluster_name: str
16
- cluster_external_id: str | None
16
+ cluster_external_id: str | None = None
17
17
 
18
18
 
19
19
  def get_cost_namespaces(
@@ -32,7 +32,7 @@ def get_cost_namespaces(
32
32
  return [
33
33
  CostNamespace(
34
34
  name=namespace.name,
35
- labels=CostNamespaceLabels.parse_obj(namespace.labels or {}),
35
+ labels=CostNamespaceLabels.model_validate(namespace.labels or {}),
36
36
  app_name=namespace.app.name,
37
37
  cluster_name=namespace.cluster.name,
38
38
  cluster_external_id=namespace.cluster.spec.external_id
@@ -6,7 +6,6 @@ from typing import Any
6
6
  from jsonpath_ng.exceptions import JsonPathParserError
7
7
  from pydantic import (
8
8
  BaseModel,
9
- Extra,
10
9
  Field,
11
10
  Json,
12
11
  )
@@ -51,7 +50,12 @@ from reconcile.utils.json import json_dumps
51
50
  from reconcile.utils.jsonpath import parse_jsonpath
52
51
 
53
52
 
54
- class SaasResourceTemplateTarget(ConfiguredBaseModel):
53
+ class SaasResourceTemplateTarget(
54
+ ConfiguredBaseModel,
55
+ validate_by_alias=True,
56
+ # ignore `namespaceSelector` and 'provider' fields from the GQL schema
57
+ extra="ignore",
58
+ ):
55
59
  path: str | None = Field(..., alias="path")
56
60
  name: str | None = Field(..., alias="name")
57
61
  # the namespace must be required to fulfill the saas file schema (utils.saasherder.interface.SaasFile)
@@ -79,12 +83,8 @@ class SaasResourceTemplateTarget(ConfiguredBaseModel):
79
83
  digest_size=20,
80
84
  ).hexdigest()
81
85
 
82
- class Config:
83
- # ignore `namespaceSelector` and 'provider' fields from the GQL schema
84
- extra = Extra.ignore
85
-
86
86
 
87
- class SaasResourceTemplate(ConfiguredBaseModel):
87
+ class SaasResourceTemplate(ConfiguredBaseModel, validate_by_alias=True):
88
88
  name: str = Field(..., alias="name")
89
89
  url: str = Field(..., alias="url")
90
90
  path: str = Field(..., alias="path")
@@ -97,7 +97,7 @@ class SaasResourceTemplate(ConfiguredBaseModel):
97
97
  targets: list[SaasResourceTemplateTarget] = Field(..., alias="targets")
98
98
 
99
99
 
100
- class SaasFile(ConfiguredBaseModel):
100
+ class SaasFile(ConfiguredBaseModel, validate_by_alias=True):
101
101
  path: str = Field(..., alias="path")
102
102
  name: str = Field(..., alias="name")
103
103
  labels: Json | None = Field(..., alias="labels")
@@ -221,7 +221,7 @@ class SaasFileList:
221
221
  with self._namespaces_as_dict_lock:
222
222
  self._namespaces_as_dict_cache = {
223
223
  "namespace": [
224
- ns.dict(by_alias=True, exclude_none=True)
224
+ ns.model_dump(by_alias=True, exclude_none=True)
225
225
  for ns in self.namespaces
226
226
  ]
227
227
  }
@@ -283,7 +283,7 @@ class SaasFileList:
283
283
  if app_name and saas_file.app.name != app_name:
284
284
  continue
285
285
 
286
- sf = saas_file.copy(deep=True)
286
+ sf = saas_file.model_copy(deep=True)
287
287
  if env_name:
288
288
  for rt in sf.resource_templates[:]:
289
289
  for target in rt.targets[:]:
@@ -314,7 +314,7 @@ def convert_parameters_to_json_string(root: dict[str, Any]) -> dict[str, Any]:
314
314
 
315
315
 
316
316
  def export_model(model: BaseModel) -> dict[str, Any]:
317
- return convert_parameters_to_json_string(model.dict(by_alias=True))
317
+ return convert_parameters_to_json_string(model.model_dump(by_alias=True))
318
318
 
319
319
 
320
320
  def get_saas_files(
@@ -32,7 +32,7 @@ def get_selected_app_names(
32
32
  prefix = f"{namespace.app.parent_app.name}-"
33
33
  name = f"{prefix}{namespace.app.name}"
34
34
  selected_app_names.add(name)
35
- app = namespace.app.dict(by_alias=True)
35
+ app = namespace.app.model_dump(by_alias=True)
36
36
  app["name"] = name
37
37
  apps["apps"].append(app)
38
38
 
@@ -40,7 +40,7 @@ def get_selected_app_names(
40
40
  name = f"{namespace.app.name}-{child.name}"
41
41
  if name not in selected_app_names:
42
42
  selected_app_names.add(f"{namespace.app.name}-{child.name}")
43
- child_dict = child.dict(by_alias=True)
43
+ child_dict = child.model_dump(by_alias=True)
44
44
  child_dict["name"] = name
45
45
  apps["apps"].append(child_dict)
46
46
 
@@ -31,7 +31,7 @@ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
31
31
 
32
32
 
33
33
  class UnleashTogglesIntegrationParams(PydanticRunParams):
34
- instance: str | None
34
+ instance: str | None = None
35
35
 
36
36
 
37
37
  def feature_toggle_equal(c: FeatureToggle, d: FeatureToggleUnleashV1) -> bool:
@@ -68,7 +68,9 @@ class UnleashTogglesIntegration(
68
68
  if not query_func:
69
69
  query_func = gql.get_api().query
70
70
  return {
71
- "toggles": [ft.dict() for ft in self.get_unleash_instances(query_func)],
71
+ "toggles": [
72
+ ft.model_dump() for ft in self.get_unleash_instances(query_func)
73
+ ],
72
74
  }
73
75
 
74
76
  def get_unleash_instances(
@@ -6,7 +6,7 @@ from typing import (
6
6
  )
7
7
 
8
8
  import requests
9
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, ConfigDict
10
10
 
11
11
  from reconcile.gql_definitions.acs.acs_instances import AcsInstanceV1
12
12
  from reconcile.gql_definitions.acs.acs_instances import query as acs_instances_query
@@ -19,8 +19,11 @@ class AcsBaseApi(BaseModel):
19
19
  timeout: int = 30
20
20
  session: requests.Session = requests.Session()
21
21
 
22
- class Config:
23
- arbitrary_types_allowed = True
22
+ model_config = ConfigDict(
23
+ validate_by_name=True,
24
+ validate_by_alias=True,
25
+ arbitrary_types_allowed=True,
26
+ )
24
27
 
25
28
  def __enter__(self) -> Self:
26
29
  return self
@@ -11,7 +11,7 @@ class Scope(BaseModel):
11
11
  """
12
12
 
13
13
  cluster: str
14
- namespace: str | None
14
+ namespace: str | None = None
15
15
 
16
16
 
17
17
  class PolicyCondition(BaseModel):
@@ -23,7 +23,7 @@ class PolicyCondition(BaseModel):
23
23
  """
24
24
 
25
25
  field_name: str
26
- negate: bool | None
26
+ negate: bool | None = None
27
27
  values: list[str]
28
28
 
29
29
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import logging
4
4
  import operator
5
5
  import os
6
- import re
7
6
  from functools import lru_cache
8
7
  from threading import Lock
9
8
  from typing import (
@@ -25,6 +24,7 @@ import reconcile.utils.lean_terraform_client as terraform
25
24
  from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
26
25
 
27
26
  if TYPE_CHECKING:
27
+ import re
28
28
  from collections.abc import (
29
29
  Iterable,
30
30
  Iterator,
@@ -1074,28 +1074,40 @@ class AWSApi:
1074
1074
  return [rt["RouteTableId"] for rt in vpc_route_tables]
1075
1075
 
1076
1076
  @staticmethod
1077
- def _filter_amis(
1078
- images: Iterable[ImageTypeDef], regex: str
1079
- ) -> list[dict[str, Any]]:
1080
- results = []
1081
- pattern = re.compile(regex)
1082
- for i in images:
1083
- if not re.search(pattern, i["Name"]):
1084
- continue
1085
- if i["State"] != "available":
1086
- continue
1087
- item = {"image_id": i["ImageId"], "tags": i.get("Tags", [])}
1088
- results.append(item)
1077
+ def normalize_tags(tags: Iterable[TagTypeDef]) -> dict[str, str]:
1078
+ return {tag["Key"]: tag["Value"] for tag in tags}
1089
1079
 
1090
- return results
1080
+ @staticmethod
1081
+ def _filter_amis(
1082
+ images: Iterable[ImageTypeDef],
1083
+ regex: re.Pattern,
1084
+ ) -> dict[str, dict[str, str]]:
1085
+ return {
1086
+ image["ImageId"]: AWSApi.normalize_tags(image.get("Tags", []))
1087
+ for image in images
1088
+ if regex.search(image["Name"]) and image["State"] == "available"
1089
+ }
1091
1090
 
1092
1091
  def get_amis_details(
1093
1092
  self,
1094
1093
  account: Mapping[str, Any],
1095
1094
  owner_account: Mapping[str, Any],
1096
- regex: str,
1095
+ regex: re.Pattern,
1097
1096
  region: str | None = None,
1098
- ) -> list[dict[str, Any]]:
1097
+ ) -> dict[str, dict[str, str]]:
1098
+ """
1099
+ Get AMI details for an account, find AMI name matches regex and state is available.
1100
+ Return ImageId and normalized tags.
1101
+
1102
+ Args:
1103
+ account: AWS account
1104
+ owner_account: AMI owner AWS account uid
1105
+ regex: regex to filter AMI name
1106
+ region: AWS account region
1107
+
1108
+ Returns:
1109
+ dict[str, dict[str, str]]: Key is AMI ImageId, value is AMI normalized tags.
1110
+ """
1099
1111
  ec2 = self._account_ec2_client(account["name"], region_name=region)
1100
1112
  images = self.get_account_amis(ec2, owner=owner_account["uid"])
1101
1113
  return self._filter_amis(images, regex)
@@ -1175,12 +1187,31 @@ class AWSApi:
1175
1187
  client = self._account_cloudwatch_client(account_name, region_name=region_name)
1176
1188
  client.delete_log_group(logGroupName=group_name)
1177
1189
 
1178
- def create_tag(
1179
- self, account: Mapping[str, Any], resource_id: str, tag: Mapping[str, str]
1190
+ def create_tags(
1191
+ self,
1192
+ account: Mapping[str, Any],
1193
+ resource_id: str,
1194
+ tags: Mapping[str, str],
1180
1195
  ) -> None:
1196
+ """
1197
+ Create tags on EC2 resources (AMI)
1198
+
1199
+ Args:
1200
+ account: AWS account
1201
+ resource_id: AWS resource id
1202
+ tags: tags to update
1203
+
1204
+ Returns:
1205
+ None
1206
+ """
1181
1207
  ec2 = self._account_ec2_client(account["name"])
1182
- tag_type_def: TagTypeDef = {"Key": tag["Key"], "Value": tag["Value"]}
1183
- ec2.create_tags(Resources=[resource_id], Tags=[tag_type_def])
1208
+ formatted_tags: list[TagTypeDef] = [
1209
+ {"Key": k, "Value": v} for k, v in tags.items()
1210
+ ]
1211
+ ec2.create_tags(
1212
+ Resources=[resource_id],
1213
+ Tags=formatted_tags,
1214
+ )
1184
1215
 
1185
1216
  def get_alb_network_interface_ips(
1186
1217
  self, account: awsh.Account, service_name: str
@@ -52,9 +52,11 @@ class AwsOrganizationOU(BaseModel):
52
52
  class AWSAccountStatus(BaseModel):
53
53
  id: str = Field(..., alias="Id")
54
54
  name: str = Field(..., alias="AccountName")
55
- uid: str | None = Field(alias="AccountId")
55
+ uid: str | None = Field(None, alias="AccountId")
56
56
  state: str = Field(..., alias="State")
57
- failure_reason: CreateAccountFailureReasonType | None = Field(alias="FailureReason")
57
+ failure_reason: CreateAccountFailureReasonType | None = Field(
58
+ None, alias="FailureReason"
59
+ )
58
60
 
59
61
 
60
62
  class AWSAccount(BaseModel):
reconcile/utils/binary.py CHANGED
@@ -38,10 +38,7 @@ def binary_version(
38
38
  def deco_binary_version(f: Callable) -> Callable:
39
39
  @wraps(f)
40
40
  def f_binary_version(*args: Any, **kwargs: Any) -> None:
41
- regex = re.compile(search_regex)
42
-
43
- cmd = [binary]
44
- cmd.extend(version_args)
41
+ cmd = [binary, *version_args]
45
42
  try:
46
43
  result = subprocess.run(cmd, capture_output=True, check=True)
47
44
  except subprocess.CalledProcessError as e:
@@ -50,15 +47,13 @@ def binary_version(
50
47
  )
51
48
  raise Exception(msg) from e
52
49
 
53
- found = False
54
- match = None
55
- for line in result.stdout.splitlines():
56
- match = regex.search(line.decode("utf-8"))
57
- if match is not None:
58
- found = True
59
- break
50
+ match = re.search(
51
+ search_regex,
52
+ result.stdout.decode("utf-8"),
53
+ re.MULTILINE,
54
+ )
60
55
 
61
- if not found or not match:
56
+ if match is None:
62
57
  raise Exception(
63
58
  f"Could not find version for binary '{binary}' via regex "
64
59
  f"for binary version check: "
@@ -26,7 +26,7 @@ class Snitch(BaseModel):
26
26
  interval: str
27
27
  alert_type: str
28
28
  alert_email: list[str]
29
- vault_data: str | None
29
+ vault_data: str | None = None
30
30
 
31
31
  def needs_vault_update(self) -> bool:
32
32
  return self.vault_data is not None and self.check_in_url != self.vault_data
@@ -5,7 +5,7 @@ from functools import cached_property
5
5
  from typing import Any, Self
6
6
 
7
7
  from deepdiff import DeepHash
8
- from pydantic import BaseModel
8
+ from pydantic import BaseModel, ConfigDict
9
9
 
10
10
  from reconcile.utils.datetime_util import utc_now
11
11
  from reconcile.utils.secret_reader import SecretReaderBase
@@ -17,7 +17,7 @@ CACHE_SOURCE_DIGEST_METADATA_KEY = "cache-source-digest"
17
17
  LATEST_CACHE_SOURCE_DIGEST_METADATA_KEY = "latest-cache-source-digest"
18
18
 
19
19
 
20
- class CacheKeyWithDigest(BaseModel):
20
+ class CacheKeyWithDigest(BaseModel, frozen=True):
21
21
  integration: str
22
22
  integration_version: str
23
23
  dry_run: bool
@@ -70,9 +70,6 @@ class CacheKeyWithDigest(BaseModel):
70
70
  args.append(f"--shard {self.shard}")
71
71
  return " ".join(args)
72
72
 
73
- class Config:
74
- frozen = True
75
-
76
73
 
77
74
  class CacheKey(BaseModel):
78
75
  integration: str
@@ -122,9 +119,10 @@ class CacheKey(BaseModel):
122
119
  """
123
120
  return self.cache_key_with_digest.build_cli_delete_args()
124
121
 
125
- class Config:
126
- frozen = True
127
- keep_untouched = (cached_property,)
122
+ model_config = ConfigDict(
123
+ frozen=True,
124
+ ignored_types=(cached_property,),
125
+ )
128
126
 
129
127
 
130
128
  class CacheValue(BaseModel):
@@ -168,7 +166,7 @@ class EarlyExitCache:
168
166
 
169
167
  def get(self, key: CacheKey) -> CacheValue:
170
168
  value = self.state.get(str(key))
171
- return CacheValue.parse_obj(value)
169
+ return CacheValue.model_validate(value)
172
170
 
173
171
  def set(
174
172
  self,
@@ -194,7 +192,7 @@ class EarlyExitCache:
194
192
  }
195
193
  self.state.add(
196
194
  str(key),
197
- value.dict(),
195
+ value.model_dump(),
198
196
  metadata=metadata,
199
197
  force=True,
200
198
  )
@@ -263,13 +263,13 @@ class GitLabApi:
263
263
  # we can determine if a pending MR exists based on the title
264
264
  return any(mr.title == title for mr in mrs)
265
265
 
266
- @retry()
266
+ @retry(no_retry_exceptions=(RuntimeError,))
267
267
  def get_project_maintainers(
268
268
  self, repo_url: str | None = None, query: dict | None = None
269
- ) -> list[str] | None:
269
+ ) -> list[str]:
270
270
  project = self.project if repo_url is None else self.get_project(repo_url)
271
271
  if project is None:
272
- return None
272
+ raise RuntimeError("project not found")
273
273
  members = project.members_all.list(iterator=True, query_parameters=query or {})
274
274
  return [m.username for m in members if m.access_level >= 40]
275
275
 
@@ -826,14 +826,16 @@ class GitLabApi:
826
826
  )
827
827
 
828
828
  def get_commit_sha(self, ref: str, repo_url: str) -> str:
829
- project = self.get_project(repo_url)
829
+ if not (project := self.get_project(repo_url)):
830
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
830
831
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
831
832
  return commits[0].id
832
833
 
833
834
  def repository_compare(
834
835
  self, repo_url: str, ref_from: str, ref_to: str
835
836
  ) -> list[dict[str, Any]]:
836
- project = self.get_project(repo_url)
837
+ if not (project := self.get_project(repo_url)):
838
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
837
839
  response: Any = project.repository_compare(ref_from, ref_to)
838
840
  return response.get("commits", [])
839
841
 
@@ -164,7 +164,9 @@ class GlitchtipClient(ApiBase):
164
164
  return ProjectAlert(
165
165
  **self._post(
166
166
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/",
167
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
167
+ data=alert.model_dump(
168
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
169
+ ),
168
170
  )
169
171
  )
170
172
 
@@ -183,7 +185,9 @@ class GlitchtipClient(ApiBase):
183
185
  return ProjectAlert(
184
186
  **self._put(
185
187
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/{alert.pk}/",
186
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
188
+ data=alert.model_dump(
189
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
190
+ ),
187
191
  )
188
192
  )
189
193