Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CumulusCI.yml linter based on Pydantic #1624

Merged
merged 39 commits into from
Apr 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d6eaca9
Add a parser for CCI.yml -- still needs to be cleaned up
Mar 3, 2020
ea7786c
Merge branch 'feature/mapping_parser' into feature/cci_yml_parser
Mar 4, 2020
f48bb74
Clean up some print statements.
Mar 4, 2020
09ae845
Rename file.
Mar 4, 2020
e0af2f3
More OOP API
Mar 4, 2020
731cefb
New apis
Mar 4, 2020
b083f15
Minor clarifications
Mar 4, 2020
c0b2e75
Pervasive warnings on loading cci.yml
Mar 4, 2020
4e6ab4a
Merge branch 'feature/mapping_parser' into feature/cci_yml_parser
Mar 4, 2020
77e0b1f
More test cases.
Mar 4, 2020
4090c9b
Cleanups and additional features.
Mar 4, 2020
d740e19
Make ApexDoc more flexible.
Mar 4, 2020
e108384
Clean up URL parsing code.
Mar 4, 2020
b401306
Merge branch 'feature/mapping_parser' into feature/cci_yml_parser
Mar 5, 2020
a08e44f
Merge branch 'feature/mapping_parser' into feature/cci_yml_parser
Mar 6, 2020
90a5364
Merge remote-tracking branch 'origin/master' into feature/cci_yml_parser
Mar 18, 2020
8154bb8
Clean up CCI.yml structured parsing code.
Mar 18, 2020
f6cf722
Add typing-extensions as a requirement.
Mar 18, 2020
d4fdba2
Cleanups to error handling.
Mar 19, 2020
4ce9193
Add docstrings.
Mar 19, 2020
7148431
Merge branch 'master' into feature/cci_yml_parser
Mar 19, 2020
0206fce
Merge 'master' into feature/cci_yml_parser
Apr 20, 2020
5bfef7f
Merge branch 'master' into feature/cci_yml_parser
May 12, 2020
7db0bb9
Merge branch 'master' into feature/cci_yml_parser
Jun 13, 2020
d4175ae
Merge remote-tracking branch 'origin/master' into feature/cci_yml_parser
Sep 15, 2020
fbd5383
Merge remote-tracking branch 'origin/main' into feature/cci_yml_parser
Feb 19, 2021
907cb24
Merge branch 'main' into feature/cci_yml_parser
Feb 24, 2021
89d9c70
Merge remote-tracking branch 'origin/main' into feature/cci_yml_parser
Feb 24, 2021
ffa5d8e
More assertive message
Feb 24, 2021
cc85944
Code cleanups
Feb 25, 2021
4f2ccca
Merge branch 'main' into feature/cci_yml_parser
Feb 25, 2021
90320c0
Merge branch 'main' into feature/cci_yml_parser
Mar 26, 2021
59783a4
Apply suggestions from code review
Apr 2, 2021
e047c00
Cleanups based on PR review
Apr 2, 2021
7b9a79a
Merge branch 'main' into feature/cci_yml_parser
Apr 7, 2021
7821840
Make more items optional. Add tests for smoke test harness.
Apr 8, 2021
759112d
make the message a little less scary
Apr 8, 2021
03a2809
Merge remote-tracking branch 'origin/main' into feature/cci_yml_parser
Apr 8, 2021
6091ee7
add dependency_resolutions, fix test for steps with both task and flow
Apr 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions cumulusci/core/config/project_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import os
import re
from io import StringIO
from pathlib import Path
from configparser import ConfigParser
from itertools import chain
Expand All @@ -10,7 +11,6 @@
API_VERSION_RE = re.compile(r"^\d\d+\.0$")

import github3
import yaml

from cumulusci.core.utils import merge_config
from cumulusci.core.config import BaseTaskFlowConfig
Expand Down Expand Up @@ -98,24 +98,26 @@ def _load_config(self):
)

# Load the project's yaml config file
with open(self.config_project_path, "r", encoding="utf-8") as f_config:
project_config = cci_safe_load(f_config)
project_config = cci_safe_load(self.config_project_path, logger=self.logger)

if project_config:
self.config_project.update(project_config)

# Load the local project yaml config file if it exists
if self.config_project_local_path:
with open(
self.config_project_local_path, "r", encoding="utf-8"
) as f_local_config:
local_config = cci_safe_load(f_local_config)
local_config = cci_safe_load(
self.config_project_local_path, logger=self.logger
)
if local_config:
self.config_project_local.update(local_config)

# merge in any additional yaml that was passed along
if self.additional_yaml:
additional_yaml_config = yaml.safe_load(self.additional_yaml)
additional_yaml_config = cci_safe_load(
StringIO(self.additional_yaml),
self.config_project_path,
logger=self.logger,
)
if additional_yaml_config:
self.config_additional_yaml.update(additional_yaml_config)

Expand Down
13 changes: 5 additions & 8 deletions cumulusci/core/config/universal_config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import os

import yaml
from pathlib import Path

from cumulusci.core.utils import merge_config
from cumulusci.core.config.project_config import BaseProjectConfig
from cumulusci.core.config import BaseTaskFlowConfig
from cumulusci.utils.yaml.cumulusci_yml import cci_safe_load

__location__ = os.path.dirname(os.path.realpath(__file__))

Expand Down Expand Up @@ -41,6 +40,7 @@ def default_cumulusci_dir():

@property
def config_global_path(self):
"""The global config path. Usually ~/.cumulusci/cumulusci.yml"""
directory = self.cumulusci_config_dir
if not os.path.exists(directory):
os.makedirs(directory)
Expand All @@ -63,15 +63,12 @@ def _load_config(self):
if UniversalConfig.config is not None:
return

# load the global config
with open(self.config_universal_path, "r", encoding="utf-8") as f_config:
config = yaml.safe_load(f_config)
UniversalConfig.config_universal = config
# load the universal config
UniversalConfig.config_universal = cci_safe_load(self.config_universal_path)

# Load the local config
if self.config_global_path:
with open(self.config_global_path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
config = cci_safe_load(self.config_global_path)
else:
config = {}
UniversalConfig.config_global = config
Expand Down
22 changes: 11 additions & 11 deletions cumulusci/utils/fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

"""Utilities for working with files"""

DataInput = Union[str, IO, Path, "FSResource"]


def _get_path_from_stream(stream):
"Try to infer a name from an open stream"
Expand All @@ -21,9 +23,7 @@ def _get_path_from_stream(stream):


@contextmanager
def load_from_source(
source: Union[str, IO, Path, "FSResource"]
) -> ContextManager[Tuple[Text, IO[Text]]]:
def load_from_source(source: DataInput) -> ContextManager[Tuple[IO[Text], Text]]:
"""Normalize potential data sources into uniform tuple

Take as input a file-like, path-like, or URL-like
Expand All @@ -37,14 +37,14 @@ def load_from_source(
For example:

>>> from yaml import safe_load
>>> with load_from_source("cumulusci.yml") as (path, file):
>>> with load_from_source("cumulusci.yml") as (file, path):
... print(path)
... print(safe_load(file).keys())
...
cumulusci.yml
dict_keys(['project', 'tasks', 'flows', 'orgs'])

>>> with load_from_source('http://www.salesforce.com') as (path, file):
>>> with load_from_source('http://www.salesforce.com') as (file, path):
... print(path)
... print(file.read(10).strip())
...
Expand All @@ -53,7 +53,7 @@ def load_from_source(

>>> from urllib.request import urlopen
>>> with urlopen("https://www.salesforce.com") as f:
... with load_from_source(f) as (path, file):
... with load_from_source(f) as (file, path):
... print(path)
... print(file.read(10).strip()) #doctest: +ELLIPSIS
...
Expand All @@ -62,7 +62,7 @@ def load_from_source(

>>> from pathlib import Path
>>> p = Path(".") / "cumulusci.yml"
>>> with load_from_source(p) as (path, file):
>>> with load_from_source(p) as (file, path):
... print(path)
... print(file.readline().strip())
...
Expand All @@ -75,20 +75,20 @@ def load_from_source(
path = _get_path_from_stream(source)
if not hasattr(source, "encoding"): # not decoded yet
source = TextIOWrapper(source, "utf-8")
yield path, source
yield source, path
elif hasattr(source, "open"): # pathlib.Path-like
with source.open("rt") as f:
path = str(source)
yield path, f
yield f, path
elif "://" in source: # URL string-like
url = source
resp = requests.get(url)
resp.raise_for_status()
yield url, StringIO(resp.text)
yield StringIO(resp.text), url
else: # path-string-like
path = source
with open(path, "rt") as f:
yield path, f
yield f, path


def proxy(funcname):
Expand Down
14 changes: 7 additions & 7 deletions cumulusci/utils/tests/test_fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_docstrings(self, urlopen):
raise

def test_binary_becomes_string(self):
with load_from_source(BytesIO(b"foo")) as (path, file):
with load_from_source(BytesIO(b"foo")) as (file, path):
data = file.read()
assert isinstance(data, str)
assert data == "foo"
Expand All @@ -45,37 +45,37 @@ def test_writable_file_throws(self):
with temporary_dir():
with open("writable", "wt") as t:
with pytest.raises(UnsupportedOperation):
with load_from_source(t) as (filename, data):
with load_from_source(t) as (data, filename):
pass

@responses.activate
def test_load_from_url(self):
html = "<!DOCTYPE HTML ..."
responses.add("GET", "http://www.salesforce.com", body=html)
with load_from_source("http://www.salesforce.com") as (filename, data):
with load_from_source("http://www.salesforce.com") as (data, filename):
assert data.read() == html

def test_load_from_Path(self):
p = Path(cumulusci.__file__).parent / "cumulusci.yml"
with load_from_source(p) as (filename, data):
with load_from_source(p) as (data, filename):
assert "tasks:" in data.read()

def test_load_from_path_string(self):
p = Path(cumulusci.__file__).parent / "cumulusci.yml"
with load_from_source(str(p)) as (filename, data):
with load_from_source(str(p)) as (data, filename):
assert "tasks:" in data.read()

def test_load_from_open_file(self):
p = Path(cumulusci.__file__).parent / "cumulusci.yml"
with open(p) as f:
with load_from_source(f) as (filename, data):
with load_from_source(f) as (data, filename):
assert "tasks:" in data.read()
assert str(p) == filename

def test_load_from_fs_resource(self):
p = Path(cumulusci.__file__).parent / "cumulusci.yml"
with open_fs_resource(p) as p2:
with load_from_source(p2) as (filename, data):
with load_from_source(p2) as (data, filename):
assert "tasks:" in data.read()


Expand Down
Loading