diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index ec33edc..2a2975c 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -6,8 +6,41 @@ on: - "v[0-9]+.[0-9]+.[0-9]+" jobs: + ci: + name: "Run tests" + strategy: + fail-fast: false + matrix: + python-version: [ + "3.9", + "3.10", + "3.11", + "3.12", + ] + poetry-version: ["1.6.1"] + os: [ + ubuntu-latest, + macos-latest, +# windows-latest, # Disabling Windows to get build working + ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install --all-extras + - name: Run tests + run: poetry run pytest + tagged-release: # TODO: Maybe make this conditional so it can run on pull requests name: "Tagged Release" + needs: ci runs-on: "ubuntu-latest" steps: @@ -24,6 +57,13 @@ jobs: run: | poetry self add "poetry-dynamic-versioning[plugin]" poetry install --all-extras + - name: Test & publish code coverage + uses: paambaati/codeclimate-action@v5.0.0 + env: + CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} + with: + coverageCommand: poetry run pytest + coverageLocations: ${{github.workspace}}/dist/coverage.info:lcov - name: Create Github release uses: "marvinpinto/action-automatic-releases@latest" with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c2675af --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,67 @@ +name: "build" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + ci: + name: "Run tests" + strategy: + fail-fast: false + matrix: + python-version: [ + "3.9", + "3.10", + "3.11", + "3.12", + ] + poetry-version: ["1.6.1"] + os: [ + ubuntu-latest, + macos-latest, +# windows-latest, # Disabling Windows to get build working + ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install dependencies + run: poetry install --all-extras + - name: Run tests + run: poetry run pytest + + code-coverage: # TODO: Maybe make this conditional so it can run on pull requests + name: "Code coverage" + needs: ci + runs-on: "ubuntu-latest" + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Run image + uses: abatilo/actions-poetry@v2 + with: + poetry-version: '1.6.1' + - name: Install dependencies + run: | + poetry self add "poetry-dynamic-versioning[plugin]" + poetry install --without dev --with test --all-extras + - name: Test & publish code coverage + uses: paambaati/codeclimate-action@v5.0.0 + env: + CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} + with: + coverageCommand: poetry run pytest + coverageLocations: ${{github.workspace}}/dist/coverage.info:lcov \ No newline at end of file diff --git a/prosper_shared/omni_config/define.py b/prosper_shared/omni_config/define.py index e1b6d9d..bec5d0a 100644 --- a/prosper_shared/omni_config/define.py +++ b/prosper_shared/omni_config/define.py @@ -18,11 +18,6 @@ def __init__(self, expected_val: str, custom_env_var_prefix: Optional[str] = Non self._expected_val = expected_val self._custom_env_variable_prefix = custom_env_var_prefix - @property - def custom_env_variable_prefix(self) -> Optional[str]: - """Getter for custom env variable prefix.""" - return self._custom_env_variable_prefix - def validate(self, val: str) -> str: """Returns the key iff the key is a string value matching the expected value. diff --git a/prosper_shared/omni_config/parse.py b/prosper_shared/omni_config/parse.py index 04201dd..07f7979 100644 --- a/prosper_shared/omni_config/parse.py +++ b/prosper_shared/omni_config/parse.py @@ -83,6 +83,7 @@ def read(self) -> dict: nested_config = {} for key, val in raw_namespace.__dict__.items(): + # TODO: This can cause weird behavior if a key is explicitly set to the default value if val is None or any( a for a in self._argument_parser._actions @@ -127,10 +128,10 @@ def read(self) -> dict: """ result = dict() value_map: Dict[str, str] = EnvironmentVariableSource.__get_value_map() - mapped_variables: List[str] = [ + matching_variables: List[str] = [ key for (key, _) in value_map.items() if key.startswith(self.__prefix) ] - for key in mapped_variables: + for key in matching_variables: value = value_map[key] sanitized: List[str] = self.__sanitize_key(key) items: dict = result @@ -149,43 +150,13 @@ def read(self) -> dict: @staticmethod def __get_value_map() -> Dict[str, str]: - """Gets a list of key-value-pairs representing the environment variables. - - Returns: - Dict[str, str]: The key-value map. - """ return os.environ.copy() def __sanitize_key(self, key: str) -> List[str]: - """Splits a key according to the specified separators. - - Args: - key (str): The key to split into lists. - - Returns: - List[str]: The list of split keys. - """ - if key is not None: - if key.startswith(self.__prefix): - key = key[len(self.__prefix) + 1 :] - - return key.split(self.__separator) - - return [key] + return key[len(self.__prefix) + 1 :].split(self.__separator) def __sanitize_value(self, value: str) -> Union[str, List[str]]: - """Splits a value into a list, if applicable. - - Args: - value (str): The value to parse. - - Returns: - Union[str, List[str]]: The value parsed. - """ - if value is not None: - if self.__list_item_separator in value: - return value.split(self.__list_item_separator) - - return value + if self.__list_item_separator in value: + return value.split(self.__list_item_separator) - return "" + return value diff --git a/pyproject.toml b/pyproject.toml index c1647b6..b437050 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ spaces_indent_inline_array = 4 sort_first = ["tool.poetry"] [tool.pytest.ini_options] -#addopts = "--cov=prosper_shared/omni_onfig --cov-report=term-missing --cov-report=lcov:dist/coverage.info --cov-fail-under=0" +addopts = "--cov=prosper_shared/omni_config --cov-report=term-missing --cov-report=lcov:dist/coverage.info --cov-fail-under=100" testpaths = "tests" python_functions = ["*_test", "test_*"] diff --git a/tests/omni_config/data/test_pyproject.toml b/tests/omni_config/data/test_parse_config.toml similarity index 100% rename from tests/omni_config/data/test_pyproject.toml rename to tests/omni_config/data/test_parse_config.toml diff --git a/tests/omni_config/data/test_parse_config_invalid.toml b/tests/omni_config/data/test_parse_config_invalid.toml new file mode 100644 index 0000000..f73974c --- /dev/null +++ b/tests/omni_config/data/test_parse_config_invalid.toml @@ -0,0 +1,2 @@ +[tool] +lib-name="asdf" \ No newline at end of file diff --git a/tests/omni_config/test_parse.py b/tests/omni_config/test_parse.py index 6f487dc..4154df5 100644 --- a/tests/omni_config/test_parse.py +++ b/tests/omni_config/test_parse.py @@ -2,6 +2,8 @@ import sys from os.path import dirname, join +import pytest + from prosper_shared.omni_config.parse import ( ArgParseSource, EnvironmentVariableSource, @@ -26,7 +28,7 @@ def test_toml_read(self): def test_toml_read_with_config_root(self): toml_config_source = TomlConfigurationSource( - join(dirname(__file__), "data", "test_pyproject.toml"), "tool.lib-name" + join(dirname(__file__), "data", "test_parse_config.toml"), "tool.lib-name" ) assert { @@ -38,6 +40,15 @@ def test_toml_read_with_config_root(self): } } == toml_config_source.read() + def test_toml_read_with_config_root_invalid(self): + toml_config_source = TomlConfigurationSource( + join(dirname(__file__), "data", "test_parse_config_invalid.toml"), + "tool.lib-name", + ) + + with pytest.raises(ValueError): + toml_config_source.read() + def test_toml_read_not_exists(self): toml_config_source = TomlConfigurationSource( join(dirname(__file__), "non_existent.toml") @@ -69,6 +80,11 @@ def test_argparse_read(self, mocker): "--list-config", dest="section1__list_config", action="append" ) parser.add_argument("--string-config", dest="section1__string_config") + parser.add_argument( + "--other-string-config", + dest="section1__other_string_config", + default="asdf", + ) argparse_config_source = ArgParseSource(parser) @@ -81,7 +97,10 @@ def test_argparse_read(self, mocker): "--int-config=123", "--list-config=asdf", "--list-config=qwer", - "--string-config='string value'", + "--string-config", + "string value", + "--other-string-config", + "asdf", ], ) assert { @@ -89,6 +108,6 @@ def test_argparse_read(self, mocker): "float_config": "123.456", "int_config": "123", "list_config": "['asdf', 'qwer']", - "string_config": "'string value'", + "string_config": "string value", } } == argparse_config_source.read()