diff --git a/flopy4/singledispatch/__init__.py b/flopy4/singledispatch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flopy4/singledispatch/plot.py b/flopy4/singledispatch/plot.py new file mode 100644 index 0000000..31933b4 --- /dev/null +++ b/flopy4/singledispatch/plot.py @@ -0,0 +1,9 @@ +from functools import singledispatch +from typing import Any + + +@singledispatch +def plot(obj, **kwargs) -> Any: + raise NotImplementedError( + "plot method not implemented for type {}".format(type(obj)) + ) diff --git a/flopy4/singledispatch/plot_int.py b/flopy4/singledispatch/plot_int.py new file mode 100644 index 0000000..40ab777 --- /dev/null +++ b/flopy4/singledispatch/plot_int.py @@ -0,0 +1,9 @@ +from typing import Any + +from flopy4.singledispatch.plot import plot + + +@plot.register +def _(v: int, **kwargs) -> Any: + print(f"Plotting a model with kwargs: {kwargs}") + return v diff --git a/pixi.lock b/pixi.lock index f126416..1cc48eb 100644 --- a/pixi.lock +++ b/pixi.lock @@ -99,9 +99,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - pypi: https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl @@ -483,9 +483,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - pypi: https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl @@ -962,9 +962,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - pypi: https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl @@ -1417,9 +1417,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - pypi: https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl @@ -1873,9 +1873,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - pypi: https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl @@ -3049,7 +3049,7 @@ packages: name: flopy4 version: 0.0.1.dev0 path: . - sha256: 6dc11acd173d84f6bfdbbcced580fe6754c11f58eb7898e13670adf97ba8d101 + sha256: 46f0c14db79a4a397d25784553531ba81338f135efd9998bbe756f935d200d25 requires_dist: - attrs - cattrs @@ -7812,55 +7812,55 @@ packages: - kind: conda name: vc version: '14.3' - build: h8a93ad2_21 - build_number: 21 + build: ha32ba9b_22 + build_number: 22 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_21.conda - sha256: f14f5238c2e2516e292af43d91df88f212d769b4853eb46d03291793dcf00da9 - md5: e632a9b865d4b653aa656c9fb4f4817c + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + sha256: 2a47c5bd8bec045959afada7063feacd074ad66b170c1ea92dd139b389fcf8fd + md5: 311c9ba1dfdd2895a8cb08346ff26259 depends: - - vc14_runtime >=14.40.33810 + - vc14_runtime >=14.38.33135 track_features: - vc14 license: BSD-3-Clause license_family: BSD purls: [] - size: 17243 - timestamp: 1725984095174 + size: 17447 + timestamp: 1728400826998 - kind: conda name: vc14_runtime version: 14.40.33810 - build: ha82c5b3_21 - build_number: 21 + build: hcc2c482_22 + build_number: 22 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_21.conda - sha256: c3bf51bff7db39ad7e890dbef1b1026df0af36975aea24dea7c5fe1e0b382c40 - md5: b3ebb670caf046e32b835fbda056c4f9 + url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + sha256: 4c669c65007f88a7cdd560192f7e6d5679d191ac71610db724e18b2410964d64 + md5: ce23a4b980ee0556a118ed96550ff3f3 depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.40.33810.* *_21 - license: LicenseRef-ProprietaryMicrosoft + - vs2015_runtime 14.40.33810.* *_22 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 751757 - timestamp: 1725984166774 + size: 750719 + timestamp: 1728401055788 - kind: conda name: vs2015_runtime version: 14.40.33810 - build: h3bf8584_21 - build_number: 21 + build: h3bf8584_22 + build_number: 22 subdir: win-64 - url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_21.conda - sha256: 472410455c381e406ec8c1d3e0342b48ee23122ef7ffb22a09d9763ca5df4d20 - md5: b3f37db7b7ae1c22600fa26a63ed99b3 + url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda + sha256: 80aa9932203d65a96f817b8be4fafc176fb2b3fe6cf6899ede678b8f0317fbff + md5: 8c6b061d44cafdfc8e8c6eb5f100caf0 depends: - vc14_runtime >=14.40.33810 license: BSD-3-Clause license_family: BSD purls: [] - size: 17241 - timestamp: 1725984096440 + size: 17453 + timestamp: 1728400827536 - kind: pypi name: wcwidth version: 0.2.13 diff --git a/pyproject.toml b/pyproject.toml index 11d66a0..880d504 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,6 +112,9 @@ ignore = [ "E741", # ambiguous variable name ] +[project.entry-points.flopy4] +plot = "flopy4.singledispatch.plot_int" + [tool.pixi.project] channels = ["conda-forge"] platforms = ["win-64", "linux-64", "osx-64"] @@ -145,3 +148,4 @@ test = { cmd = "pytest -v -n auto" } [tool.pixi.feature.lint.tasks] lint = { cmd = "ruff check ." } + diff --git a/test/test_singledispatch.py b/test/test_singledispatch.py new file mode 100644 index 0000000..6e5337b --- /dev/null +++ b/test/test_singledispatch.py @@ -0,0 +1,62 @@ +import ast +import inspect +import subprocess +import sys +from importlib.metadata import entry_points + +import pytest + +from flopy4.singledispatch.plot import plot + + +def get_function_body(func): + source = inspect.getsource(func) + parsed = ast.parse(source) + for node in ast.walk(parsed): + if isinstance(node, ast.FunctionDef): + return ast.get_source_segment(source, node.body[0]) + raise ValueError("Function body not found") + + +def run_test_in_subprocess(test_func): + def wrapper(): + test_func_source = get_function_body(test_func) + test_code = f""" +import pytest +from importlib.metadata import entry_points +from flopy4.singledispatch.plot import plot + +{test_func_source} + +""" + result = subprocess.run( + [sys.executable, "-c", test_code], capture_output=True, text=True + ) + if result.returncode != 0: + print(result.stdout) + print(result.stderr) + assert result.returncode == 0, f"Test failed: {test_func.__name__}" + + return wrapper + + +@run_test_in_subprocess +def test_register_singledispatch_with_entrypoints(): + eps = entry_points(group="flopy4", name="plot") + for ep in eps: + ep.load() + + # should not throw an error, because plot_int was loaded via entry points + return_val = plot(5) + assert return_val == 5 + with pytest.raises(NotImplementedError): + plot("five") + + +@run_test_in_subprocess +def test_register_singledispatch_without_entrypoints(): + # should throw an error, because plot_int was not loaded via entry points + with pytest.raises(NotImplementedError): + plot(5) + with pytest.raises(NotImplementedError): + plot("five")