Repository: jordantshaw/pydantic-config (language: python)

Issue:
Main to Dev Sync



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..a9b86e2
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,39 @@
+# This workflow will upload a Python Package using Twine when a release is created
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Publish
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python
+      uses: actions/setup-python@v3
+      with:
+        python-version: '3.x'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install build
+    - name: Build package
+      run: python -m build
+    - name: Publish package
+      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/README.md b/README.md
index 846c273..fe2a53d 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ description = "Test application description"
 ```
 
 ```python
-from pydantic_config import SettingsModel
+from src.pydantic_config import SettingsModel
 
 
 class Settings(SettingsModel):
@@ -53,7 +53,7 @@ description = "Test application description"
 ```
 
 ```python
-from pydantic_config import SettingsModel
+from src.pydantic_config import SettingsModel
 
 
 class Settings(SettingsModel):
@@ -91,7 +91,7 @@ item2 = "value2"
 ```
 
 ```python
-from pydantic_config import SettingsModel
+from src.pydantic_config import SettingsModel
 
 
 class Settings(SettingsModel):
diff --git a/pyproject.toml b/pyproject.toml
index 23ae836..cda2398 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
 name = "pydantic-config"
 description = "Support for Pydantic settings configuration file loading"
 version = "0.1.0"
-authors = ["Jordan Shaw"]
+authors = [{name="Jordan Shaw"}]
 readme = "README.md"
 requires-python = ">=3.7"
 classifiers = [
@@ -19,11 +19,24 @@ classifiers = [
 ]
 
 dependencies = [
-    'pydantic',
-    'pyyaml',
-    'python-dotenv',
-    'toml'
+    'pydantic==1.10.5',
+    'pyyaml==6.0',
+    'python-dotenv==0.21.0',
+    'toml==0.10.2'
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest", 'twine', 'build'
+]
+test = [
+    "pytest"
 ]
 
 [project.urls]
 "Homepage" = "https://github.com/jordantshaw/pydantic-config"
+
+[tool.pytest.ini_options]
+addopts = [
+    "--import-mode=importlib",
+]
diff --git a/pydantic_config/__init__.py b/src/pydantic_config/__init__.py
similarity index 100%
rename from pydantic_config/__init__.py
rename to src/pydantic_config/__init__.py
diff --git a/pydantic_config/loaders.py b/src/pydantic_config/loaders.py
similarity index 100%
rename from pydantic_config/loaders.py
rename to src/pydantic_config/loaders.py
diff --git a/pydantic_config/main.py b/src/pydantic_config/main.py
similarity index 99%
rename from pydantic_config/main.py
rename to src/pydantic_config/main.py
index 54db28f..d1975de 100644
--- a/pydantic_config/main.py
+++ b/src/pydantic_config/main.py
@@ -36,7 +36,6 @@ def customise_sources(
             )
 
 
-
 def config_file_settings(settings: BaseSettings) -> Dict[str, Any]:
     encoding = getattr(settings.__config__, 'config_file_encoding', None)
     files = getattr(settings.__config__, 'config_file', [])


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..b1f4b5c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,39 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+
+name: Test
+
+on:
+  push:
+    branches: [ "main", "develop" ]
+  pull_request:
+    branches: [ "main", "develop" ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install flake8 pytest
+        pip install -e .[test];
+    - name: Lint with flake8
+      run: |
+        # stop the build if there are Python syntax errors or undefined names
+        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+    - name: Test with pytest
+      run: |
+        pytest
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..0b7fc9d
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,60 @@
+from datetime import timedelta
+from typing import Generator, Any
+
+import pytest
+
+
+@pytest.fixture(scope='session')
+def config_toml_file(tmp_path_factory):
+    config = '''
+    [app]
+    description = "description from config.toml"
+    '''
+    file_path = tmp_path_factory.mktemp("data") / "config.toml"
+    with open(file_path, 'w') as file:
+        file.write(config)
+
+    return file_path
+
+
+@pytest.fixture(scope='session')
+def config_yaml_file(tmp_path_factory):
+    config = '''
+    app:
+        description: description from config.yaml
+    '''
+    file_path = tmp_path_factory.mktemp("data") / "config.yaml"
+    with open(file_path, 'w') as file:
+        file.write(config)
+
+    return file_path
+
+
+@pytest.fixture(scope='session')
+def config_ini_file(tmp_path_factory):
+    config = '''
+    [APP]
+    DESCRIPTION = description from config.ini
+    '''
+    file_path = tmp_path_factory.mktemp("data") / "config.ini"
+    with open(file_path, 'w') as file:
+        file.write(config)
+
+    return file_path
+
+
+@pytest.fixture(scope='session')
+def config_json_file(tmp_path_factory):
+    config = '''
+    {
+        "app": {
+            "description": "description from config.json"
+        }
+    }
+    '''
+    file_path = tmp_path_factory.mktemp("data") / "config.json"
+    with open(file_path, 'w') as file:
+        file.write(config)
+
+    return file_path
+
diff --git a/tests/test_loaders.py b/tests/test_loaders.py
new file mode 100644
index 0000000..3ac39e9
--- /dev/null
+++ b/tests/test_loaders.py
@@ -0,0 +1,21 @@
+from pydantic_config.loaders import toml_file_loader, ini_file_loader, yaml_file_loader, json_file_loader
+
+
+def test_toml_load(config_toml_file):
+    data = toml_file_loader(config_toml_file)
+    assert data == {'app': {'description': 'description from config.toml'}}
+
+
+def test_ini_load(config_ini_file):
+    data = ini_file_loader(config_ini_file)
+    assert data == {'DEFAULT': {}, 'APP': {'description': 'description from config.ini'}}
+
+
+def test_yaml_load(config_yaml_file):
+    data = yaml_file_loader(config_yaml_file)
+    assert data == {'app': {'description': 'description from config.yaml'}}
+
+
+def test_json_load(config_json_file):
+    data = json_file_loader(config_json_file)
+    assert data == {'app': {'description': 'description from config.json'}}
diff --git a/tests/test_settings_model.py b/tests/test_settings_model.py
new file mode 100644
index 0000000..0c37b46
--- /dev/null
+++ b/tests/test_settings_model.py
@@ -0,0 +1,48 @@
+import pytest
+from pydantic import BaseModel
+
+from pydantic_config import SettingsModel
+
+
+def test_config_file(config_toml_file):
+    class App(BaseModel):
+        name: str = 'AppName'
+        description: str = None
+
+    class Settings(SettingsModel):
+        app: App
+
+        class Config:
+            config_file = [config_toml_file]
+
+    settings = Settings()
+    assert settings.dict() == {'app': {'name': 'AppName', 'description': 'description from config.toml'}}
+
+
+def test_invalid_config_file():
+    file = 'invalid/file/path/file.toml'
+
+    class Settings(SettingsModel):
+        foo: str = 'bar'
+
+        class Config:
+            config_file = [file]
+
+    with pytest.raises(OSError) as exc:
+        Settings()
+
+
+def test_multiple_config_files(config_toml_file, config_yaml_file):
+    class App(BaseModel):
+        name: str = 'AppName'
+        description: str = None
+
+    class Settings(SettingsModel):
+        app: App
+
+        class Config:
+            config_file = [config_toml_file, config_yaml_file]
+
+    settings = Settings()
+    assert settings.dict() == {'app': {'name': 'AppName', 'description': 'description from config.yaml'}}
+