From: Edward Betts <edward@4angle.com>
Date: Mon, 2 Feb 2026 15:31:08 +0000
Subject: remove-toml

---
 pyproject.toml                           | 168 ++++++++++++++++++++++---------
 src/maison/config_sources/toml_source.py |  39 +++++++
 tests/conftest.py                        |  54 +++++++++-
 3 files changed, 212 insertions(+), 49 deletions(-)
 create mode 100644 src/maison/config_sources/toml_source.py

diff --git a/pyproject.toml b/pyproject.toml
index 580c3dc..645bc95 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,64 +1,136 @@
-[project]
+[tool.poetry]
 name = "maison"
-version = "2.0.2"
+version = "2.0.0"
 description = "Read settings from config files"
-authors = [
-    { name = "Dom Batten", email = "dominic.batten@googlemail.com" },
-]
+authors = ["Dom Batten <dominic.batten@googlemail.com>"]
 license = "MIT"
 readme = "README.md"
-requires-python = ">=3.9,<4.0"
-keywords = [
-    "maison",
-]
+homepage = "https://github.com/dbatten5/maison"
+repository = "https://github.com/dbatten5/maison"
+documentation = "https://maison.readthedocs.io"
 classifiers = [
+    "Development Status :: 3 - Alpha",
     "Programming Language :: Python :: 3.9",
-    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
 ]
-dependencies = [
-    "loguru>=0.7.3",
-    "platformdirs>=4.3.8",
-    "tomli>=2.2.1 ; python_full_version < '3.11'",
-    "typer>=0.15.4",
-    "typing-extensions>=4.13.2",
+include = [
+  { path = "tests", format = "sdist" }
 ]
 
-[dependency-groups]
-dev = [
-    "commitizen>=4.7.0",
-    "nox>=2025.5.1",
-    "pre-commit>=4.2.0",
-    "pre-commit-hooks>=5.0.0",
-    "ruff>=0.11.9",
-    "bandit>=1.8.3",
-    "pip-audit>=2.9.0",
-    "pytest>=8.3.5",
-    "pytest-cov>=6.1.1",
-    "pyright>=1.1.400",
-    "pydantic>=2.11.9",
+[tool.poetry.urls]
+Changelog = "https://github.com/dbatten5/maison/releases"
+
+[tool.poetry.dependencies]
+python = "^3.9.1"
+click = "^8.0.1"
+
+[tool.poetry.group.test.dependencies]
+pytest = "^8.3.0"
+pydantic = "^2.8.2"
+coverage = {extras = ["toml"], version = "^7.4.0"}
+six = "^1.16.0"
+
+[tool.poetry.group.dev.dependencies]
+typeguard = "^4.1.5"
+safety = "^2.3.5"
+mypy = "^1.11.0"
+black = "^24.8.0"
+pre-commit = "^3.5.0"
+pre-commit-hooks = "^4.0.1"
+Pygments = "^2.18.0"
+ruff = "^0.1.13"
+
+[tool.poetry.group.docs.dependencies]
+sphinx = "^6.0.0"
+sphinx-autobuild = ">=2024.4.16"
+sphinx-click = ">=3.0.2"
+myst-parser = "2.0.0"
+furo = "^2023.9.10"
+
+[tool.poetry.scripts]
+maison = "maison.__main__:main"
+
+[tool.coverage.paths]
+source = ["src", "*/site-packages"]
+
+[tool.coverage.run]
+branch = true
+source = ["maison"]
+
+[tool.coverage.report]
+show_missing = true
+fail_under = 100
+
+[tool.mypy]
+strict = true
+pretty = true
+show_column_numbers = true
+show_error_codes = true
+show_error_context = true
+
+[[tool.mypy.overrides]]
+module = ["toml"]
+ignore_missing_imports = true
+
+[tool.ruff]
+src = ['src', 'tests']
+line-length = 88
+target-version = 'py38'
+
+[tool.ruff.lint]
+ignore = [
+  'B019',
 ]
-docs = [
-    "furo>=2024.8.6",
-    "myst-parser>=3.0.1",
-    "sphinx>=7.4.7",
-    "sphinx-autobuild>=2024.10.3",
-    "sphinx-autodoc-typehints>=2.3.0",
-    "sphinx-copybutton>=0.5.2",
-    "sphinx-tabs>=3.4.7",
-    "sphinxcontrib-typer>=0.5.1",
+select = [
+    'A',
+    'ARG',
+    'B',
+    'B9',
+    'BLE',
+    'C',
+    'C4',
+    'D',
+    'DTZ',
+    'E',
+    'F',
+    'I',
+    'N',
+    'PIE',
+    'PT',
+    'PTH',
+    'Q',
+    'RET',
+    'RUF',
+    'S',
+    'SIM',
+    'SLF',
+    'T10',
+    'TCH',
+    'UP',
+    'W',
+]
+
+[tool.ruff.lint.per-file-ignores]
+"__init__.py" = ['F401']
+"tests/*" = [
+    'S',
+    'D102',
+    'D212',
+    'D415',
+    'D205',
+    'D104',
 ]
 
+[tool.ruff.lint.mccabe]
+max-complexity = 10
 
-[project.urls]
-Homepage = "https://github.com/dbatten/maison"
-Repository = "https://github.com/dbatten/maison"
+[tool.ruff.lint.pydocstyle]
+convention = 'google'
 
-[[tool.uv.index]]
-name = "testpypi"
-url = "https://test.pypi.org/simple/"
-publish-url = "https://test.pypi.org/legacy/"
-explicit = true
+[tool.ruff.lint.isort]
+force-single-line = true
 
 [build-system]
-requires = ["setuptools>=61.0"]
-build-backend = "setuptools.build_meta"
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/src/maison/config_sources/toml_source.py b/src/maison/config_sources/toml_source.py
new file mode 100644
index 0000000..f0de1c9
--- /dev/null
+++ b/src/maison/config_sources/toml_source.py
@@ -0,0 +1,39 @@
+"""Module to hold the `TomlSource` class definition."""
+
+import tomllib
+from functools import lru_cache
+from typing import Any
+from typing import Dict
+
+from ..errors import BadTomlError
+from .base_source import BaseSource
+
+
+class TomlSource(BaseSource):
+    """Class to represent a `.toml` config source."""
+
+    def to_dict(self) -> Dict[Any, Any]:
+        """Convert the source config file to a dict.
+
+        Returns:
+            a dict of the config options and values
+        """
+        return self._load_file()
+
+    @lru_cache
+    def _load_file(self) -> Dict[Any, Any]:
+        """Load the `.toml` file.
+
+        Returns:
+            the `.toml` source converted to a `dict`
+
+        Raises:
+            BadTomlError: If toml cannot be parsed
+        """
+        try:
+            with open(self.filepath, 'rb') as fd:
+                return dict(tomllib.load(fd))
+        except tomllib.TOMLDecodeError as exc:
+            raise BadTomlError(
+                f"Error trying to load toml file '{self.filepath}'"
+            ) from exc
diff --git a/tests/conftest.py b/tests/conftest.py
index 850262f..e295ebc 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1 +1,53 @@
-"""Fixtures used in all tests."""
+"""Store the classes and fixtures used throughout the tests."""
+
+from pathlib import Path
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Optional
+
+import pytest
+import tomli_w
+
+
+@pytest.fixture(name="create_tmp_file")
+def create_tmp_file_fixture(tmp_path: Path) -> Callable[..., Path]:
+    """Fixture for creating a temporary file."""
+
+    def _create_tmp_file(content: str = "", filename: str = "file.txt") -> Path:
+        tmp_file = tmp_path / filename
+        tmp_file.write_text(content)
+        return tmp_file
+
+    return _create_tmp_file
+
+
+@pytest.fixture(name="create_toml")
+def create_toml_fixture(create_tmp_file: Callable[..., Path]) -> Callable[..., Path]:
+    """Fixture for creating a `.toml` file."""
+
+    def _create_toml(
+        filename: str,
+        content: Optional[Dict[str, Any]] = None,
+    ) -> Path:
+        content = content or {}
+        config_toml = tomli_w.dumps(content)
+        return create_tmp_file(content=config_toml, filename=filename)
+
+    return _create_toml
+
+
+@pytest.fixture()
+def create_pyproject_toml(create_toml: Callable[..., Path]) -> Callable[..., Path]:
+    """Fixture for creating a `pyproject.toml`."""
+
+    def _create_pyproject_toml(
+        section_name: str = "foo",
+        content: Optional[Dict[str, Any]] = None,
+        filename: str = "pyproject.toml",
+    ) -> Path:
+        content = content or {"bar": "baz"}
+        config_dict = {"tool": {section_name: content}}
+        return create_toml(filename=filename, content=config_dict)
+
+    return _create_pyproject_toml
