cumulusci-plus 5.0.18__py3-none-any.whl → 5.0.19__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.
cumulusci/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "5.0.18"
1
+ __version__ = "5.0.19"
@@ -255,7 +255,9 @@ class UnmanagedDependency(UnmanagedStaticDependency, ABC):
255
255
 
256
256
  return package_zip
257
257
 
258
- def install(self, context: BaseProjectConfig, org: OrgConfig):
258
+ def install(
259
+ self, context: BaseProjectConfig, org: OrgConfig, options: Optional[dict] = {}
260
+ ):
259
261
 
260
262
  context.logger.info(f"Deploying unmanaged metadata from {self.description}")
261
263
 
@@ -411,6 +413,7 @@ class VcsDynamicDependency(BaseVcsDynamicDependency, ABC):
411
413
  unmanaged=not managed,
412
414
  namespace_inject=namespace if namespace and managed else None,
413
415
  namespace_strip=namespace if namespace and not managed else None,
416
+ package_dependency=self.package_dependency,
414
417
  )
415
418
  ]
416
419
  return []
@@ -446,6 +449,7 @@ class VcsDynamicDependency(BaseVcsDynamicDependency, ABC):
446
449
  namespace_strip=namespace
447
450
  if namespace and not managed
448
451
  else None,
452
+ package_dependency=self.package_dependency,
449
453
  )
450
454
  )
451
455
 
@@ -493,22 +497,22 @@ class VcsDynamicDependency(BaseVcsDynamicDependency, ABC):
493
497
 
494
498
  # Look for any flow to executed in project config
495
499
  # Pre flows will run to dynamically generate metadata and deploy.
496
- deps.extend(
497
- self._flatten_dependency_flow(
498
- package_config,
499
- "dependency_flow_pre",
500
- managed=False,
501
- namespace=namespace,
502
- )
500
+ flow = self._flatten_dependency_flow(
501
+ package_config,
502
+ "dependency_flow_pre",
503
+ managed=False,
504
+ namespace=namespace,
503
505
  )
504
-
505
- # Look for subfolders under unpackaged/pre
506
- # unpackaged/pre is always deployed unmanaged, no namespace manipulation.
507
- deps.extend(
508
- self._flatten_unpackaged(
509
- repo, "unpackaged/pre", self.skip, managed=False, namespace=None
506
+ if flow:
507
+ deps.extend(flow)
508
+ else:
509
+ # Look for subfolders under unpackaged/pre
510
+ # unpackaged/pre is always deployed unmanaged, no namespace manipulation.
511
+ deps.extend(
512
+ self._flatten_unpackaged(
513
+ repo, "unpackaged/pre", self.skip, managed=False, namespace=None
514
+ )
510
515
  )
511
- )
512
516
 
513
517
  if not self.package_dependency:
514
518
  if managed:
@@ -532,25 +536,26 @@ class VcsDynamicDependency(BaseVcsDynamicDependency, ABC):
532
536
 
533
537
  # Look for any flow to executed in project config
534
538
  # Pre flows will run to dynamically generate metadata and deploy.
535
- deps.extend(
536
- self._flatten_dependency_flow(
537
- package_config,
538
- "dependency_flow_post",
539
- managed=managed,
540
- namespace=namespace,
541
- )
539
+ flow = self._flatten_dependency_flow(
540
+ package_config,
541
+ "dependency_flow_post",
542
+ managed=managed,
543
+ namespace=namespace,
542
544
  )
543
545
 
544
- # We always inject the project's namespace into unpackaged/post metadata if managed
545
- deps.extend(
546
- self._flatten_unpackaged(
547
- repo,
548
- "unpackaged/post",
549
- self.skip,
550
- managed=managed,
551
- namespace=namespace,
546
+ if flow:
547
+ deps.extend(flow)
548
+ else:
549
+ # We always inject the project's namespace into unpackaged/post metadata if managed
550
+ deps.extend(
551
+ self._flatten_unpackaged(
552
+ repo,
553
+ "unpackaged/post",
554
+ self.skip,
555
+ managed=managed,
556
+ namespace=namespace,
557
+ )
552
558
  )
553
- )
554
559
 
555
560
  return deps
556
561
 
@@ -574,6 +579,9 @@ class UnmanagedVcsDependency(UnmanagedDependency, ABC):
574
579
  namespace_inject: Optional[str] = None
575
580
  namespace_strip: Optional[str] = None
576
581
 
582
+ # Field to reference corresponding package dependency
583
+ package_dependency: Optional["BasePackageVersionDependency"] = None
584
+
577
585
  @root_validator(pre=True)
578
586
  @abstractmethod
579
587
  def sync_vcs_and_url(cls, values):
@@ -619,6 +627,19 @@ class UnmanagedVcsDependency(UnmanagedDependency, ABC):
619
627
 
620
628
  return f"{self.url}{subfolder} @{self.ref}"
621
629
 
630
+ def install(
631
+ self, context: BaseProjectConfig, org: OrgConfig, options: Optional[dict] = {}
632
+ ):
633
+ if (
634
+ self.package_dependency is not None
635
+ and not self.package_dependency.is_installable(org, options)
636
+ ):
637
+ context.logger.info(
638
+ f"{self.description} or a newer version is already deployed; skipping."
639
+ )
640
+ return
641
+ super().install(context, org)
642
+
622
643
 
623
644
  class UnmanagedVcsDependencyFlow(UnmanagedStaticDependency, ABC):
624
645
  vcs: str
@@ -632,6 +653,9 @@ class UnmanagedVcsDependencyFlow(UnmanagedStaticDependency, ABC):
632
653
  namespace_strip: Optional[str] = None
633
654
  password_env_name: Optional[str] = None
634
655
 
656
+ # Field to reference corresponding package dependency
657
+ package_dependency: Optional["BasePackageVersionDependency"] = None
658
+
635
659
  @property
636
660
  def name(self):
637
661
  return f"Deploy {self.url} Flow: {self.flow_name}"
@@ -640,7 +664,18 @@ class UnmanagedVcsDependencyFlow(UnmanagedStaticDependency, ABC):
640
664
  def description(self):
641
665
  return f"{self.url} Flow: {self.flow_name} @{self.commit}"
642
666
 
643
- def install(self, context: BaseProjectConfig, org: OrgConfig):
667
+ def install(
668
+ self, context: BaseProjectConfig, org: OrgConfig, options: Optional[dict] = {}
669
+ ):
670
+ if (
671
+ self.package_dependency is not None
672
+ and not self.package_dependency.is_installable(org, options)
673
+ ):
674
+ context.logger.info(
675
+ f"{self.description} or a newer version is already deployed; skipping."
676
+ )
677
+ return
678
+
644
679
  context.logger.info(f"Deploying dependency Flow from {self.description}")
645
680
 
646
681
  from cumulusci.utils.yaml.cumulusci_yml import VCSSourceModel
@@ -656,7 +691,6 @@ class UnmanagedVcsDependencyFlow(UnmanagedStaticDependency, ABC):
656
691
  vcs_source = VCSSource.create(context, source_model)
657
692
 
658
693
  # Fetch the data and get remote project config.
659
- context.logger.info(f"Fetching from {vcs_source}")
660
694
  project_config = vcs_source.fetch()
661
695
 
662
696
  project_config.set_keychain(context.keychain)
@@ -708,3 +742,13 @@ class UnmanagedVcsDependencyFlow(UnmanagedStaticDependency, ABC):
708
742
  coordinator.run(org)
709
743
  duration = datetime.now() - start_time
710
744
  context.logger.info(f"Ran {self.flow_name} in {format_duration(duration)}")
745
+
746
+
747
+ class BasePackageVersionDependency(StaticDependency, ABC):
748
+ @abstractmethod
749
+ def is_installable(self, org: OrgConfig, options: Optional[dict] = {}) -> bool:
750
+ pass
751
+
752
+
753
+ UnmanagedVcsDependency.update_forward_refs()
754
+ UnmanagedVcsDependencyFlow.update_forward_refs()
@@ -20,7 +20,7 @@ from cumulusci.utils import download_extract_zip
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
- class PackageNamespaceVersionDependency(base_dependency.StaticDependency):
23
+ class PackageNamespaceVersionDependency(base_dependency.BasePackageVersionDependency):
24
24
  """Static dependency on a package identified by namespace and version number."""
25
25
 
26
26
  namespace: str
@@ -34,6 +34,21 @@ class PackageNamespaceVersionDependency(base_dependency.StaticDependency):
34
34
  def package(self):
35
35
  return self.package_name or self.namespace or "Unknown Package"
36
36
 
37
+ def is_installable(self, org: OrgConfig, options: Optional[dict] = {}) -> bool:
38
+ if "Beta" in self.version:
39
+ version_string = self.version.split(" ")[0]
40
+ beta = self.version.split(" ")[-1].strip(")")
41
+ version = f"{version_string}b{beta}"
42
+ else:
43
+ version = self.version
44
+
45
+ if org.has_minimum_package_version(
46
+ self.namespace,
47
+ version,
48
+ ):
49
+ return False
50
+ return True
51
+
37
52
  def install(
38
53
  self,
39
54
  context: BaseProjectConfig,
@@ -48,17 +63,7 @@ class PackageNamespaceVersionDependency(base_dependency.StaticDependency):
48
63
  if not retry_options:
49
64
  retry_options = DEFAULT_PACKAGE_RETRY_OPTIONS
50
65
 
51
- if "Beta" in self.version:
52
- version_string = self.version.split(" ")[0]
53
- beta = self.version.split(" ")[-1].strip(")")
54
- version = f"{version_string}b{beta}"
55
- else:
56
- version = self.version
57
-
58
- if org.has_minimum_package_version(
59
- self.namespace,
60
- version,
61
- ):
66
+ if not self.is_installable(org):
62
67
  context.logger.info(
63
68
  f"{self} or a newer version is already installed; skipping."
64
69
  )
@@ -83,7 +88,7 @@ class PackageNamespaceVersionDependency(base_dependency.StaticDependency):
83
88
  return f"{self.package} {self.version}"
84
89
 
85
90
 
86
- class PackageVersionIdDependency(base_dependency.StaticDependency):
91
+ class PackageVersionIdDependency(base_dependency.BasePackageVersionDependency):
87
92
  """Static dependency on a package identified by 04t version id."""
88
93
 
89
94
  version_id: str
@@ -96,6 +101,14 @@ class PackageVersionIdDependency(base_dependency.StaticDependency):
96
101
  def package(self):
97
102
  return self.package_name or "Unknown Package"
98
103
 
104
+ def is_installable(self, org: OrgConfig, options: Optional[dict] = {}) -> bool:
105
+ if any(
106
+ self.version_id == v.id
107
+ for v in itertools.chain(*org.installed_packages.values())
108
+ ) and not options.get("force_pre_post_install"):
109
+ return False
110
+ return True
111
+
99
112
  def install(
100
113
  self,
101
114
  context: BaseProjectConfig,
@@ -110,10 +123,7 @@ class PackageVersionIdDependency(base_dependency.StaticDependency):
110
123
  if not retry_options:
111
124
  retry_options = DEFAULT_PACKAGE_RETRY_OPTIONS
112
125
 
113
- if any(
114
- self.version_id == v.id
115
- for v in itertools.chain(*org.installed_packages.values())
116
- ):
126
+ if not self.is_installable(org):
117
127
  context.logger.info(
118
128
  f"{self} or a newer version is already installed; skipping."
119
129
  )
@@ -256,6 +256,8 @@ class TestGitHubDynamicDependency:
256
256
  namespace="bar", version="2.0"
257
257
  )
258
258
 
259
+ root_repo = PackageNamespaceVersionDependency(namespace="bar", version="2.0")
260
+
259
261
  assert gh.flatten(project_config) == [
260
262
  GitHubDynamicDependency(
261
263
  github="https://github.com/SFDO-Tooling/DependencyRepo",
@@ -266,20 +268,23 @@ class TestGitHubDynamicDependency:
266
268
  subfolder="unpackaged/pre/first",
267
269
  unmanaged=True,
268
270
  ref="aaaaa",
271
+ package_dependency=root_repo,
269
272
  ),
270
273
  UnmanagedGitHubRefDependency(
271
274
  github="https://github.com/SFDO-Tooling/RootRepo",
272
275
  subfolder="unpackaged/pre/second",
273
276
  unmanaged=True,
274
277
  ref="aaaaa",
278
+ package_dependency=root_repo,
275
279
  ),
276
- PackageNamespaceVersionDependency(namespace="bar", version="2.0"),
280
+ root_repo,
277
281
  UnmanagedGitHubRefDependency(
278
282
  github="https://github.com/SFDO-Tooling/RootRepo",
279
283
  subfolder="unpackaged/post/first",
280
284
  unmanaged=False,
281
285
  ref="aaaaa",
282
286
  namespace_inject="bar",
287
+ package_dependency=root_repo,
283
288
  ),
284
289
  ]
285
290
 
@@ -295,6 +300,8 @@ class TestGitHubDynamicDependency:
295
300
  namespace="bar", version="2.0"
296
301
  )
297
302
 
303
+ root_repo = PackageNamespaceVersionDependency(namespace="bar", version="2.0")
304
+
298
305
  assert gh.flatten(project_config) == [
299
306
  GitHubDynamicDependency(
300
307
  github="https://github.com/SFDO-Tooling/DependencyRepo",
@@ -305,14 +312,16 @@ class TestGitHubDynamicDependency:
305
312
  subfolder="unpackaged/pre/second",
306
313
  unmanaged=True,
307
314
  ref="aaaaa",
315
+ package_dependency=root_repo,
308
316
  ),
309
- PackageNamespaceVersionDependency(namespace="bar", version="2.0"),
317
+ root_repo,
310
318
  UnmanagedGitHubRefDependency(
311
319
  github="https://github.com/SFDO-Tooling/RootRepo",
312
320
  subfolder="unpackaged/post/first",
313
321
  unmanaged=False,
314
322
  ref="aaaaa",
315
323
  namespace_inject="bar",
324
+ package_dependency=root_repo,
316
325
  ),
317
326
  ]
318
327
 
@@ -1086,14 +1095,11 @@ class TestUnmanagedVcsDependencyFlow:
1086
1095
 
1087
1096
  # Verify logging
1088
1097
  assert (
1089
- mock_context.logger.info.call_count == 3
1098
+ mock_context.logger.info.call_count == 2
1090
1099
  ) # Initial log, fetching log, final log
1091
1100
  mock_context.logger.info.assert_any_call(
1092
1101
  "Deploying dependency Flow from https://github.com/test/repo Flow: install_deps @abc123"
1093
1102
  )
1094
- mock_context.logger.info.assert_any_call(
1095
- f"Fetching from {mock_vcs_source_instance}"
1096
- )
1097
1103
  mock_context.logger.info.assert_any_call("Ran install_deps in 5.2s")
1098
1104
 
1099
1105
  # Verify datetime and format_duration calls
@@ -767,6 +767,16 @@ class TestStaticDependencyResolution:
767
767
 
768
768
  gh = GitHubDynamicDependency(github="https://github.com/SFDO-Tooling/RootRepo")
769
769
 
770
+ dep_repo = PackageNamespaceVersionDependency(
771
+ namespace="foo",
772
+ version="1.1",
773
+ package_name="DependencyRepo",
774
+ password_env_name="DEP_PW",
775
+ )
776
+ root_repo = PackageNamespaceVersionDependency(
777
+ namespace="bar", version="2.0", package_name="RootRepo"
778
+ )
779
+
770
780
  assert get_static_dependencies(
771
781
  project_config,
772
782
  dependencies=[gh],
@@ -777,41 +787,39 @@ class TestStaticDependencyResolution:
777
787
  subfolder="unpackaged/pre/top",
778
788
  unmanaged=True,
779
789
  ref="tag_sha",
790
+ package_dependency=dep_repo,
780
791
  ),
781
- PackageNamespaceVersionDependency(
782
- namespace="foo",
783
- version="1.1",
784
- package_name="DependencyRepo",
785
- password_env_name="DEP_PW",
786
- ),
792
+ dep_repo,
787
793
  UnmanagedGitHubRefDependency(
788
794
  github="https://github.com/SFDO-Tooling/DependencyRepo",
789
795
  subfolder="unpackaged/post/top",
790
796
  unmanaged=False,
791
797
  ref="tag_sha",
792
798
  namespace_inject="foo",
799
+ package_dependency=dep_repo,
793
800
  ),
794
801
  UnmanagedGitHubRefDependency(
795
802
  github="https://github.com/SFDO-Tooling/RootRepo",
796
803
  subfolder="unpackaged/pre/first",
797
804
  unmanaged=True,
798
805
  ref="tag_sha",
806
+ package_dependency=root_repo,
799
807
  ),
800
808
  UnmanagedGitHubRefDependency(
801
809
  github="https://github.com/SFDO-Tooling/RootRepo",
802
810
  subfolder="unpackaged/pre/second",
803
811
  unmanaged=True,
804
812
  ref="tag_sha",
813
+ package_dependency=root_repo,
805
814
  ),
806
- PackageNamespaceVersionDependency(
807
- namespace="bar", version="2.0", package_name="RootRepo"
808
- ),
815
+ root_repo,
809
816
  UnmanagedGitHubRefDependency(
810
817
  github="https://github.com/SFDO-Tooling/RootRepo",
811
818
  subfolder="unpackaged/post/first",
812
819
  unmanaged=False,
813
820
  ref="tag_sha",
814
821
  namespace_inject="bar",
822
+ package_dependency=root_repo,
815
823
  ),
816
824
  ]
817
825
 
@@ -855,50 +863,58 @@ version_id: 04t000000000000""",
855
863
  pins=pins,
856
864
  )
857
865
 
866
+ dep_repo = PackageNamespaceVersionDependency(
867
+ namespace="foo",
868
+ version="1.0", # from the pinned tag
869
+ package_name="DependencyRepo",
870
+ version_id="04t000000000000",
871
+ )
872
+ root_repo = PackageNamespaceVersionDependency(
873
+ namespace="bar",
874
+ version="1.5", # From pinned tag
875
+ package_name="RootRepo",
876
+ version_id="04t000000000000",
877
+ )
878
+
858
879
  assert deps == [
859
880
  UnmanagedGitHubRefDependency(
860
881
  github="https://github.com/SFDO-Tooling/DependencyRepo",
861
882
  subfolder="unpackaged/pre/top",
862
883
  unmanaged=True,
863
884
  ref="tag_sha",
885
+ package_dependency=dep_repo,
864
886
  ),
865
- PackageNamespaceVersionDependency(
866
- namespace="foo",
867
- version="1.0", # from the pinned tag
868
- package_name="DependencyRepo",
869
- version_id="04t000000000000",
870
- ),
887
+ dep_repo,
871
888
  UnmanagedGitHubRefDependency(
872
889
  github="https://github.com/SFDO-Tooling/DependencyRepo",
873
890
  subfolder="unpackaged/post/top",
874
891
  unmanaged=False,
875
892
  ref="tag_sha",
876
893
  namespace_inject="foo",
894
+ package_dependency=dep_repo,
877
895
  ),
878
896
  UnmanagedGitHubRefDependency(
879
897
  github="https://github.com/SFDO-Tooling/RootRepo",
880
898
  subfolder="unpackaged/pre/first",
881
899
  unmanaged=True,
882
900
  ref="tag_sha",
901
+ package_dependency=root_repo,
883
902
  ),
884
903
  UnmanagedGitHubRefDependency(
885
904
  github="https://github.com/SFDO-Tooling/RootRepo",
886
905
  subfolder="unpackaged/pre/second",
887
906
  unmanaged=True,
888
907
  ref="tag_sha",
908
+ package_dependency=root_repo,
889
909
  ),
890
- PackageNamespaceVersionDependency(
891
- namespace="bar",
892
- version="1.5", # From pinned tag
893
- package_name="RootRepo",
894
- version_id="04t000000000000",
895
- ),
910
+ root_repo,
896
911
  UnmanagedGitHubRefDependency(
897
912
  github="https://github.com/SFDO-Tooling/RootRepo",
898
913
  subfolder="unpackaged/post/first",
899
914
  unmanaged=False,
900
915
  ref="tag_sha",
901
916
  namespace_inject="bar",
917
+ package_dependency=root_repo,
902
918
  ),
903
919
  ]
904
920
 
@@ -938,6 +954,16 @@ version_id: 04t000000000000""",
938
954
 
939
955
  gh = GitHubDynamicDependency(github="https://github.com/SFDO-Tooling/RootRepo")
940
956
 
957
+ root_repo = PackageNamespaceVersionDependency(
958
+ namespace="bar", version="2.0", package_name="RootRepo"
959
+ )
960
+ dep_repo = PackageNamespaceVersionDependency(
961
+ namespace="foo",
962
+ version="1.1", # from the pinned tag
963
+ package_name="DependencyRepo",
964
+ password_env_name="DEP_PW",
965
+ )
966
+
941
967
  assert get_static_dependencies(
942
968
  project_config,
943
969
  dependencies=[gh],
@@ -949,6 +975,7 @@ version_id: 04t000000000000""",
949
975
  subfolder="unpackaged/pre/top",
950
976
  unmanaged=True,
951
977
  ref="tag_sha",
978
+ package_dependency=dep_repo,
952
979
  ),
953
980
  UnmanagedGitHubRefDependency(
954
981
  github="https://github.com/SFDO-Tooling/DependencyRepo",
@@ -956,28 +983,30 @@ version_id: 04t000000000000""",
956
983
  unmanaged=False,
957
984
  ref="tag_sha",
958
985
  namespace_inject="foo",
986
+ package_dependency=dep_repo,
959
987
  ),
960
988
  UnmanagedGitHubRefDependency(
961
989
  github="https://github.com/SFDO-Tooling/RootRepo",
962
990
  subfolder="unpackaged/pre/first",
963
991
  unmanaged=True,
964
992
  ref="tag_sha",
993
+ package_dependency=root_repo,
965
994
  ),
966
995
  UnmanagedGitHubRefDependency(
967
996
  github="https://github.com/SFDO-Tooling/RootRepo",
968
997
  subfolder="unpackaged/pre/second",
969
998
  unmanaged=True,
970
999
  ref="tag_sha",
1000
+ package_dependency=root_repo,
971
1001
  ),
972
- PackageNamespaceVersionDependency(
973
- namespace="bar", version="2.0", package_name="RootRepo"
974
- ),
1002
+ root_repo,
975
1003
  UnmanagedGitHubRefDependency(
976
1004
  github="https://github.com/SFDO-Tooling/RootRepo",
977
1005
  subfolder="unpackaged/post/first",
978
1006
  unmanaged=False,
979
1007
  ref="tag_sha",
980
1008
  namespace_inject="bar",
1009
+ package_dependency=root_repo,
981
1010
  ),
982
1011
  ]
983
1012
 
@@ -994,6 +1023,10 @@ version_id: 04t000000000000""",
994
1023
 
995
1024
  gh = GitHubDynamicDependency(github="https://github.com/SFDO-Tooling/RootRepo")
996
1025
 
1026
+ root_repo = PackageNamespaceVersionDependency(
1027
+ namespace="bar", version="2.0", package_name="RootRepo"
1028
+ )
1029
+
997
1030
  assert get_static_dependencies(
998
1031
  project_config,
999
1032
  dependencies=[gh],
@@ -1007,21 +1040,22 @@ version_id: 04t000000000000""",
1007
1040
  subfolder="unpackaged/pre/first",
1008
1041
  unmanaged=True,
1009
1042
  ref="tag_sha",
1043
+ package_dependency=root_repo,
1010
1044
  ),
1011
1045
  UnmanagedGitHubRefDependency(
1012
1046
  github="https://github.com/SFDO-Tooling/RootRepo",
1013
1047
  subfolder="unpackaged/pre/second",
1014
1048
  unmanaged=True,
1015
1049
  ref="tag_sha",
1050
+ package_dependency=root_repo,
1016
1051
  ),
1017
- PackageNamespaceVersionDependency(
1018
- namespace="bar", version="2.0", package_name="RootRepo"
1019
- ),
1052
+ root_repo,
1020
1053
  UnmanagedGitHubRefDependency(
1021
1054
  github="https://github.com/SFDO-Tooling/RootRepo",
1022
1055
  subfolder="unpackaged/post/first",
1023
1056
  unmanaged=False,
1024
1057
  ref="tag_sha",
1025
1058
  namespace_inject="bar",
1059
+ package_dependency=root_repo,
1026
1060
  ),
1027
1061
  ]
cumulusci/cumulusci.yml CHANGED
@@ -823,10 +823,6 @@ tasks:
823
823
  class_path: cumulusci.tasks.salesforce.SfDataCommands.DataCreateRecordTask
824
824
  description: "Executes the `sf data create` command against an org"
825
825
  group: SalesforceDX Data Commands
826
- get_first_item_from_record_list:
827
- class_path: cumulusci.tasks.utility.data_management.GetFirstItemFromRecordListTask
828
- description: "Get the first item from a record list"
829
- group: Utilities
830
826
  flows:
831
827
  ci_beta:
832
828
  group: Continuous Integration
@@ -164,3 +164,237 @@ class DataDeleteRecordTask(SfDataToolingAPISupportedCommands):
164
164
 
165
165
  def _run_task(self):
166
166
  return super()._run_task()
167
+
168
+
169
+ class DataUpdateRecordTask(SfDataToolingAPISupportedCommands):
170
+ class Options(SfDataToolingAPISupportedCommands.Options):
171
+ sobject: str = Field(
172
+ ...,
173
+ description="API name of the Salesforce or Tooling API object that you're updating a record from.",
174
+ )
175
+ record_id: str = Field(None, description="ID of the record you’re updating.")
176
+ where: str = Field(
177
+ None,
178
+ description="List of <fieldName>=<value> pairs that identify the record you want to update.",
179
+ )
180
+ values: str = Field(
181
+ ...,
182
+ description="Values for the flags in the form <fieldName>=<value>, separate multiple pairs with spaces.",
183
+ )
184
+
185
+ def _init_task(self):
186
+ super()._init_task()
187
+ self.data_command += "update record"
188
+
189
+ def _init_options(self, kwargs):
190
+ super()._init_options(kwargs)
191
+ if self.parsed_options.sobject:
192
+ self.args.extend(["--sobject", self.parsed_options.sobject])
193
+ if self.parsed_options.record_id:
194
+ self.args.extend(["--record-id", self.parsed_options.record_id])
195
+ if self.parsed_options.where:
196
+ self.args.extend(["--where", self.parsed_options.where])
197
+ if self.parsed_options.values:
198
+ self.args.extend(["--values", self.parsed_options.values])
199
+
200
+ def _run_task(self):
201
+ return super()._run_task()
202
+
203
+
204
+ class DataGetRecordTask(SfDataToolingAPISupportedCommands):
205
+ class Options(SfDataToolingAPISupportedCommands.Options):
206
+ sobject: str = Field(
207
+ ...,
208
+ description="API name of the Salesforce or Tooling API object that you're fetching a record from.",
209
+ )
210
+ record_id: str = Field(None, description="ID of the record you’re fetching.")
211
+ where: str = Field(
212
+ None,
213
+ description="List of <fieldName>=<value> pairs that identify the record you want to fetch.",
214
+ )
215
+
216
+ def _init_task(self):
217
+ super()._init_task()
218
+ self.data_command += "get record"
219
+
220
+ def _init_options(self, kwargs):
221
+ super()._init_options(kwargs)
222
+ if self.parsed_options.sobject:
223
+ self.args.extend(["--sobject", self.parsed_options.sobject])
224
+ if self.parsed_options.record_id:
225
+ self.args.extend(["--record-id", self.parsed_options.record_id])
226
+ if self.parsed_options.where:
227
+ self.args.extend(["--where", self.parsed_options.where])
228
+
229
+ def _run_task(self):
230
+ return super()._run_task()
231
+
232
+
233
+ class DataQueryResumeTask(SfDataToolingAPISupportedCommands):
234
+ class Options(SfDataToolingAPISupportedCommands.Options):
235
+ bulk_query_id: str = Field(
236
+ ...,
237
+ description="The 18-character ID of the bulk query to resume.",
238
+ )
239
+ result_format: str = Field(
240
+ None,
241
+ description="Format to display the results; the --json_output flag overrides this flag. Permissible values are: human, csv, json.",
242
+ )
243
+
244
+ def _init_task(self):
245
+ super()._init_task()
246
+ self.data_command += "query resume"
247
+
248
+ def _init_options(self, kwargs):
249
+ super()._init_options(kwargs)
250
+ if self.parsed_options.bulk_query_id:
251
+ self.args.extend(["--bulk-query-id", self.parsed_options.bulk_query_id])
252
+ if self.parsed_options.result_format:
253
+ self.args.extend(["--result-format", self.parsed_options.result_format])
254
+
255
+ def _run_task(self):
256
+ return super()._run_task()
257
+
258
+
259
+ class DataDeleteBulkTask(SfDataCommands):
260
+ class Options(SfDataCommands.Options):
261
+ sobject: str = Field(
262
+ ...,
263
+ description="The API name of the object for the bulk job.",
264
+ )
265
+ file: str = Field(
266
+ ...,
267
+ description="The path to the CSV file that contains the IDs of the records to delete.",
268
+ )
269
+ wait: int = Field(
270
+ None,
271
+ description="The number of minutes to wait for the command to complete.",
272
+ )
273
+
274
+ def _init_task(self):
275
+ super()._init_task()
276
+ self.data_command += "delete bulk"
277
+
278
+ def _init_options(self, kwargs):
279
+ super()._init_options(kwargs)
280
+ if self.parsed_options.sobject:
281
+ self.args.extend(["--sobject", self.parsed_options.sobject])
282
+ if self.parsed_options.file:
283
+ self.args.extend(["--file", self.parsed_options.file])
284
+ if self.parsed_options.wait:
285
+ self.args.extend(["--wait", str(self.parsed_options.wait)])
286
+
287
+ def _run_task(self):
288
+ return super()._run_task()
289
+
290
+
291
+ class DataUpsertBulkTask(SfDataCommands):
292
+ class Options(SfDataCommands.Options):
293
+ sobject: str = Field(
294
+ ...,
295
+ description="The API name of the object for the bulk job.",
296
+ )
297
+ file: str = Field(
298
+ ...,
299
+ description="The path to the CSV file that contains the records to upsert.",
300
+ )
301
+ external_id_field: str = Field(
302
+ ...,
303
+ description="The API name of the external ID field for the upsert.",
304
+ )
305
+ wait: int = Field(
306
+ None,
307
+ description="The number of minutes to wait for the command to complete.",
308
+ )
309
+
310
+ def _init_task(self):
311
+ super()._init_task()
312
+ self.data_command += "upsert bulk"
313
+
314
+ def _init_options(self, kwargs):
315
+ super()._init_options(kwargs)
316
+ if self.parsed_options.sobject:
317
+ self.args.extend(["--sobject", self.parsed_options.sobject])
318
+ if self.parsed_options.file:
319
+ self.args.extend(["--file", self.parsed_options.file])
320
+ if self.parsed_options.external_id_field:
321
+ self.args.extend(
322
+ ["--external-id-field", self.parsed_options.external_id_field]
323
+ )
324
+ if self.parsed_options.wait:
325
+ self.args.extend(["--wait", str(self.parsed_options.wait)])
326
+
327
+ def _run_task(self):
328
+ return super()._run_task()
329
+
330
+
331
+ class DataImportTreeTask(SfDataCommands):
332
+ class Options(SfDataCommands.Options):
333
+ files: list = Field(
334
+ None,
335
+ description="A list of paths to sObject Tree API plan definition files.",
336
+ )
337
+ plan: str = Field(
338
+ None,
339
+ description="The path to a plan definition file.",
340
+ )
341
+ content_type_map: str = Field(
342
+ None,
343
+ description="A mapping of file extensions to content types.",
344
+ )
345
+
346
+ def _init_task(self):
347
+ super()._init_task()
348
+ self.data_command += "import tree"
349
+
350
+ def _init_options(self, kwargs):
351
+ super()._init_options(kwargs)
352
+ if self.parsed_options.files:
353
+ self.args.extend(["--files", ",".join(self.parsed_options.files)])
354
+ if self.parsed_options.plan:
355
+ self.args.extend(["--plan", self.parsed_options.plan])
356
+ if self.parsed_options.content_type_map:
357
+ self.args.extend(
358
+ ["--content-type-map", self.parsed_options.content_type_map]
359
+ )
360
+
361
+ def _run_task(self):
362
+ return super()._run_task()
363
+
364
+
365
+ class DataExportTreeTask(SfDataCommands):
366
+ class Options(SfDataCommands.Options):
367
+ query: str = Field(
368
+ ...,
369
+ description="A SOQL query that retrieves the records you want to export.",
370
+ )
371
+ plan: bool = Field(
372
+ False,
373
+ description="Generate a plan definition file.",
374
+ )
375
+ prefix: str = Field(
376
+ None,
377
+ description="The prefix for the exported data files.",
378
+ )
379
+ output_dir: str = Field(
380
+ None,
381
+ description="The directory to store the exported files.",
382
+ )
383
+
384
+ def _init_task(self):
385
+ super()._init_task()
386
+ self.data_command += "export tree"
387
+
388
+ def _init_options(self, kwargs):
389
+ super()._init_options(kwargs)
390
+ if self.parsed_options.query:
391
+ self.args.extend(["--query", self.parsed_options.query])
392
+ if self.parsed_options.plan:
393
+ self.args.extend(["--plan"])
394
+ if self.parsed_options.prefix:
395
+ self.args.extend(["--prefix", self.parsed_options.prefix])
396
+ if self.parsed_options.output_dir:
397
+ self.args.extend(["--output-dir", self.parsed_options.output_dir])
398
+
399
+ def _run_task(self):
400
+ return super()._run_task()
@@ -23,6 +23,9 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
23
23
  "description": "The description of the the new profile",
24
24
  "required": False,
25
25
  },
26
+ "collision_check": {
27
+ "description": "Performs a collision check with metadata already present in the target org. Defaults to True"
28
+ },
26
29
  }
27
30
 
28
31
  def _init_options(self, kwargs):
@@ -32,6 +35,7 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
32
35
  "Either the name or the ID of the user license must be set."
33
36
  )
34
37
  self.license = self.options.get("license", "Salesforce")
38
+ self.sf = None
35
39
 
36
40
  def _run_task(self):
37
41
 
@@ -39,6 +43,15 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
39
43
  self.description = self.options.get("description") or ""
40
44
  self.license_id = self.options.get("license_id")
41
45
 
46
+ if self.options.get("collision_check", True):
47
+ profile_id = self._get_profile_id(self.name)
48
+ if profile_id:
49
+ self.logger.info(
50
+ f"Profile '{self.name}' already exists with id: {profile_id}"
51
+ )
52
+ self.return_values = {"profile_id": profile_id}
53
+ return profile_id
54
+
42
55
  if not self.license_id:
43
56
  self.license_id = self._get_user_license_id(self.license)
44
57
 
@@ -48,22 +61,38 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
48
61
  self.logger.info(f"Profile '{self.name}' created with id: {result}")
49
62
  return result
50
63
 
64
+ def _get_profile_id(self, profile_name):
65
+ """Returns the Id of a Profile from a given Name"""
66
+ res = self._query_sf(
67
+ f"SELECT Id, Name FROM Profile WHERE FullName = '{profile_name}' LIMIT 1"
68
+ )
69
+ if res["records"]:
70
+ return res["records"][0]["Id"]
71
+
72
+ self.logger.info(f"Profile name '{profile_name}' was not found.")
73
+ return None
74
+
51
75
  def _get_user_license_id(self, license_name):
52
76
  """Returns the Id of a UserLicense from a given Name"""
53
- self.sf = get_simple_salesforce_connection(
54
- self.project_config,
55
- self.org_config,
56
- api_version=self.org_config.latest_api_version,
57
- base_url=None,
58
- )
59
- res = self.sf.query(
77
+ res = self._query_sf(
60
78
  f"SELECT Id, Name FROM UserLicense WHERE Name = '{license_name}' LIMIT 1"
61
79
  )
80
+
62
81
  if res["records"]:
63
82
  return res["records"][0]["Id"]
64
83
  else:
65
84
  raise TaskOptionsError(f"License name '{license_name}' was not found.")
66
85
 
86
+ def _query_sf(self, query):
87
+ self.sf = self.sf or get_simple_salesforce_connection(
88
+ self.project_config,
89
+ self.org_config,
90
+ api_version=self.org_config.latest_api_version,
91
+ base_url=None,
92
+ )
93
+ res = self.sf.query(query)
94
+ return res
95
+
67
96
  def _get_api(self):
68
97
  return self.api_class(
69
98
  self,
@@ -95,6 +95,15 @@ def test_run_task_success():
95
95
  json={
96
96
  "done": True,
97
97
  "totalSize": 1,
98
+ "records": [],
99
+ },
100
+ )
101
+ responses.add(
102
+ responses.GET,
103
+ query_url,
104
+ json={
105
+ "done": True,
106
+ "totalSize": 0,
98
107
  "records": [
99
108
  {
100
109
  "attributes": {
@@ -115,9 +124,12 @@ def test_run_task_success():
115
124
  result = task._run_task()
116
125
  assert result == "001R0000029IyDPIA0"
117
126
  assert responses.calls[0].request.params == {
127
+ "q": "SELECT Id, Name FROM Profile WHERE FullName = 'Test Profile Name' LIMIT 1"
128
+ }
129
+ assert responses.calls[1].request.params == {
118
130
  "q": "SELECT Id, Name FROM UserLicense WHERE Name = 'Foo' LIMIT 1"
119
131
  }
120
- soap_body = responses.calls[1].request.body
132
+ soap_body = responses.calls[2].request.body
121
133
  assert "<Name>Test Profile Name</Name>" in str(soap_body)
122
134
  assert "<UserLicenseId>10056000000VGjUAAW</UserLicenseId>" in str(soap_body)
123
135
  assert "<Description>Have fun stormin da castle</Description>" in str(soap_body)
@@ -135,6 +147,15 @@ def test_run_task_fault():
135
147
  )
136
148
  task.org_config._latest_api_version = "53.0"
137
149
 
150
+ responses.add(
151
+ responses.GET,
152
+ "https://test.salesforce.com/services/data/v53.0/query/",
153
+ json={
154
+ "done": True,
155
+ "totalSize": 0,
156
+ "records": [],
157
+ },
158
+ )
138
159
  responses.add(
139
160
  responses.POST,
140
161
  "https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
@@ -157,6 +178,17 @@ def test_run_task_field_error():
157
178
  },
158
179
  )
159
180
  task.org_config._latest_api_version = "53.0"
181
+
182
+ responses.add(
183
+ responses.GET,
184
+ "https://test.salesforce.com/services/data/v53.0/query/",
185
+ json={
186
+ "done": True,
187
+ "totalSize": 0,
188
+ "records": [],
189
+ },
190
+ )
191
+
160
192
  responses.add(
161
193
  responses.POST,
162
194
  "https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
@@ -180,6 +212,16 @@ def test_run_task_error():
180
212
  )
181
213
  task.org_config._latest_api_version = "53.0"
182
214
 
215
+ responses.add(
216
+ responses.GET,
217
+ "https://test.salesforce.com/services/data/v53.0/query/",
218
+ json={
219
+ "done": True,
220
+ "totalSize": 0,
221
+ "records": [],
222
+ },
223
+ )
224
+
183
225
  responses.add(
184
226
  responses.POST,
185
227
  "https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
@@ -200,3 +242,85 @@ def test_task_options_error():
200
242
  "description": "Foo",
201
243
  },
202
244
  )
245
+
246
+
247
+ @responses.activate
248
+ def test_run_task_success_with_collision_check():
249
+ query_url = "https://test.salesforce.com/services/data/v53.0/query/"
250
+
251
+ task = create_task(
252
+ CreateBlankProfile,
253
+ {
254
+ "license": "Foo",
255
+ "name": "Test Profile Name",
256
+ "description": "Have fun stormin da castle",
257
+ },
258
+ )
259
+ task.org_config._latest_api_version = "53.0"
260
+
261
+ responses.add(
262
+ responses.GET,
263
+ query_url,
264
+ json={
265
+ "done": True,
266
+ "totalSize": 1,
267
+ "records": [
268
+ {
269
+ "attributes": {
270
+ "type": "Profile",
271
+ "url": "/services/data/v53.0/sobjects/Profile/10056000000VGjUAAW",
272
+ },
273
+ "Id": "10056000000VGjUAAW",
274
+ "Name": "Test Profile Name",
275
+ }
276
+ ],
277
+ },
278
+ )
279
+
280
+ result = task._run_task()
281
+ assert result == "10056000000VGjUAAW"
282
+ assert responses.calls[0].request.params == {
283
+ "q": "SELECT Id, Name FROM Profile WHERE FullName = 'Test Profile Name' LIMIT 1"
284
+ }
285
+
286
+
287
+ @responses.activate
288
+ def test_run_task_with_invalid_license():
289
+ query_url = "https://test.salesforce.com/services/data/v53.0/query/"
290
+
291
+ task = create_task(
292
+ CreateBlankProfile,
293
+ {
294
+ "license": "Foo",
295
+ "name": "Test Profile Name",
296
+ "description": "Have fun stormin da castle",
297
+ },
298
+ )
299
+ task.org_config._latest_api_version = "53.0"
300
+
301
+ responses.add(
302
+ responses.GET,
303
+ query_url,
304
+ json={
305
+ "done": True,
306
+ "totalSize": 1,
307
+ "records": [],
308
+ },
309
+ )
310
+ responses.add(
311
+ responses.GET,
312
+ query_url,
313
+ json={
314
+ "done": True,
315
+ "totalSize": 0,
316
+ "records": [],
317
+ },
318
+ )
319
+ responses.add(
320
+ responses.POST,
321
+ "https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
322
+ RESPONSE_SUCCESS,
323
+ )
324
+ with pytest.raises(TaskOptionsError) as e:
325
+ task._run_task()
326
+ assert "License name 'Foo' was not found." in str(e)
@@ -362,7 +362,7 @@ def test_install_dependency_installs_unmanaged():
362
362
 
363
363
  task._install_dependency(task.dependencies[0])
364
364
  task.dependencies[0].install.assert_called_once_with(
365
- task.project_config, task.org_config
365
+ task.project_config, task.org_config, task.options
366
366
  )
367
367
 
368
368
 
@@ -64,6 +64,9 @@ class UpdateDependencies(BaseSalesforceTask):
64
64
  "base_package_url_format": {
65
65
  "description": "If `interactive` is set to True, display package Ids using a format string ({} will be replaced with the package Id)."
66
66
  },
67
+ "force_pre_post_install": {
68
+ "description": "Forces the pre-install and post-install steps to be run. Defaults to False."
69
+ },
67
70
  **{k: v for k, v in PACKAGE_INSTALL_TASK_OPTIONS.items() if k != "password"},
68
71
  }
69
72
 
@@ -220,8 +223,11 @@ class UpdateDependencies(BaseSalesforceTask):
220
223
  if not click.confirm("Continue to install dependencies?", default=True):
221
224
  raise CumulusCIException("Dependency installation was canceled.")
222
225
 
223
- for d in dependencies:
224
- self._install_dependency(d)
226
+ if dependencies:
227
+ self.logger.info("Installing dependencies:")
228
+
229
+ for d in dependencies:
230
+ self._install_dependency(d)
225
231
 
226
232
  self.org_config.reset_installed_packages()
227
233
 
@@ -233,7 +239,7 @@ class UpdateDependencies(BaseSalesforceTask):
233
239
  self.project_config, self.org_config, self.install_options
234
240
  )
235
241
  else:
236
- dependency.install(self.project_config, self.org_config)
242
+ dependency.install(self.project_config, self.org_config, self.options)
237
243
 
238
244
  def freeze(self, step):
239
245
  if self.options["interactive"]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cumulusci-plus
3
- Version: 5.0.18
3
+ Version: 5.0.19
4
4
  Summary: Build and release tools for Salesforce developers
5
5
  Project-URL: Homepage, https://github.com/jorgesolebur/CumulusCI
6
6
  Project-URL: Changelog, https://cumulusci.readthedocs.io/en/stable/history.html
@@ -127,7 +127,7 @@ license](https://github.com/SFDO-Tooling/CumulusCI/blob/main/LICENSE)
127
127
  and is not covered by the Salesforce Master Subscription Agreement.
128
128
 
129
129
  <!-- Changelog -->
130
- ## v5.0.18 (2025-08-23)
130
+ ## v5.0.19 (2025-09-04)
131
131
 
132
132
  <!-- Release notes generated using configuration in .github/release.yml at main -->
133
133
 
@@ -135,6 +135,6 @@ and is not covered by the Salesforce Master Subscription Agreement.
135
135
 
136
136
  ### Changes
137
137
 
138
- - Add sf data command of create, query and delete. by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#61](https://github.com/jorgesolebur/CumulusCI/pull/61)
138
+ - Feature/pm 2055 pre post flow by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#63](https://github.com/jorgesolebur/CumulusCI/pull/63)
139
139
 
140
- **Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.17...v5.0.18
140
+ **Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.18...v5.0.19
@@ -1,8 +1,8 @@
1
- cumulusci/__about__.py,sha256=rNEejWvxeGfAnWPU00R1oUZaWa42-NuQA6ta50JVBY0,23
1
+ cumulusci/__about__.py,sha256=iG1RqsHMM9Y4rg_H9ZqoQLdpz9vroikOtO-jPCwzbaY,23
2
2
  cumulusci/__init__.py,sha256=jdanFQ_i8vbdO7Eltsf4pOfvV4mwa_Osyc4gxWKJ8ng,764
3
3
  cumulusci/__main__.py,sha256=kgRH-n5AJrH_daCK_EJwH7azAUxdXEmpi-r-dPGMR6Y,43
4
4
  cumulusci/conftest.py,sha256=AIL98BDwNAQtdo8YFmLKwav0tmrQ5dpbw1cX2FyGouQ,5108
5
- cumulusci/cumulusci.yml,sha256=RQN4mskRwSYaP33g-b9SZg8j62rU5ynpCE7ZBxfDqFY,73476
5
+ cumulusci/cumulusci.yml,sha256=5w0fVZqfgWu-RkNN_uHt1HnZqpDM0f4WySCNxpcLjE4,73262
6
6
  cumulusci/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  cumulusci/cli/cci.py,sha256=yAq8jFoGde6g_1TeAAjzZYsk77itiONCQGBFe3g3nOs,11836
8
8
  cumulusci/cli/error.py,sha256=znj0YN8D2Grozm1u7mZAsJlmmdGebbuy0c1ofQluL4Q,4410
@@ -65,17 +65,17 @@ cumulusci/core/config/tests/test_config.py,sha256=ZtIQSIzQWebw7mYXgehnp3CvoatC_t
65
65
  cumulusci/core/config/tests/test_config_expensive.py,sha256=__3JEuoAQ8s5njTcbyZlpXHr0jR0Qtne96xyF7fzqjQ,30137
66
66
  cumulusci/core/config/tests/test_config_util.py,sha256=X1SY9PIhLoQuC8duBKgs804aghN3n12DhqiC_f6jSmM,3177
67
67
  cumulusci/core/dependencies/__init__.py,sha256=Txf4VCrRW-aREKHqzK3ZyauQMsgtCXjiLkQzpMQT0kI,1533
68
- cumulusci/core/dependencies/base.py,sha256=JBLma_3fB5q-ee5zs-WpSX6hfbGJ09UYgs6vquROjiQ,23433
69
- cumulusci/core/dependencies/dependencies.py,sha256=74KjrCRn7AjKPBSGOm4pU34eJd8GSp1wNs7EFIC0wBE,8689
68
+ cumulusci/core/dependencies/base.py,sha256=xeZXWxkBZHQc9GjLhouv5ogkZaQJ7r8-yrl8fi6gMx0,24972
69
+ cumulusci/core/dependencies/dependencies.py,sha256=ldibl-bm_zTgpecE625VW1zIYJAlcNdwx02FSwi6xkk,9101
70
70
  cumulusci/core/dependencies/github.py,sha256=ozpRc5ADJsRDD5C_T-TLFygnBDE5Y9_03ZLCtZ-qr98,5897
71
71
  cumulusci/core/dependencies/github_resolvers.py,sha256=Em8p41Q-npoKv1ZAYNxXVrluQmYitzVfLLXlmln-MGw,9196
72
72
  cumulusci/core/dependencies/resolvers.py,sha256=xWVijK6Eu-WFyGnQPFANLkZFTjq4NQYhXsnLos5CElc,20742
73
73
  cumulusci/core/dependencies/utils.py,sha256=y54fWqLZ8IUIV9i1b6WcDXIJsK0ygny5DTsZCXbgeoM,369
74
74
  cumulusci/core/dependencies/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  cumulusci/core/dependencies/tests/conftest.py,sha256=rd0ZqpV6GTk7IRtPYJ7jyLWHM1g_xtZ4PABuvkN-TZY,11004
76
- cumulusci/core/dependencies/tests/test_dependencies.py,sha256=1nE1dkhwOSWyIP3KWyy5GJrUa-DMDOGcoWxcoSRV3D0,40572
76
+ cumulusci/core/dependencies/tests/test_dependencies.py,sha256=sdRcNmiBi0VsQwfbxgQSxTYwndocAD246KIAjFdn1N8,40748
77
77
  cumulusci/core/dependencies/tests/test_github.py,sha256=_JiUh-xgGmnsA2X_57i3gOwcDv_xBj66mdhitcZ-APU,2765
78
- cumulusci/core/dependencies/tests/test_resolvers.py,sha256=kLnvs0QTDKQwkxZ_lAAcetWZUNwBoP-TJYp9fdgRbHU,36271
78
+ cumulusci/core/dependencies/tests/test_resolvers.py,sha256=dhclH8_qQN4MXtd0gZ6TvLvehUVQJSJr7oGJdbht_hc,37415
79
79
  cumulusci/core/keychain/__init__.py,sha256=UbuaIrKZSczVVqbG_7BHFTkQukbqinGJlJlQGIpIsOI,595
80
80
  cumulusci/core/keychain/base_project_keychain.py,sha256=uIQ6Qc5xMIQIGWL947Ou9XdXpHvsGrrZdGKMypk6BFc,16927
81
81
  cumulusci/core/keychain/encrypted_file_project_keychain.py,sha256=yUrh4h2lynEjDvBDERFRXbJ4ZGTKKEjwXuo4bjBg7Xg,37734
@@ -516,7 +516,7 @@ cumulusci/tasks/salesforce/RetrievePackaged.py,sha256=XMdA3qBIuGJaZnPNeZBKupz9N6
516
516
  cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py,sha256=-pVD0RJ4vsB1TM9b7VcPQAV-pO4LTAm-YoESq4bM2dA,3289
517
517
  cumulusci/tasks/salesforce/RetrieveUnpackaged.py,sha256=_9jglbNWmjg9kq5EkvCusprrt14czurBtNM2hbU9RFI,1174
518
518
  cumulusci/tasks/salesforce/SOQLQuery.py,sha256=RFV-bLzvAx_m4SWWLMyCKuA3ie08iLwLyrOVyhpNCPE,1730
519
- cumulusci/tasks/salesforce/SfDataCommands.py,sha256=tCrjvxZDk4vrVmeJo7BR5uTEZUUbhbAGNoRuwt4zn1k,6094
519
+ cumulusci/tasks/salesforce/SfDataCommands.py,sha256=5t3441TOC7_Qv4rCerXsBpWRlQCIIqd40o8vGnnFV1g,14415
520
520
  cumulusci/tasks/salesforce/UninstallLocal.py,sha256=6nnRDyFrwIcXd9WygDHIMhMQYBZMFEQ75Sh6nNxivfg,492
521
521
  cumulusci/tasks/salesforce/UninstallLocalBundles.py,sha256=eG4yoDJdjruo9kwfcQU-i5_AVt473uWF7SvLD7IQoSw,759
522
522
  cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py,sha256=KgM6TPSkczAdeM10fOAtijs4uszJ_f219lGgVNwJ1eM,2210
@@ -538,14 +538,14 @@ cumulusci/tasks/salesforce/network_member_group.py,sha256=sWkRr4A6Zm_Iv_7uzPmiNj
538
538
  cumulusci/tasks/salesforce/nonsourcetracking.py,sha256=kKaQGEOErCIaE53A_TO4Hc5Afhv2aIi97UjPAN-4cQM,8720
539
539
  cumulusci/tasks/salesforce/org_settings.py,sha256=0Xg_voNeHV94husGpo7HTpA0QpqQ1NBJKqBsvLM24NA,7105
540
540
  cumulusci/tasks/salesforce/package_upload.py,sha256=AKrguElY4cTprmReq4s5BtIuG-QoxeaP9-YT8385Hzo,13024
541
- cumulusci/tasks/salesforce/profiles.py,sha256=-MCBsXuFEG-FJARf44r1aBVzGNTszguXvVZVOt8IN84,2671
541
+ cumulusci/tasks/salesforce/profiles.py,sha256=J3fO72UdnSct7HpPUTwVWVwbSWnAyfHxCGYqMcVF05M,3724
542
542
  cumulusci/tasks/salesforce/promote_package_version.py,sha256=2mD_iOzkbelaglH_NJKfR_MJoJOVpnqPuaGmnRTL5Go,15186
543
543
  cumulusci/tasks/salesforce/retrieve_profile.py,sha256=VTO--xWQJ7sidMMkvrfvgd2fSUaumQWJK25Aqa0oSNU,7798
544
544
  cumulusci/tasks/salesforce/salesforce_files.py,sha256=91VHtOkZzi9Tabfy0IDFWBW5bZPi8xOPb_4RPCqmFaY,9193
545
545
  cumulusci/tasks/salesforce/sourcetracking.py,sha256=-JaZt1NNlA_dEzYsIHvFKZ9va-MMA2JOPIplFN1g2EM,18797
546
546
  cumulusci/tasks/salesforce/trigger_handlers.py,sha256=cs6pDHhvi_eu0Vr8sLtfH2nrhlsF8TPrkKezjciy79o,4932
547
547
  cumulusci/tasks/salesforce/uninstall_packaged_incremental.py,sha256=9-_3S0PaVm-K6t44McBHSfRTB7KVzkHUMii4-p5PkS0,5673
548
- cumulusci/tasks/salesforce/update_dependencies.py,sha256=Ncynls5YaiXEYQts3qQhk50z9RXl5vZGuwWYa9c_2ME,11896
548
+ cumulusci/tasks/salesforce/update_dependencies.py,sha256=BE-ey6eH_x75jC4D6kSa-aR0JaCuPbzvN5MlhxlKAkU,12153
549
549
  cumulusci/tasks/salesforce/update_profile.py,sha256=P8TQeWEjzXFI4hN5cUk9zMCweBerqNP08seIuYEo-RI,15163
550
550
  cumulusci/tasks/salesforce/tests/__init__.py,sha256=zEUlLU8eRXUU1HAcYdHtdAgHbdlAPPj39rcWRPEu2H4,57
551
551
  cumulusci/tasks/salesforce/tests/test_CreateCommunity.py,sha256=aepyVVrM6zfmT2UJ_pKKdgwv7DsU66F_eiwmE4EfVUo,8720
@@ -583,12 +583,12 @@ cumulusci/tasks/salesforce/tests/test_install_package_version.py,sha256=f_hHO83v
583
583
  cumulusci/tasks/salesforce/tests/test_network_member_group.py,sha256=N0ZXcy74nNQdpmGrBnkVzY6mALW4KxoYhQtARrZJ578,13968
584
584
  cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py,sha256=14yjidvGa0Q85n8uoWXwMeFsOWYJGqvFShevAbidfbU,9154
585
585
  cumulusci/tasks/salesforce/tests/test_org_settings.py,sha256=SRpqihPzRy29WEinOlU-l-DU8viyFP1guIzjfROTK7A,13532
586
- cumulusci/tasks/salesforce/tests/test_profiles.py,sha256=iYTsTPkZspXOAKJfjX8uydHPLnmkspsqxHOqOMgT7xE,6329
586
+ cumulusci/tasks/salesforce/tests/test_profiles.py,sha256=qe1A6s_0ckuRJQsTDPbVr0QMuCaYvzH7dAG5ufkOmGc,9462
587
587
  cumulusci/tasks/salesforce/tests/test_retrieve_profile.py,sha256=Ujbt1k34CmN6-CvcKhTY_vgVp-OtpvGb0NDXS-vGYSQ,9858
588
588
  cumulusci/tasks/salesforce/tests/test_salesforce_files.py,sha256=eBeyanF7ygldukf9TQrPSXmeTU5sLQi_vNHVplx8dd0,8005
589
589
  cumulusci/tasks/salesforce/tests/test_sourcetracking.py,sha256=n9dyJ21OZs8P574Ds3uzVESCVl6fK0_RYv2bKI1dm3E,12540
590
590
  cumulusci/tasks/salesforce/tests/test_trigger_handlers.py,sha256=IpqnCKgGVWU0XUP4LDB10HG1LLj5VvsSfxMBa26EScs,10567
591
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py,sha256=3PQTs0csDoekm7ZH-S9MDZ1JRM0l_Tln6DvPyEevOZs,14665
591
+ cumulusci/tasks/salesforce/tests/test_update_dependencies.py,sha256=C7Zu115-Bi585Fu7tWy3TF9ZKDcKiu1zXzqJiOhA0L0,14679
592
592
  cumulusci/tasks/salesforce/tests/util.py,sha256=YbeHOKyKNhKNYtd1g467a4sQ07W3SvyMAuT0E3bL6Js,2543
593
593
  cumulusci/tasks/salesforce/users/permsets.py,sha256=EMFX7QhSbVd1LM0ognYlaXUhYcpSY-0CbWOARRVORM0,9326
594
594
  cumulusci/tasks/salesforce/users/photos.py,sha256=67-fQk0m9w4bf1N2WSWfsn-kwtmIuLtSpyJCqa8Cr1w,5745
@@ -613,9 +613,7 @@ cumulusci/tasks/tests/test_pushfails.py,sha256=9JG9D0iD4dR-1fKheaRN7BEy3lzzuOKeR
613
613
  cumulusci/tasks/tests/test_salesforce.py,sha256=yCGtuHapxyAEmXQhuF2g2fh2naknTu7Md4OfEJQvGAA,2594
614
614
  cumulusci/tasks/tests/test_sfdx.py,sha256=oUbHo28d796m5RuskXMLitJw2rCLjjXIfxggzr4gsso,3545
615
615
  cumulusci/tasks/tests/test_util.py,sha256=D1T0QnvPTS0PHeZWo2xiVgE1jVTYcLzGTGHwEIoVmxk,7296
616
- cumulusci/tasks/utility/data_management.py,sha256=7DbK0uxMxj_e4WuH08gWFVxr_ZBMrvHvumHnM_oi2YM,443
617
616
  cumulusci/tasks/utility/env_management.py,sha256=hJX6ySEiXD2oFW38JqbGQKMj89ucxdSBsPwytSdkgO8,6591
618
- cumulusci/tasks/utility/tests/test_data_management.py,sha256=f3F1yFikaNEkzllMXZa67gNX0T_PCC-4zEoL8kUII_Q,1363
619
617
  cumulusci/tasks/utility/tests/test_env_management.py,sha256=fw34meWGOe1YYZO449MMCi2O7BgSaOA_I_wScrIr1Uk,8702
620
618
  cumulusci/tasks/vcs/__init__.py,sha256=ZzpMZnhooXZ6r_ywBVTS3UNw9uMcXW6h33LylRqTDK0,700
621
619
  cumulusci/tasks/vcs/commit_status.py,sha256=hgPUVHeQyIfMsCuwrw2RI-ufnbjdRARc6HI3BEgdcxI,2332
@@ -742,9 +740,9 @@ cumulusci/vcs/tests/dummy_service.py,sha256=RltOUpMIhSDNrfxk0LhLqlH4ppC0sK6NC2cO
742
740
  cumulusci/vcs/tests/test_vcs_base.py,sha256=9mp6uZ3lTxY4onjUNCucp9N9aB3UylKS7_2Zu_hdAZw,24331
743
741
  cumulusci/vcs/tests/test_vcs_bootstrap.py,sha256=N0NA48-rGNIIjY3Z7PtVnNwHObSlEGDk2K55TQGI8g4,27954
744
742
  cumulusci/vcs/utils/__init__.py,sha256=py4fEcHM7Vd0M0XWznOlywxaeCtG3nEVGmELmEKVGU8,869
745
- cumulusci_plus-5.0.18.dist-info/METADATA,sha256=EjWQpoxW3IMvVViclvXJR9WyKkDlsnxTxZrsX6m0Zwk,5769
746
- cumulusci_plus-5.0.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
747
- cumulusci_plus-5.0.18.dist-info/entry_points.txt,sha256=nTtu04b9iLXhzADcTrb5PwmdXE6e2MTUAMh9OK6Z2pg,80
748
- cumulusci_plus-5.0.18.dist-info/licenses/AUTHORS.rst,sha256=PvewjKImdKPhhJ6xR2EEZ4T7GbpY2ZeAeyWm2aLtiMQ,676
749
- cumulusci_plus-5.0.18.dist-info/licenses/LICENSE,sha256=NFsF_s7RVXk2dU6tmRAN8wF45pnD98VZ5IwqOsyBcaU,1499
750
- cumulusci_plus-5.0.18.dist-info/RECORD,,
743
+ cumulusci_plus-5.0.19.dist-info/METADATA,sha256=H6WNavlWKMBgslTFJcl_b64AJF_owi7XhFRlYg6G5RI,5750
744
+ cumulusci_plus-5.0.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
745
+ cumulusci_plus-5.0.19.dist-info/entry_points.txt,sha256=nTtu04b9iLXhzADcTrb5PwmdXE6e2MTUAMh9OK6Z2pg,80
746
+ cumulusci_plus-5.0.19.dist-info/licenses/AUTHORS.rst,sha256=PvewjKImdKPhhJ6xR2EEZ4T7GbpY2ZeAeyWm2aLtiMQ,676
747
+ cumulusci_plus-5.0.19.dist-info/licenses/LICENSE,sha256=NFsF_s7RVXk2dU6tmRAN8wF45pnD98VZ5IwqOsyBcaU,1499
748
+ cumulusci_plus-5.0.19.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- from typing import Any
2
-
3
- from cumulusci.core.tasks import BaseTask
4
- from cumulusci.utils.options import CCIOptions, Field
5
-
6
-
7
- class GetFirstItemFromRecordListTask(BaseTask):
8
- class Options(CCIOptions):
9
- result: dict[str, Any] = Field(
10
- None, description="The result from SF CLI operations."
11
- )
12
-
13
- parsed_options: Options
14
-
15
- def _run_task(self):
16
- self.return_values = self.parsed_options.result["records"][0]
@@ -1,43 +0,0 @@
1
- import pytest
2
-
3
- from cumulusci.core.config import (
4
- BaseProjectConfig,
5
- OrgConfig,
6
- TaskConfig,
7
- UniversalConfig,
8
- )
9
- from cumulusci.tasks.utility.data_management import GetFirstItemFromRecordListTask
10
-
11
-
12
- @pytest.fixture
13
- def a_task():
14
- universal_config = UniversalConfig()
15
- project_config = BaseProjectConfig(universal_config, config={"noyaml": True})
16
- org_config = OrgConfig({}, "test")
17
-
18
- def _a_task(options):
19
- task_config = TaskConfig({"options": options})
20
- return GetFirstItemFromRecordListTask(project_config, task_config, org_config)
21
-
22
- return _a_task
23
-
24
-
25
- class TestGetFirstItemFromRecordListTask:
26
- def test_returns_first_item(self, a_task):
27
- """Tests that the task returns the first item from the 'records' list."""
28
- result_input = {
29
- "records": [
30
- {"Id": "001", "Name": "Test Account"},
31
- {"Id": "002", "Name": "Another Account"},
32
- ]
33
- }
34
- task = a_task({"result": result_input})
35
- task()
36
- assert task.return_values == {"Id": "001", "Name": "Test Account"}
37
-
38
- def test_empty_record_list(self, a_task):
39
- """Tests that an IndexError is raised for an empty 'records' list."""
40
- result_input = {"records": []}
41
- task = a_task({"result": result_input})
42
- with pytest.raises(IndexError):
43
- task()