cumulusci-plus 5.0.24__py3-none-any.whl → 5.0.26__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.

Potentially problematic release.


This version of cumulusci-plus might be problematic. Click here for more details.

Files changed (50) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/task.py +17 -0
  3. cumulusci/cli/tests/test_error.py +3 -1
  4. cumulusci/cli/tests/test_task.py +88 -2
  5. cumulusci/core/github.py +1 -1
  6. cumulusci/core/sfdx.py +3 -1
  7. cumulusci/cumulusci.yml +20 -0
  8. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
  9. cumulusci/salesforce_api/rest_deploy.py +1 -1
  10. cumulusci/tasks/apex/anon.py +1 -1
  11. cumulusci/tasks/apex/testrunner.py +6 -1
  12. cumulusci/tasks/bulkdata/extract.py +0 -1
  13. cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
  14. cumulusci/tasks/bulkdata/tests/test_select_utils.py +6 -0
  15. cumulusci/tasks/metadata_etl/base.py +7 -3
  16. cumulusci/tasks/push/README.md +15 -17
  17. cumulusci/tasks/release_notes/README.md +13 -13
  18. cumulusci/tasks/robotframework/tests/test_robotframework.py +1 -1
  19. cumulusci/tasks/salesforce/Deploy.py +5 -1
  20. cumulusci/tasks/salesforce/composite.py +1 -1
  21. cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
  22. cumulusci/tasks/salesforce/enable_prediction.py +5 -1
  23. cumulusci/tasks/salesforce/sourcetracking.py +1 -1
  24. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +912 -0
  25. cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
  26. cumulusci/tasks/salesforce/update_external_credential.py +562 -0
  27. cumulusci/tasks/salesforce/update_named_credential.py +441 -0
  28. cumulusci/tasks/salesforce/update_profile.py +17 -13
  29. cumulusci/tasks/salesforce/users/permsets.py +70 -2
  30. cumulusci/tasks/salesforce/users/tests/test_permsets.py +184 -0
  31. cumulusci/tasks/sfdmu/__init__.py +0 -0
  32. cumulusci/tasks/sfdmu/sfdmu.py +256 -0
  33. cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
  34. cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
  35. cumulusci/tasks/sfdmu/tests/test_sfdmu.py +443 -0
  36. cumulusci/tasks/utility/credentialManager.py +256 -0
  37. cumulusci/tasks/utility/directoryRecreator.py +30 -0
  38. cumulusci/tasks/utility/secretsToEnv.py +130 -0
  39. cumulusci/tasks/utility/tests/test_credentialManager.py +564 -0
  40. cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
  41. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1091 -0
  42. cumulusci/utils/__init__.py +23 -1
  43. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
  44. cumulusci/utils/yaml/tests/test_model_parser.py +2 -2
  45. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/METADATA +7 -9
  46. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/RECORD +50 -35
  47. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/WHEEL +0 -0
  48. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/entry_points.txt +0 -0
  49. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/licenses/AUTHORS.rst +0 -0
  50. {cumulusci_plus-5.0.24.dist-info → cumulusci_plus-5.0.26.dist-info}/licenses/LICENSE +0 -0
cumulusci/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "5.0.24"
1
+ __version__ = "5.0.26"
cumulusci/cli/task.py CHANGED
@@ -2,6 +2,7 @@ import json
2
2
  from pathlib import Path
3
3
 
4
4
  import click
5
+ from dotenv import load_dotenv
5
6
  from rich.console import Console
6
7
  from rst2ansi import rst2ansi
7
8
 
@@ -126,6 +127,10 @@ class RunTaskCommand(click.MultiCommand):
126
127
  "help": "Drops into the Python debugger at task completion.",
127
128
  "is_flag": True,
128
129
  },
130
+ "loadenv": {
131
+ "help": "Loads environment variables from the .env file.",
132
+ "is_flag": True,
133
+ },
129
134
  }
130
135
 
131
136
  def list_commands(self, ctx):
@@ -151,6 +156,17 @@ class RunTaskCommand(click.MultiCommand):
151
156
 
152
157
  def run_task(*args, **kwargs):
153
158
  """Callback function that executes when the command fires."""
159
+ # Load environment variables FIRST, before any task processing
160
+ if kwargs.get("loadenv", None):
161
+ # Load .env file from the project root directory
162
+ env_path = (
163
+ Path(runtime.project_config.repo_root) / ".env"
164
+ if runtime.project_config
165
+ else None
166
+ )
167
+ if env_path:
168
+ load_dotenv(env_path)
169
+
154
170
  org, org_config = runtime.get_org(
155
171
  kwargs.pop("org", None), fail_if_missing=False
156
172
  )
@@ -168,6 +184,7 @@ class RunTaskCommand(click.MultiCommand):
168
184
  task_config.config["options"].update(options)
169
185
 
170
186
  try:
187
+
171
188
  task = task_class(
172
189
  task_config.project_config, task_config, org_config=org_config
173
190
  )
@@ -98,7 +98,9 @@ Environment Info: Rossian / x68_46
98
98
  )
99
99
  webbrowser_open.assert_called_once_with(expected_gist_url)
100
100
 
101
- @pytest.mark.skipif(sys.version_info > (3, 11), reason="requires python3.10 or higher")
101
+ @pytest.mark.skipif(
102
+ sys.version_info > (3, 11), reason="requires python3.10 or higher"
103
+ )
102
104
  @mock.patch("cumulusci.cli.error.platform")
103
105
  @mock.patch("cumulusci.cli.error.sys")
104
106
  @mock.patch("cumulusci.cli.error.datetime")
@@ -1,6 +1,8 @@
1
1
  import contextlib
2
2
  import io
3
3
  import json
4
+ import tempfile
5
+ from pathlib import Path
4
6
  from unittest.mock import Mock, patch
5
7
 
6
8
  import click
@@ -126,10 +128,10 @@ def test_format_help(runtime):
126
128
 
127
129
  def test_get_default_command_options():
128
130
  opts = task.RunTaskCommand()._get_default_command_options(is_salesforce_task=False)
129
- assert len(opts) == 4
131
+ assert len(opts) == 5
130
132
 
131
133
  opts = task.RunTaskCommand()._get_default_command_options(is_salesforce_task=True)
132
- assert len(opts) == 5
134
+ assert len(opts) == 6
133
135
  assert any([o.name == "org" for o in opts])
134
136
 
135
137
 
@@ -264,3 +266,87 @@ class SetTrace(Exception):
264
266
  class DummyDerivedTask(DummyTask):
265
267
  def _run_task(self):
266
268
  click.echo(f"<{self.__class__}>\n\tcolor: {self.options['color']}")
269
+
270
+
271
+ @patch("cumulusci.cli.task.load_dotenv")
272
+ def test_task_run__loadenv_with_project_root(load_dotenv, runtime):
273
+ """Test that loadenv loads .env file from project root when project exists."""
274
+ DummyTask._run_task = Mock()
275
+
276
+ # Create a temporary directory for the test
277
+ with tempfile.TemporaryDirectory() as temp_dir:
278
+ runtime.project_config._repo_info = {"root": temp_dir}
279
+
280
+ multi_cmd = task.RunTaskCommand()
281
+ with click.Context(multi_cmd, obj=runtime) as ctx:
282
+ cmd = multi_cmd.get_command(ctx, "dummy-task")
283
+ cmd.callback(runtime, "dummy-task", color="blue", loadenv=True)
284
+
285
+ # Verify load_dotenv was called with the correct path
286
+ expected_path = Path(temp_dir) / ".env"
287
+ load_dotenv.assert_called_once_with(expected_path)
288
+ DummyTask._run_task.assert_called_once()
289
+
290
+
291
+ @patch("cumulusci.cli.task.load_dotenv")
292
+ def test_task_run__loadenv_false(load_dotenv, runtime):
293
+ """Test that loadenv does not call load_dotenv when loadenv=False."""
294
+ DummyTask._run_task = Mock()
295
+
296
+ multi_cmd = task.RunTaskCommand()
297
+ with click.Context(multi_cmd, obj=runtime) as ctx:
298
+ cmd = multi_cmd.get_command(ctx, "dummy-task")
299
+ cmd.callback(runtime, "dummy-task", color="blue", loadenv=False)
300
+
301
+ # Verify load_dotenv was not called
302
+ load_dotenv.assert_not_called()
303
+ DummyTask._run_task.assert_called_once()
304
+
305
+
306
+ @patch("cumulusci.cli.task.load_dotenv")
307
+ def test_task_run__loadenv_not_provided(load_dotenv, runtime):
308
+ """Test that loadenv does not call load_dotenv when loadenv is not provided."""
309
+ DummyTask._run_task = Mock()
310
+
311
+ multi_cmd = task.RunTaskCommand()
312
+ with click.Context(multi_cmd, obj=runtime) as ctx:
313
+ cmd = multi_cmd.get_command(ctx, "dummy-task")
314
+ cmd.callback(runtime, "dummy-task", color="blue")
315
+
316
+ # Verify load_dotenv was not called
317
+ load_dotenv.assert_not_called()
318
+ DummyTask._run_task.assert_called_once()
319
+
320
+
321
+ @patch("cumulusci.cli.task.load_dotenv")
322
+ def test_task_run__loadenv_none_value(load_dotenv, runtime):
323
+ """Test that loadenv does not call load_dotenv when loadenv=None."""
324
+ DummyTask._run_task = Mock()
325
+
326
+ multi_cmd = task.RunTaskCommand()
327
+ with click.Context(multi_cmd, obj=runtime) as ctx:
328
+ cmd = multi_cmd.get_command(ctx, "dummy-task")
329
+ cmd.callback(runtime, "dummy-task", color="blue", loadenv=None)
330
+
331
+ # Verify load_dotenv was not called
332
+ load_dotenv.assert_not_called()
333
+ DummyTask._run_task.assert_called_once()
334
+
335
+
336
+ def test_get_default_command_options_includes_loadenv():
337
+ """Test that the loadenv option is included in default command options."""
338
+ opts = task.RunTaskCommand()._get_default_command_options(is_salesforce_task=False)
339
+
340
+ # Should have 5 global options including loadenv
341
+ assert len(opts) == 5
342
+
343
+ # Find the loadenv option
344
+ loadenv_opt = None
345
+ for opt in opts:
346
+ if hasattr(opt, "name") and opt.name == "loadenv":
347
+ loadenv_opt = opt
348
+ break
349
+
350
+ assert loadenv_opt is not None
351
+ assert loadenv_opt.is_flag is True
352
+ assert "Loads environment variables from the .env file" in loadenv_opt.help
cumulusci/core/github.py CHANGED
@@ -189,7 +189,7 @@ def validate_service(options: dict, keychain) -> dict:
189
189
  server_domain = options.get("server_domain", None)
190
190
 
191
191
  gh = _determine_github_client(server_domain, {"token": token})
192
- if type(gh) == GitHubEnterprise:
192
+ if isinstance(gh, GitHubEnterprise):
193
193
  validate_gh_enterprise(server_domain, keychain)
194
194
  try:
195
195
  authed_user = gh.me()
cumulusci/core/sfdx.py CHANGED
@@ -14,10 +14,12 @@ import sarge
14
14
 
15
15
  # Fix for TextIOWrapper flush issue with sarge.Capture objects
16
16
  # Add flush method to sarge.Capture to prevent AttributeError during garbage collection
17
- if not hasattr(sarge.Capture, 'flush'):
17
+ if not hasattr(sarge.Capture, "flush"):
18
+
18
19
  def _capture_flush(self):
19
20
  """No-op flush method for sarge.Capture compatibility with TextIOWrapper"""
20
21
  pass
22
+
21
23
  sarge.Capture.flush = _capture_flush
22
24
 
23
25
  from cumulusci.core.enums import StrEnum
cumulusci/cumulusci.yml CHANGED
@@ -761,6 +761,14 @@ tasks:
761
761
  description: Get or set environment variables.
762
762
  class_path: cumulusci.tasks.utility.env_management.EnvManagement
763
763
  group: Utilities
764
+ secrets_to_dotenv:
765
+ description: Get all environment variables from a target JSON file and generates a .env file that can be used to set up the environment variables for the deployment
766
+ class_path: cumulusci.tasks.utility.secretsToEnv.SecretsToEnv
767
+ group: Utilities
768
+ directory_recreator:
769
+ description: Remove and recreate a directory
770
+ class_path: cumulusci.tasks.utility.directoryRecreator.DirectoryRecreator
771
+ group: Utilities
764
772
  download_extract:
765
773
  description: Downloads files and folders from a VCS repository.
766
774
  class_path: cumulusci.tasks.vcs.download_extract.DownloadExtract
@@ -795,6 +803,10 @@ tasks:
795
803
  description: Load Custom Settings specified in a YAML file to the target org
796
804
  class_path: cumulusci.tasks.salesforce.LoadCustomSettings
797
805
  group: "Data Operations"
806
+ sfdmu:
807
+ description: Execute SFDmu data migration with namespace injection support
808
+ class_path: cumulusci.tasks.sfdmu.sfdmu.SfdmuTask
809
+ group: "Data Operations"
798
810
  remove_metadata_xml_elements:
799
811
  description: Remove specified XML elements from one or more metadata files
800
812
  class_path: cumulusci.tasks.metadata.modify.RemoveElementsXPath
@@ -842,6 +854,14 @@ tasks:
842
854
  options:
843
855
  package_name: $project_config.project__package__name
844
856
  fail_on_error: False
857
+ update_named_credential:
858
+ class_path: cumulusci.tasks.salesforce.update_named_credential.UpdateNamedCredential
859
+ description: Update named credential parameters
860
+ group: Metadata Transformations
861
+ update_external_credential:
862
+ class_path: cumulusci.tasks.salesforce.update_external_credential.UpdateExternalCredential
863
+ description: Update external credential parameters
864
+ group: Metadata Transformations
845
865
 
846
866
  flows:
847
867
  ci_beta:
@@ -203,7 +203,7 @@ class ObjectManagerPage(BasePage):
203
203
 
204
204
  except Exception as e:
205
205
  self.builtin.log(
206
- f"on try #{tries+1} we caught this error: {e}", "DEBUG"
206
+ f"on try #{tries + 1} we caught this error: {e}", "DEBUG"
207
207
  )
208
208
  self.builtin.sleep("1 second")
209
209
  last_error = e
@@ -138,7 +138,7 @@ class RestDeploy:
138
138
 
139
139
  # Construct an error message from deployment failure details
140
140
  def _construct_error_message(self, failure):
141
- error_message = f"{str.upper(failure['problemType'])} in file {failure['fileName'][len(PARENT_DIR_NAME)+len('/'):]}: {failure['problem']}"
141
+ error_message = f"{str.upper(failure['problemType'])} in file {failure['fileName'][len(PARENT_DIR_NAME) + len('/'):]}: {failure['problem']}"
142
142
 
143
143
  if failure["lineNumber"] and failure["columnNumber"]:
144
144
  error_message += (
@@ -4,7 +4,7 @@ from cumulusci.core.exceptions import (
4
4
  SalesforceException,
5
5
  TaskOptionsError,
6
6
  )
7
- from cumulusci.core.utils import process_bool_arg, determine_managed_mode
7
+ from cumulusci.core.utils import determine_managed_mode, process_bool_arg
8
8
  from cumulusci.tasks.salesforce import BaseSalesforceApiTask
9
9
  from cumulusci.utils import in_directory, inject_namespace
10
10
  from cumulusci.utils.http.requests_utils import safe_json_from_response
@@ -10,7 +10,12 @@ from cumulusci.core.exceptions import (
10
10
  CumulusCIException,
11
11
  TaskOptionsError,
12
12
  )
13
- from cumulusci.core.utils import decode_to_unicode, process_bool_arg, process_list_arg, determine_managed_mode
13
+ from cumulusci.core.utils import (
14
+ decode_to_unicode,
15
+ determine_managed_mode,
16
+ process_bool_arg,
17
+ process_list_arg,
18
+ )
14
19
  from cumulusci.tasks.salesforce import BaseSalesforceApiTask
15
20
  from cumulusci.utils.http.requests_utils import safe_json_from_response
16
21
 
@@ -213,7 +213,6 @@ class ExtractData(SqlAlchemyMixin, BaseSalesforceApiTask):
213
213
  IsPersonAccount_index = columns.index(mapping.fields["IsPersonAccount"])
214
214
 
215
215
  def strip_name_field(record):
216
- nonlocal Name_index, IsPersonAccount_index
217
216
  if record[IsPersonAccount_index].lower() == "true":
218
217
  record[Name_index] = ""
219
218
  return record
@@ -2715,8 +2715,6 @@ class TestLoadData:
2715
2715
  chunks_index = 0
2716
2716
 
2717
2717
  def fetchmany(batch_size):
2718
- nonlocal chunks_index
2719
-
2720
2718
  assert 200 == batch_size
2721
2719
 
2722
2720
  # _generate_contact_id_map_for_person_accounts should break if fetchmany returns falsy.
@@ -1,3 +1,5 @@
1
+ import sys
2
+
1
3
  import pytest
2
4
 
3
5
  from cumulusci.tasks.bulkdata.select_utils import (
@@ -618,6 +620,10 @@ def test_vectorize_records_mixed_numerical_boolean_categorical():
618
620
  not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
619
621
  reason="requires optional dependencies for annoy",
620
622
  )
623
+ @pytest.mark.skipif(
624
+ sys.platform == "darwin" and sys.version_info[:2] in [(3, 11), (3, 13)],
625
+ reason="Annoy library has known compatibility issues on macOS with Python 3.11 and 3.13",
626
+ )
621
627
  def test_annoy_post_process():
622
628
  # Test data
623
629
  load_records = [["Alice", "Engineer"], ["Bob", "Doctor"]]
@@ -7,7 +7,11 @@ from cumulusci.core.config import TaskConfig
7
7
  from cumulusci.core.enums import StrEnum
8
8
  from cumulusci.core.exceptions import CumulusCIException, TaskOptionsError
9
9
  from cumulusci.core.tasks import BaseSalesforceTask
10
- from cumulusci.core.utils import process_bool_arg, process_list_arg, determine_managed_mode
10
+ from cumulusci.core.utils import (
11
+ determine_managed_mode,
12
+ process_bool_arg,
13
+ process_list_arg,
14
+ )
11
15
  from cumulusci.salesforce_api.metadata import ApiRetrieveUnpackaged
12
16
  from cumulusci.tasks.metadata.package import PackageXmlGenerator
13
17
  from cumulusci.utils import inject_namespace
@@ -74,8 +78,8 @@ class BaseMetadataETLTask(BaseSalesforceTask, metaclass=ABCMeta):
74
78
  self.options["namespaced_org"] or False
75
79
  )
76
80
  else:
77
- self.options["namespaced_org"] = (
78
- bool(namespace) and namespace == getattr(self.org_config, 'namespace', None)
81
+ self.options["namespaced_org"] = bool(namespace) and namespace == getattr(
82
+ self.org_config, "namespace", None
79
83
  )
80
84
 
81
85
  def _inject_namespace(self, text):
@@ -1,21 +1,20 @@
1
1
  # Push Upgrade API Scripts
2
2
 
3
- These scripts are designed to work with the Salesforce Push Upgrade API (in Pilot in Winter 16) which exposes new objects via the Tooling API that allow interacting with push upgrades in a packaging org. The main purpose of these scripts is to use the Push Upgrade API to automate push upgrades through Jenkins.
3
+ These scripts are designed to work with the Salesforce Push Upgrade API (in Pilot in Winter 16) which exposes new objects via the Tooling API that allow interacting with push upgrades in a packaging org. The main purpose of these scripts is to use the Push Upgrade API to automate push upgrades through Jenkins.
4
4
 
5
5
  # push_api.py - Python Wrapper for Push Upgrade API
6
6
 
7
- This python file provides wrapper classes around the Tooling API objects and abstracts interaction with them and their related data to make writing scripts easier. All the other scripts in this directory use the SalesforcePushApi wrapper to interact with the Tooling API.
7
+ This python file provides wrapper classes around the Tooling API objects and abstracts interaction with them and their related data to make writing scripts easier. All the other scripts in this directory use the SalesforcePushApi wrapper to interact with the Tooling API.
8
8
 
9
9
  Initializing the SalesforcePushApi wrapper can be done with the following python code:
10
10
 
11
11
  push_api = SalesforcePushApi(sf_user, sf_pass, sf_serverurl)
12
12
 
13
13
  You can also pass two optional keyword args to the initialization to control the wrapper's behavior
14
-
15
- * **lazy**: A list of objects that should be lazily looked up. Currently, the only implementations for this are 'jobs' and 'subscribers'. If either are included in the list, they will be looked up on demand when needed by a referenced object. For example, if you are querying all jobs and subscribers is not set to lazy, all subscribers will first be retrieved. If lazy is enabled, subscriber orgs will only be retrieved when trying to resolve references for a particular job. Generally, if you have a lot of subscribers and only expect your script to need to lookup a small number of them, enabling lazy for subscribers will reduce api calls and cause the script to run faster.
16
14
 
17
- * **default_where**: A dictionary with Push Upgrade API objects as key and a value containing a SOQL WHERE clause statement which is applied to all queries against the object to effectively set the universe for a given object. For example:
18
-
15
+ - **lazy**: A list of objects that should be lazily looked up. Currently, the only implementations for this are 'jobs' and 'subscribers'. If either are included in the list, they will be looked up on demand when needed by a referenced object. For example, if you are querying all jobs and subscribers is not set to lazy, all subscribers will first be retrieved. If lazy is enabled, subscriber orgs will only be retrieved when trying to resolve references for a particular job. Generally, if you have a lot of subscribers and only expect your script to need to lookup a small number of them, enabling lazy for subscribers will reduce api calls and cause the script to run faster.
16
+
17
+ - **default_where**: A dictionary with Push Upgrade API objects as key and a value containing a SOQL WHERE clause statement which is applied to all queries against the object to effectively set the universe for a given object. For example:
19
18
  default_where = {'PackageSubscriber': "OrgType = 'Sandbox'"}
20
19
 
21
20
  In the example above, the wrapper would never return a PackageSubscriber which is not a Sandbox org.
@@ -24,22 +23,22 @@ In the example above, the wrapper would never return a PackageSubscriber which i
24
23
 
25
24
  ## Common Environment Variables
26
25
 
27
- The push scripts are all designed to receive their arguments via environment variables. The following are common amongst all of the Push Scripts
26
+ The push scripts are all designed to receive their arguments via environment variables. The following are common amongst all of the Push Scripts
28
27
 
29
- * **SF_USERNAME**: The Salesforce username for the packaging org
30
- * **SF_PASSWORD**: The Salesforce password and security token for the packaging org
31
- * **SF_SERVERURL**: The login url for the Salesforce packaging org.
28
+ - **SF_USERNAME**: The Salesforce username for the packaging org
29
+ - **SF_PASSWORD**: The Salesforce password and security token for the packaging org
30
+ - **SF_SERVERURL**: The login url for the Salesforce packaging org.
32
31
 
33
32
  ## get_version_id.py
34
33
 
35
- Takes a namespace and version string and looks up the given version. Returns the version's Salesforce Id.
34
+ Takes a namespace and version string and looks up the given version. Returns the version's Salesforce Id.
36
35
 
37
36
  The script handles parsing the version number string into a SOQL query against the MetadataPackageVersion object with the correct MajorVersion, MinorVersion, PatchVersion, ReleaseState, and BuildNumber (i.e. Beta number).
38
37
 
39
38
  ### Required Environment Variables
40
39
 
41
- * **NAMESPACE**: The Package's namespace prefix
42
- * **VERSION_NUMBER**: The version number string.
40
+ - **NAMESPACE**: The Package's namespace prefix
41
+ - **VERSION_NUMBER**: The version number string.
43
42
 
44
43
  ## orgs_for_push.py
45
44
 
@@ -47,13 +46,12 @@ Takes a MetadataPackageVersion Id and optionally a where clause to filter Subscr
47
46
 
48
47
  ### Required Environment Variables
49
48
 
50
- * **VERSION**: The MetadataPackageVersion Id of the version you want to push upgrade. This is used to look for all users not on the version or a newer version
49
+ - **VERSION**: The MetadataPackageVersion Id of the version you want to push upgrade. This is used to look for all users not on the version or a newer version
51
50
 
52
51
  ### Optional Environment Variables
53
52
 
54
- * **SUBSCRIBER_WHERE**: An extra filter to be applied to all Subscriber queries. For example, setting this to OrgType = 'Sandbox' would find all Sandbox orgs eligible for push upgrade to the specified version
53
+ - **SUBSCRIBER_WHERE**: An extra filter to be applied to all Subscriber queries. For example, setting this to OrgType = 'Sandbox' would find all Sandbox orgs eligible for push upgrade to the specified version
55
54
 
56
55
  ## failed_orgs_for_push.py
57
56
 
58
- Takes a PackagePushRequest Id and optionally a where clause to filter Subscribers and returns a list of OrgId's one per line for all orgs which failed the
59
-
57
+ Takes a PackagePushRequest Id and optionally a where clause to filter Subscribers and returns a list of OrgId's one per line for all orgs which failed the
@@ -13,25 +13,25 @@ Start the section with `# Critical Changes` followed by your content
13
13
  For example:
14
14
 
15
15
  This won't be included
16
-
16
+
17
17
  # Critical Changes
18
-
18
+
19
19
  This will be included in Critical Changes
20
-
20
+
21
21
  ## Changes
22
22
 
23
- The Changes section is where you should list off any changes worth highlight to users in the release notes. This section should always include instructions for users for any post-upgrade tasks they need to perform to enable new functionality. For example, users should be told to grant permissions and add new CustomFields to layouts.
23
+ The Changes section is where you should list off any changes worth highlight to users in the release notes. This section should always include instructions for users for any post-upgrade tasks they need to perform to enable new functionality. For example, users should be told to grant permissions and add new CustomFields to layouts.
24
24
 
25
25
  Start the section with `# Changes` followed by your content
26
26
 
27
27
  For example:
28
28
 
29
29
  This won't be included
30
-
30
+
31
31
  # Changes
32
-
32
+
33
33
  This will be included in Changes
34
-
34
+
35
35
  ## Issues Closed
36
36
 
37
37
  The Issues Closed section is where you should link to any closed issues that should be listed in the release notes.
@@ -41,9 +41,9 @@ Start the section with `# Changes` followed by your content
41
41
  For example:
42
42
 
43
43
  This won't be included
44
-
44
+
45
45
  # Issues Closed
46
-
46
+
47
47
  Fixes #102
48
48
  resolves #100
49
49
  This release closes #101
@@ -55,9 +55,9 @@ Would output:
55
55
  #100: Title of Issue 100
56
56
  #101: Title of Issue 101
57
57
  #102: Title of Issue 102
58
-
58
+
59
59
  A few notes about how issues are parsed:
60
60
 
61
- * The parser uses the same format as Github: https://help.github.com/articles/closing-issues-via-commit-messages/
62
- * The parser searches for all issue numbers and sorts them by their integer value, looks up their title, and outputs a formatted line with the issue number and title for each issue.
63
- * The parser ignores everything else in the line that is not an issue number. Anything that is not an issue number will not appear in the rendered release notes
61
+ - The parser uses the same format as Github: https://help.github.com/articles/closing-issues-via-commit-messages/
62
+ - The parser searches for all issue numbers and sorts them by their integer value, looks up their title, and outputs a formatted line with the issue number and title for each issue.
63
+ - The parser ignores everything else in the line that is not an issue number. Anything that is not an issue number will not appear in the rendered release notes
@@ -826,7 +826,7 @@ class TestRobotPerformanceKeywords:
826
826
  elapsed_times.sort()
827
827
 
828
828
  assert elapsed_times[1:] == [53, 11655.9, 18000.0]
829
- assert float(elapsed_times[0]) < 3
829
+ assert float(elapsed_times[0]) < 3 or float(elapsed_times[0]) <= 3.0
830
830
 
831
831
  def test_metrics(self):
832
832
  pattern = "Max_CPU_Percent: "
@@ -12,7 +12,11 @@ from cumulusci.core.source_transforms.transforms import (
12
12
  SourceTransform,
13
13
  SourceTransformList,
14
14
  )
15
- from cumulusci.core.utils import process_bool_arg, process_list_arg, determine_managed_mode
15
+ from cumulusci.core.utils import (
16
+ determine_managed_mode,
17
+ process_bool_arg,
18
+ process_list_arg,
19
+ )
16
20
  from cumulusci.salesforce_api.metadata import ApiDeploy, ApiRetrieveUnpackaged
17
21
  from cumulusci.salesforce_api.package_zip import MetadataPackageZipBuilder
18
22
  from cumulusci.salesforce_api.rest_deploy import RestDeploy
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from cumulusci.cli.ui import CliTable
8
8
  from cumulusci.core.exceptions import SalesforceException
9
- from cumulusci.core.utils import process_bool_arg, process_list_arg, determine_managed_mode
9
+ from cumulusci.core.utils import determine_managed_mode, process_list_arg
10
10
  from cumulusci.tasks.salesforce import BaseSalesforceApiTask
11
11
  from cumulusci.utils import inject_namespace
12
12
 
@@ -3,7 +3,7 @@
3
3
  from simple_salesforce.exceptions import SalesforceError
4
4
 
5
5
  from cumulusci.core.exceptions import TaskOptionsError
6
- from cumulusci.core.utils import process_bool_arg, determine_managed_mode
6
+ from cumulusci.core.utils import determine_managed_mode, process_bool_arg
7
7
  from cumulusci.tasks.salesforce import BaseSalesforceApiTask
8
8
 
9
9
 
@@ -1,7 +1,11 @@
1
1
  from simple_salesforce.exceptions import SalesforceError
2
2
 
3
3
  from cumulusci.core.exceptions import CumulusCIException
4
- from cumulusci.core.utils import process_bool_arg, process_list_arg, determine_managed_mode
4
+ from cumulusci.core.utils import (
5
+ determine_managed_mode,
6
+ process_bool_arg,
7
+ process_list_arg,
8
+ )
5
9
  from cumulusci.tasks.salesforce import BaseSalesforceApiTask
6
10
  from cumulusci.utils import inject_namespace
7
11
  from cumulusci.utils.http.requests_utils import safe_json_from_response
@@ -312,7 +312,7 @@ def retrieve_components(
312
312
  "5",
313
313
  "--ignore-conflicts",
314
314
  ]
315
-
315
+
316
316
  # Only add --output-dir if output_dir was specified
317
317
  if output_dir:
318
318
  sfdx_args.extend(["--output-dir", retrieve_target])