From ac1743d642039d166751678224adc948a42a8271 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Oct 2023 18:09:50 +0100 Subject: [PATCH 01/10] Test with python 3.13 --- .github/workflows/run-all-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index f16bc71e..6e721053 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13-dev'] steps: - uses: actions/checkout@v4 From e6f54b93243dc73acdbeefa4f50a19819f30ba06 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Mon, 23 Oct 2023 14:11:27 +0100 Subject: [PATCH 02/10] Drop setup.py in favour of pyproject.toml #170 --- .gitignore | 9 ++++++ README.rst | 15 ++++++---- docs/HISTORY.txt | 5 ++++ pyproject.toml | 47 ++++++++++++++++++++++++++++++ setup.py | 75 ------------------------------------------------ 5 files changed, 71 insertions(+), 80 deletions(-) delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 9f0ccb1f..cc9c4328 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,15 @@ venv/ .venv/ coverage.xml .mutmut-cache +.watchmanconfig html/ .dccache +.pytest_cache +.pyre/ +.mypy_cache/ +.pytype/ +.ruff_cache/ +.venv/ +venv/ +__pycache__/ *.stderr* diff --git a/README.rst b/README.rst index 1885abd4..68fde4a6 100644 --- a/README.rst +++ b/README.rst @@ -12,17 +12,22 @@ implement this protocol: * `Shapely `_ * `pyshp `_ -So when you want to write your own geospatial library with support +When you want to write your own geospatial library with support for this protocol you may use pygeoif as a starting point and build -your functionality on top of it +your functionality on top of it. It has no requirements outside the +Python standard library and is therefore easy to integrate into your +project. It is tested on `CPython `_ and +`PyPy `_, but it should work on alternative +Python implementations like `Jython `_ or +`IronPython `_ as well. You may think of pygeoif as a 'shapely ultralight' which lets you construct geometries and perform **very** basic operations like reading and writing geometries from/to WKT, constructing line strings out of points, polygons from linear rings, multi polygons from polygons, etc. It was inspired by shapely and implements the -geometries in a way that when you are familiar with shapely -you feel right at home with pygeoif. +geometries in a way that when you are familiar with pygeoif, +you will feel right at home with shapely or the other way round. It was written to provide clean and python only geometries for fastkml_ @@ -35,7 +40,7 @@ It was written to provide clean and python only geometries for fastkml_ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black -.. image:: https://img.shields.io/badge/type%20checker-mypy-blue +.. image:: https://img.shields.io/badge/type-checker-mypy-blue :target: http://mypy-lang.org/ .. image:: https://www.openhub.net/p/pygeoif/widgets/project_thin_badge.gif diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 1916fb4e..87776683 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -1,6 +1,11 @@ Changelog ========= +1.1.1 (unreleased) +------------------ + + - Use pyproject.toml instead of setup.py + 1.1 (2023/10/13) ----------------- diff --git a/pyproject.toml b/pyproject.toml index 7d93569f..a0b606d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,50 @@ +[build-system] +requires = ["setuptools>=61.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "pygeoif" +version = "1.1.1" +authors = [{name = "Christian Ledermann", email = "christian.ledermann@gmail.com"}] +license = {text = "LGPL"} +description = "A basic implementation of the __geo_interface__" +keywords = ["GIS", "Spatial", "WKT"] +classifiers = [ + "Topic :: Scientific/Engineering :: GIS", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Development Status :: 5 - Production/Stable", + "Operating System :: OS Independent", +] +urls = {Homepage = "https://github.com/cleder/pygeoif/"} +requires-python = ">=3.7" +dependencies = ["typing_extensions"] + +[project.readme] +file = "README.rst" +content-type = "text/x-rst" + +[project.optional-dependencies] +testing = ["pytest"] + +[tool.setuptools] +zip-safe = true +include-package-data = true + +[tool.setuptools.packages.find] +exclude = ["ez_setup", "docs"] # examples; tests +namespaces = false + [tool.check-manifest] ignore = [".*", "mutmut_config.py", "test-requirements.txt", "tox.ini"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6df7a579..00000000 --- a/setup.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Setup script.""" -import os -import sys -from typing import Any -from typing import List - -from setuptools import find_packages -from setuptools import setup -from setuptools.command.test import test as TestCommand # noqa: N812 - - -class PyTest(TestCommand): - """Run the tests with PyTest.""" - - def finalize_options(self) -> None: - TestCommand.finalize_options(self) - self.test_args: List[Any] = [] - self.test_suite = True - - def run_tests(self) -> None: - # import here, cause outside the eggs aren't loaded - import pytest - - errno = pytest.main(self.test_args) - sys.exit(errno) - - -version = "1.1.0" - -with open("README.rst", encoding="utf-8") as readme: - README = readme.read() -with open(os.path.join("docs", "HISTORY.txt"), encoding="utf-8") as changelog: - HISTORY = changelog.read() - -setup( - name="pygeoif", - version=version, - description="A basic implementation of the __geo_interface__", - long_description=README + "\n" + HISTORY, - classifiers=[ - "Topic :: Scientific/Engineering :: GIS", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "Development Status :: 5 - Production/Stable", - "Operating System :: OS Independent", - ], - # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers - keywords="GIS Spatial WKT", - author="Christian Ledermann", - author_email="christian.ledermann@gmail.com", - url="https://github.com/cleder/pygeoif/", - license="LGPL", - packages=find_packages(exclude=["ez_setup", "examples", "tests"]), - include_package_data=True, - zip_safe=False, - tests_require=["pytest"], - install_requires=["typing_extensions"], - python_requires=">=3.7", - cmdclass={"test": PyTest}, - entry_points=""" - # -*- Entry points: -*- - """, -) - -__all__ = ["version"] From b736063d4ccc9eafd6b5c65f409d4f5506cb7f2b Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Mon, 23 Oct 2023 16:11:41 +0100 Subject: [PATCH 03/10] format pyproject.toml --- .pre-commit-config.yaml | 27 +++++---- pyproject.toml | 123 ++++++++++++++++++++++++---------------- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f5d5e3c..9ebed4fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,17 +40,12 @@ repos: rev: 5.12.0 hooks: - id: isort - # - repo: https://github.com/dhruvmanila/remove-print-statements - # rev: 'v0.5.0' - # hooks: - # - id: remove-print-statements - # args: ['--verbose'] # Show all the print statements to be removed - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.0 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.0.292' + rev: 'v0.1.1' hooks: - id: ruff - repo: https://github.com/PyCQA/flake8 @@ -90,7 +85,7 @@ repos: # - flake8-use-fstring - pep8-naming - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.0 + rev: v1.6.1 hooks: - id: mypy # - repo: https://github.com/Lucas-C/pre-commit-hooks-markup @@ -101,14 +96,22 @@ repos: rev: "0.49" hooks: - id: check-manifest + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.15 + hooks: + - id: validate-pyproject + - repo: https://github.com/kieran-ryan/pyprojectsort + rev: v0.3.0 + hooks: + - id: pyprojectsort - repo: https://github.com/python-jsonschema/check-jsonschema rev: "0.27.0" hooks: - id: check-github-workflows - id: check-github-actions - id: check-readthedocs - # - repo: https://github.com/regebro/pyroma - # rev: "4.1" - # hooks: - # - id: pyroma + - repo: https://github.com/regebro/pyroma + rev: "4.2" + hooks: + - id: pyroma ... diff --git a/pyproject.toml b/pyproject.toml index a0b606d2..4cee34cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,18 @@ [build-system] -requires = ["setuptools>=61.2"] build-backend = "setuptools.build_meta" +requires = [ + "setuptools>=61.2", +] [project] -name = "pygeoif" -version = "1.1.1" -authors = [{name = "Christian Ledermann", email = "christian.ledermann@gmail.com"}] -license = {text = "LGPL"} -description = "A basic implementation of the __geo_interface__" -keywords = ["GIS", "Spatial", "WKT"] +authors = [ + { email = "christian.ledermann@gmail.com", name = "Christian Ledermann" }, +] classifiers = [ - "Topic :: Scientific/Engineering :: GIS", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", @@ -21,78 +23,103 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "Development Status :: 5 - Production/Stable", - "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: GIS", +] +dependencies = [ + "typing_extensions", +] +description = "A basic implementation of the __geo_interface__" +keywords = [ + "GIS", + "Spatial", + "WKT", ] -urls = {Homepage = "https://github.com/cleder/pygeoif/"} +name = "pygeoif" requires-python = ">=3.7" -dependencies = ["typing_extensions"] +version = "1.1.1" -[project.readme] -file = "README.rst" -content-type = "text/x-rst" +[project.license] +text = "LGPL" [project.optional-dependencies] -testing = ["pytest"] +testing = [ + "pytest", +] -[tool.setuptools] -zip-safe = true -include-package-data = true +[project.readme] +content-type = "text/x-rst" +file = "README.rst" -[tool.setuptools.packages.find] -exclude = ["ez_setup", "docs"] # examples; tests -namespaces = false +[project.urls] +Homepage = "https://github.com/cleder/pygeoif/" [tool.check-manifest] -ignore = [".*", "mutmut_config.py", "test-requirements.txt", "tox.ini"] +ignore = [ + ".*", + "mutmut_config.py", + "test-requirements.txt", + "tox.ini", +] [tool.flake8] max_line_length = 88 [tool.isort] -line_length = 88 force_single_line = true +line_length = 88 [tool.mypy] disallow_any_generics = true -disallow_untyped_calls = true -disallow_untyped_defs = true disallow_incomplete_defs = true +disallow_untyped_calls = true disallow_untyped_decorators = true +disallow_untyped_defs = true ignore_errors = false ignore_missing_imports = false implicit_reexport = false -strict_optional = true -strict_equality = true no_implicit_optional = true -warn_unused_ignores = true -warn_redundant_casts = true -warn_unused_configs = true -warn_unreachable = true +overrides = [ + { disallow_untyped_defs = false, ignore_errors = true, module = "tests.*" }, +] +show_error_codes = true +strict_equality = true +strict_optional = true warn_no_return = true +warn_redundant_casts = true warn_return_any = true -show_error_codes = true - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false -ignore_errors = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true [tool.pyright] -include = ["pygeoif"] -exclude = ["**/node_modules", +exclude = [ "**/__pycache__", - ".pytype", + "**/node_modules", ".pyre", - "pygeoif/tests" + ".pytype", + "pygeoif/tests", +] +include = [ + "pygeoif", ] reportMissingImports = true reportMissingTypeStubs = true -[tool.ruff] - [tool.ruff.per-file-ignores] -"tests/*.py" = ["D103"] -"setup.py" = ["E501"] +"setup.py" = [ + "E501", +] +"tests/*.py" = [ + "D103", +] + +[tool.setuptools] +include-package-data = true +zip-safe = true + +[tool.setuptools.packages.find] +exclude = [ + "docs", + "ez_setup", +] +namespaces = false From dac3e64af9b5c49b8cdbcc0f74aaf8cb2bdd24f2 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Mon, 23 Oct 2023 18:37:32 +0100 Subject: [PATCH 04/10] make version dynamic to have only one source of truth --- README.rst | 3 +-- pygeoif/__init__.py | 3 ++- pygeoif/about.py | 6 ++++++ pyproject.toml | 16 ++++++++-------- 4 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 pygeoif/about.py diff --git a/README.rst b/README.rst index 68fde4a6..551ed028 100644 --- a/README.rst +++ b/README.rst @@ -18,8 +18,7 @@ your functionality on top of it. It has no requirements outside the Python standard library and is therefore easy to integrate into your project. It is tested on `CPython `_ and `PyPy `_, but it should work on alternative -Python implementations like `Jython `_ or -`IronPython `_ as well. +Python implementations (that implement the language specification *>=3.8*) as well. You may think of pygeoif as a 'shapely ultralight' which lets you construct geometries and perform **very** basic operations like diff --git a/pygeoif/__init__.py b/pygeoif/__init__.py index d030abd7..ff97a73f 100644 --- a/pygeoif/__init__.py +++ b/pygeoif/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2012 - 2022 Christian Ledermann +# Copyright (C) 2012 - 2023 Christian Ledermann # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -16,6 +16,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # """PyGeoIf provides a GeoJSON-like protocol for geo-spatial (GIS) vector data.""" +from pygeoif.about import __version__ # noqa: F401 from pygeoif.factories import from_wkt from pygeoif.factories import mapping from pygeoif.factories import orient diff --git a/pygeoif/about.py b/pygeoif/about.py new file mode 100644 index 00000000..b3df8abd --- /dev/null +++ b/pygeoif/about.py @@ -0,0 +1,6 @@ +""" +About pygeoif. + +The only purpose of this module is to provide a version number for the package. +""" +__version__ = "1.1.1" diff --git a/pyproject.toml b/pyproject.toml index 4cee34cd..0bd0ced5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ dependencies = [ "typing_extensions", ] description = "A basic implementation of the __geo_interface__" +dynamic = [ + "version", +] keywords = [ "GIS", "Spatial", @@ -36,7 +39,6 @@ keywords = [ ] name = "pygeoif" requires-python = ">=3.7" -version = "1.1.1" [project.license] text = "LGPL" @@ -115,11 +117,9 @@ reportMissingTypeStubs = true [tool.setuptools] include-package-data = true -zip-safe = true - -[tool.setuptools.packages.find] -exclude = [ - "docs", - "ez_setup", +packages = [ + "pygeoif", ] -namespaces = false + +[tool.setuptools.dynamic.version] +attr = "pygeoif.about.__version__" From e3e48097b4f4f43b7a829b04c04264d8f44cbc3e Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Mon, 23 Oct 2023 19:37:20 +0100 Subject: [PATCH 05/10] remove manifest.in --- .pre-commit-config.yaml | 4 ---- MANIFEST.in | 7 ------- pyproject.toml | 14 ++++++++------ 3 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 MANIFEST.in diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ebed4fa..2581a36d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -92,10 +92,6 @@ repos: # rev: v1.0.1 # hooks: # - id: rst-linter - - repo: https://github.com/mgedmin/check-manifest - rev: "0.49" - hooks: - - id: check-manifest - repo: https://github.com/abravalheri/validate-pyproject rev: v0.15 hooks: diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 3f1f5cd2..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include *.rst -recursive-include docs *.txt -recursive-exclude *.pyc *.pyo -include docs/LICENSE.GPL -include pygeoif/py.typed -exclude pygeoif/.* -recursive-include tests *.py diff --git a/pyproject.toml b/pyproject.toml index 0bd0ced5..0c8419d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,11 +115,13 @@ reportMissingTypeStubs = true "D103", ] -[tool.setuptools] -include-package-data = true -packages = [ - "pygeoif", -] - [tool.setuptools.dynamic.version] attr = "pygeoif.about.__version__" + +[tool.setuptools.packages.find] +exclude = [ + "docs*", +] +include = [ + "pygeoif*", +] From cd0da84ff15850c53990ee30058a97bb0c48ef75 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 24 Oct 2023 12:39:22 +0100 Subject: [PATCH 06/10] configure ruff strict --- pyproject.toml | 96 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c8419d3..129efb13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Scientific/Engineering :: GIS", @@ -44,8 +45,16 @@ requires-python = ">=3.7" text = "LGPL" [project.optional-dependencies] -testing = [ +dev = [ + "pre-commit", +] +lint = [ + "black", + "ruff", +] +tests = [ "pytest", + "pytest-cov", ] [project.readme] @@ -55,14 +64,6 @@ file = "README.rst" [project.urls] Homepage = "https://github.com/cleder/pygeoif/" -[tool.check-manifest] -ignore = [ - ".*", - "mutmut_config.py", - "test-requirements.txt", - "tox.ini", -] - [tool.flake8] max_line_length = 88 @@ -107,10 +108,81 @@ include = [ reportMissingImports = true reportMissingTypeStubs = true -[tool.ruff.per-file-ignores] -"setup.py" = [ - "E501", +[tool.ruff] +fix = true +ignore = [ + "ANN101", + "ANN102", + "D203", + "D212", + "FA100", +] +select = [ + "A", + "AIR", + "ANN", + "ARG", + "ASYNC", + "B", + "BLE", + "C4", + "C90", + "COM", + "CPY", + "D", + "DJ", + "DTZ", + "E", + "EM", + "ERA", + "EXE", + "F", + "FA", + "FBT", + "FIX", + "FLY", + "FURB", + "G", + "I", + "ICN", + "INP", + "INT", + "ISC", + "LOG", + "N", + "NPY", + "PD", + "PERF", + "PGH", + "PIE", + "PL", + "PT", + "PTH", + "PYI", + "Q", + "RET", + "RSE", + "RUF", + "S", + "SIM", + "SLF", + "SLOT", + "T10", + "T20", + "TCH", + "TD", + "TID", + "TRY", + "UP", + "W", + "YTT", ] +target-version = "py37" + +[tool.ruff.isort] +force-single-line = true + +[tool.ruff.per-file-ignores] "tests/*.py" = [ "D103", ] From a4fcfef02b26dcbcdc6a8673c05e41010460c3e7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:39:54 +0000 Subject: [PATCH 07/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pygeoif/feature.py | 14 ++++++++------ pygeoif/functions.py | 3 ++- pygeoif/geometry.py | 17 +++++++++-------- tests/test_factories.py | 2 +- tests/test_geometrycollection.py | 2 +- tests/test_point.py | 2 +- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/pygeoif/feature.py b/pygeoif/feature.py index 040d7766..505d3738 100644 --- a/pygeoif/feature.py +++ b/pygeoif/feature.py @@ -56,7 +56,8 @@ class Feature: """ Aggregates a geometry instance with associated user-defined properties. - Attributes + Attributes: + ---------- ~~~~~~~~~~~ geometry : object A geometry instance @@ -64,7 +65,8 @@ class Feature: A dictionary linking field keys with values associated with geometry instance - Example + Example: + ------- ~~~~~~~~ >>> p = Point(1.0, -1.0) @@ -139,17 +141,17 @@ def __geo_interface__(self) -> GeoFeatureInterface: class FeatureCollection: - """A heterogenous collection of Features. + """ + A heterogenous collection of Features. - Attributes + Attributes: ---------- features : sequence A sequence of feature instances - Example + Example: ------- - >>> from pygeoif import geometry >>> p = geometry.Point(1.0, -1.0) >>> props = {'Name': 'Sample Point', 'Other': 'Other Data'} diff --git a/pygeoif/functions.py b/pygeoif/functions.py index 1c803dc2..71fb125c 100644 --- a/pygeoif/functions.py +++ b/pygeoif/functions.py @@ -34,7 +34,8 @@ def signed_area(coords: LineType) -> float: - """Return the signed area enclosed by a ring. + """ + Return the signed area enclosed by a ring. Linear time algorithm: http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 indicates a counter-clockwise oriented ring. diff --git a/pygeoif/geometry.py b/pygeoif/geometry.py index 28d2f70d..fbbbac8d 100644 --- a/pygeoif/geometry.py +++ b/pygeoif/geometry.py @@ -191,14 +191,13 @@ class Point(_Geometry): A point has zero length and zero area. - Attributes + Attributes: ---------- x, y, z : float Coordinate values - Example + Example: ------- - >>> p = Point(1.0, -1.0) >>> print p POINT (1.0 -1.0) @@ -442,6 +441,7 @@ def __init__(self, coordinates: LineType) -> None: Initialize a LinearRing. Args: + ---- coordinates (Sequence): A sequence of (x, y [,z]) numeric coordinate pairs or triples """ @@ -974,7 +974,7 @@ class GeometryCollection(_MultiGeometry): """ A heterogenous collection of geometries. - Attributes + Attributes: ---------- geoms : sequence A sequence of geometry instances @@ -984,9 +984,8 @@ class GeometryCollection(_MultiGeometry): class isn't generally supported by ordinary GIS sw (viewers and so on). So it's very rarely used in the real GIS professional world. - Example + Example: ------- - Initialize Geometries and construct a GeometryCollection >>> from pygeoif import geometry @@ -1001,12 +1000,13 @@ class isn't generally supported by ordinary GIS sw (viewers and so on). So """ def __init__( - self, geometries: Iterable[Union[Geometry, "GeometryCollection"]] + self, geometries: Iterable[Union[Geometry, "GeometryCollection"]], ) -> None: """ Initialize the MultiGeometry with Geometries. Args: + ---- geometries (Iterable[Geometry] """ self._geoms = tuple(geom for geom in geometries if geom) @@ -1045,7 +1045,8 @@ def __len__(self) -> int: """ Length of the collection. - Returns: + Returns + ------- int: Number of geometries in the collection. """ return len(self._geoms) diff --git a/tests/test_factories.py b/tests/test_factories.py index 5c714b95..f427cffb 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -378,7 +378,7 @@ def test_multilinestring(self) -> None: f = geometry.MultiLineString([[[0.0, 0.0], [1.0, 2.0]]]) s = factories.shape(f) assert f.__geo_interface__ == s.__geo_interface__ - assert (0, 0, 1, 2) == f.bounds + assert f.bounds == (0, 0, 1, 2) def test_multipolygon(self) -> None: f = geometry.MultiPolygon( diff --git a/tests/test_geometrycollection.py b/tests/test_geometrycollection.py index 3064492d..79696c9b 100644 --- a/tests/test_geometrycollection.py +++ b/tests/test_geometrycollection.py @@ -375,7 +375,7 @@ def test_nested_geometry_collection_neq() -> None: multipoint = geometry.MultiPoint([(0, 0), (1, 1), (1, 2), (2, 2)]) gc1 = geometry.GeometryCollection([geometry.Point(0, 0), multipoint]) gc1_1 = geometry.GeometryCollection( - [geometry.Point(0, 0), multipoint, geometry.Point(0, 0)] + [geometry.Point(0, 0), multipoint, geometry.Point(0, 0)], ) line = geometry.LineString([(0, 0), (3, 1)]) gc2 = geometry.GeometryCollection([gc1, line]) diff --git a/tests/test_point.py b/tests/test_point.py index 96e9a709..5eca7478 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -61,7 +61,7 @@ def test_xy_raises_error_accessing_z() -> None: point = geometry.Point(1, 0) with pytest.raises( - DimensionError, match=r"^The Point\(1, 0\) geometry does not have z values$" + DimensionError, match=r"^The Point\(1, 0\) geometry does not have z values$", ): point.z From 446f5094b8941289ff8b396ddfcf4177e6dba09a Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:40:10 +0000 Subject: [PATCH 08/10] style: format code with Black and isort This commit fixes the style issues introduced in a4fcfef according to the output from Black and isort. Details: https://github.com/cleder/pygeoif/pull/172 --- pygeoif/geometry.py | 3 ++- tests/test_point.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pygeoif/geometry.py b/pygeoif/geometry.py index fbbbac8d..190eb6b1 100644 --- a/pygeoif/geometry.py +++ b/pygeoif/geometry.py @@ -1000,7 +1000,8 @@ class isn't generally supported by ordinary GIS sw (viewers and so on). So """ def __init__( - self, geometries: Iterable[Union[Geometry, "GeometryCollection"]], + self, + geometries: Iterable[Union[Geometry, "GeometryCollection"]], ) -> None: """ Initialize the MultiGeometry with Geometries. diff --git a/tests/test_point.py b/tests/test_point.py index 5eca7478..e456753f 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -61,7 +61,8 @@ def test_xy_raises_error_accessing_z() -> None: point = geometry.Point(1, 0) with pytest.raises( - DimensionError, match=r"^The Point\(1, 0\) geometry does not have z values$", + DimensionError, + match=r"^The Point\(1, 0\) geometry does not have z values$", ): point.z From 83ee20bb997ecb886ed8e6aee0f30a6341cd2e29 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 24 Oct 2023 18:53:25 +0100 Subject: [PATCH 09/10] fix ruff errors --- .pre-commit-config.yaml | 1 + docs/LICENSE.txt | 2 +- pygeoif/exceptions.py | 2 +- pygeoif/factories.py | 22 ++++++---- pygeoif/feature.py | 11 ++--- pygeoif/functions.py | 6 +-- pygeoif/geometry.py | 93 ++++++++++++++++++++++++++--------------- pygeoif/types.py | 4 +- pyproject.toml | 25 +++++++++++ tests/test_factories.py | 16 +++---- tests/test_point.py | 2 +- tests/test_polygon.py | 2 +- tox.ini | 2 +- 13 files changed, 121 insertions(+), 67 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2581a36d..c510c05b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,6 +48,7 @@ repos: rev: 'v0.1.1' hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 hooks: diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt index 8fd99c4f..d8d8473b 100644 --- a/docs/LICENSE.txt +++ b/docs/LICENSE.txt @@ -2,7 +2,7 @@ Pygeoif is a basic implementation of the __geo_interface__ in pure Python - Copyright (C) 2012 - 2022 Christian Ledermann + Copyright (C) 2012 - 2023 Christian Ledermann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/pygeoif/exceptions.py b/pygeoif/exceptions.py index 0974b098..e5c3219b 100644 --- a/pygeoif/exceptions.py +++ b/pygeoif/exceptions.py @@ -26,7 +26,7 @@ class WKTParserError(AttributeError): class InvalidGeometryError(ValueError): - """This geometry is not valid.""" + """Geometry is not valid.""" __all__ = [ diff --git a/pygeoif/factories.py b/pygeoif/factories.py index e5c2fa35..f6a0d58f 100644 --- a/pygeoif/factories.py +++ b/pygeoif/factories.py @@ -58,7 +58,7 @@ mpre: Pattern[str] = re.compile(r"\(\((.+?)\)\)") -def orient(polygon: Polygon, ccw: bool = True) -> Polygon: +def orient(polygon: Polygon, ccw: bool = True) -> Polygon: # noqa: FBT001, FBT002 """ Return a polygon with exteriors and interiors in the right orientation. @@ -69,12 +69,12 @@ def orient(polygon: Polygon, ccw: bool = True) -> Polygon: s = 1.0 if ccw else -1.0 rings = [] ring = polygon.exterior - if signed_area(ring.coords) / s >= 0.0: + if signed_area(ring.coords) / s >= 0: rings.append(ring.coords) else: rings.append(list(ring.coords)[::-1]) for ring in polygon.interiors: - if signed_area(ring.coords) / s <= 0.0: + if signed_area(ring.coords) / s <= 0: rings.append(ring.coords) else: rings.append(list(ring.coords)[::-1]) @@ -86,7 +86,7 @@ def box( miny: float, maxx: float, maxy: float, - ccw: bool = True, + ccw: bool = True, # noqa: FBT001, FBT002 ) -> Polygon: """Return a rectangular polygon with configurable normal vector.""" coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)] @@ -143,7 +143,10 @@ def shape( } geometry = context if isinstance(context, dict) else mapping(context) if not geometry: - raise TypeError("Object does not implement __geo_interface__") + msg = ( # type: ignore [unreachable] + "Object does not implement __geo_interface__" + ) + raise TypeError(msg) constructor = type_map.get(geometry["type"]) if constructor: @@ -155,7 +158,8 @@ def shape( shape(fi) for fi in geometry["geometries"] # type: ignore [typeddict-item] ] return GeometryCollection(geometries) - raise NotImplementedError(f"[{geometry['type']} is not implemented") + msg = f"[{geometry['type']} is not implemented" + raise NotImplementedError(msg) def num(number: str) -> float: @@ -296,12 +300,14 @@ def from_wkt(geo_str: str) -> Optional[Union[Geometry, GeometryCollection]]: outerstr = outer.search(wkt) coordinates = outerstr.group(1) # type: ignore [union-attr] except AttributeError as exc: - raise WKTParserError(f"Cannot parse {wkt}") from exc + msg = f"Cannot parse {wkt}" + raise WKTParserError(msg) from exc constructor = type_map[geometry_type] try: return constructor(coordinates) # type: ignore [return-value] except TypeError as exc: - raise WKTParserError(f"Cannot parse {wkt}") from exc + msg = f"Cannot parse {wkt}" + raise WKTParserError(msg) from exc def mapping( diff --git a/pygeoif/feature.py b/pygeoif/feature.py index 505d3738..9ca7d998 100644 --- a/pygeoif/feature.py +++ b/pygeoif/feature.py @@ -20,7 +20,7 @@ from typing import Any from typing import Dict from typing import Generator -from typing import Iterable +from typing import Iterator from typing import Optional from typing import Sequence from typing import Union @@ -58,7 +58,6 @@ class Feature: Attributes: ---------- - ~~~~~~~~~~~ geometry : object A geometry instance properties : dict @@ -67,8 +66,6 @@ class Feature: Example: ------- - ~~~~~~~~ - >>> p = Point(1.0, -1.0) >>> props = {'Name': 'Sample Point', 'Other': 'Other Data'} >>> a = Feature(p, props) @@ -111,7 +108,7 @@ def __repr__(self) -> str: ) @property - def id(self) -> Optional[Union[str, int]]: + def id(self) -> Optional[Union[str, int]]: # noqa: A003 """Return the id of the feature.""" return self._feature_id @@ -191,12 +188,12 @@ def __len__(self) -> int: """Return the umber of features in this collection.""" return len(self._features) - def __iter__(self) -> Iterable[Feature]: + def __iter__(self) -> Iterator[Feature]: """Iterate over the features of the collection.""" return iter(self._features) def __repr__(self) -> str: - """Retrun the representation.""" + """Return the representation.""" return f"{self.__class__.__name__}({self._features!r})" @property diff --git a/pygeoif/functions.py b/pygeoif/functions.py index 71fb125c..73c42b38 100644 --- a/pygeoif/functions.py +++ b/pygeoif/functions.py @@ -89,7 +89,7 @@ def _cross(o: Point2D, a: Point2D, b: Point2D) -> float: def _build_hull(points: Iterable[Point2D]) -> List[Point2D]: hull: List[Point2D] = [] for p in points: - while len(hull) >= 2 and _cross(hull[-2], hull[-1], p) <= 0: + while len(hull) >= 2 and _cross(hull[-2], hull[-1], p) <= 0: # noqa: PLR2004 hull.pop() hull.append(p) return hull @@ -120,13 +120,13 @@ def convex_hull(points: Iterable[Point2D]) -> LineType: # Boring case: no points, a single point or a line between two points, # possibly repeated multiple times. - if len(points) <= 2: + if len(points) <= 2: # noqa: PLR2004 return points lower = _build_hull(points) upper = _build_hull(reversed(points)) - if len(lower) == len(upper) == 2 and set(lower) == set(upper): + if len(lower) == len(upper) == 2 and set(lower) == set(upper): # noqa: PLR2004 # all points are in a straight line return lower # Concatenation of the lower and upper hulls gives the convex hull. diff --git a/pygeoif/geometry.py b/pygeoif/geometry.py index 190eb6b1..af9ce80f 100644 --- a/pygeoif/geometry.py +++ b/pygeoif/geometry.py @@ -68,7 +68,9 @@ def __eq__(self, other: object) -> bool: ), compare_coordinates( self.__geo_interface__["coordinates"], - other.__geo_interface__.get("coordinates"), # type: ignore + other.__geo_interface__.get( # type: ignore [attr-defined] + "coordinates", + ), ), ), ) @@ -110,7 +112,7 @@ def convex_hull(self) -> Optional[Union["Point", "LineString", "Polygon"]]: return None if len(hull) == 1: return Point(*hull[0]) - return LineString(hull) if len(hull) == 2 else Polygon(hull) + return LineString(hull) if len(hull) == 2 else Polygon(hull) # noqa: PLR2004 @property def geom_type(self) -> str: @@ -124,12 +126,14 @@ def has_z(self) -> Optional[bool]: Return None if the geometry is empty. """ - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) @property def is_empty(self) -> bool: """Return if this geometry is empty.""" - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) @property def wkt(self) -> str: @@ -141,7 +145,8 @@ def wkt(self) -> str: @property def __geo_interface__(self) -> GeoInterface: if self.is_empty: - raise AttributeError("Empty Geometry") + msg = "Empty Geometry" + raise AttributeError(msg) return { "type": self.geom_type, "bbox": cast(Bounds, self.bounds), @@ -150,7 +155,8 @@ def __geo_interface__(self) -> GeoInterface: @property def _wkt_coords(self) -> str: - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) @property def _wkt_inset(self) -> str: @@ -165,24 +171,28 @@ def _wkt_type(self) -> str: @classmethod def _check_dict(cls, geo_interface: GeoInterface) -> None: if geo_interface["type"] != cls.__name__: + msg = f"You cannot assign {geo_interface['type']} to {cls.__name__}" raise ValueError( - f"You cannot assign {geo_interface['type']} to {cls.__name__}", + msg, ) @classmethod def _from_dict(cls, geo_interface: GeoInterface) -> "_Geometry": cls._check_dict(geo_interface) - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) @classmethod def _from_interface(cls, obj: GeoType) -> "_Geometry": return cls._from_dict(obj.__geo_interface__) def _prepare_hull(self) -> Iterable[Point2D]: - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) def _get_bounds(self) -> Bounds: - raise NotImplementedError("Must be implemented by subclass") + msg = "Must be implemented by subclass" + raise NotImplementedError(msg) class Point(_Geometry): @@ -236,7 +246,7 @@ def is_empty(self) -> bool: A Point is considered empty when it has less than 2 coordinates. """ - return len(self._coordinates) < 2 + return len(self._coordinates) < 2 # noqa: PLR2004 @property def x(self) -> float: @@ -253,7 +263,8 @@ def z(self) -> Optional[float]: """Return z coordinate.""" if self.has_z: return self._coordinates[2] # type: ignore [misc] - raise DimensionError(f"The {self!r} geometry does not have z values") + msg = f"The {self!r} geometry does not have z values" + raise DimensionError(msg) @property def coords(self) -> Tuple[PointType]: @@ -263,7 +274,7 @@ def coords(self) -> Tuple[PointType]: @property def has_z(self) -> bool: """Return True if the geometry's coordinate sequence(s) have z values.""" - return len(self._coordinates) == 3 + return len(self._coordinates) == 3 # noqa: PLR2004 @property def _wkt_coords(self) -> str: @@ -272,7 +283,7 @@ def _wkt_coords(self) -> str: @property def _wkt_inset(self) -> str: """Return Z for 3 dimensional geometry or an empty string for 2 dimensions.""" - return " Z " if len(self._coordinates) == 3 else " " + return " Z " if len(self._coordinates) == 3 else " " # noqa: PLR2004 @property def __geo_interface__(self) -> GeoInterface: @@ -349,7 +360,7 @@ def is_empty(self) -> bool: A Linestring is considered empty when it has less than 2 points. """ - return len(self._geoms) < 2 + return len(self._geoms) < 2 # noqa: PLR2004 @property def has_z(self) -> Optional[bool]: @@ -369,11 +380,11 @@ def maybe_valid(self) -> bool: @property def _wkt_inset(self) -> str: - return self.geoms[0]._wkt_inset + return self.geoms[0]._wkt_inset # noqa: SLF001 @property def _wkt_coords(self) -> str: - return ", ".join(point._wkt_coords for point in self.geoms) + return ", ".join(point._wkt_coords for point in self.geoms) # noqa: SLF001 @property def __geo_interface__(self) -> GeoInterface: @@ -403,8 +414,11 @@ def _set_geoms(coordinates: LineType) -> Tuple[Point, ...]: last_len = None for coord in dedupe(coordinates): if len(coord) != last_len and last_len is not None: + msg = ( # type: ignore [unreachable] + "All coordinates must have the same dimension" + ) raise DimensionError( - "All coordinates must have the same dimension", + msg, ) last_len = len(coord) point = Point(*coord) @@ -447,13 +461,14 @@ def __init__(self, coordinates: LineType) -> None: """ super().__init__(coordinates) if not self.is_empty and self._geoms[0].coords != self._geoms[-1].coords: - self._geoms = self._geoms + (self._geoms[0],) + self._geoms = (*self._geoms, self._geoms[0]) @property def centroid(self) -> Optional[Point]: """Return the centroid of the ring.""" if self.has_z: - raise DimensionError("Centeroid is only implemented for 2D coordinates") + msg = "Centeroid is only implemented for 2D coordinates" + raise DimensionError(msg) try: cent, area = centroid(self.coords) except ZeroDivisionError: @@ -478,7 +493,8 @@ def maybe_valid(self) -> bool: Even if this test passes the geometry may still be invalid. """ if self.has_z: - raise DimensionError("Validation is only implemented for 2D coordinates") + msg = "Validation is only implemented for 2D coordinates" + raise DimensionError(msg) min_x, min_y, max_x, max_y = self.bounds # type: ignore [misc] if min_x == max_x or min_y == max_y: return False @@ -592,19 +608,21 @@ def maybe_valid(self) -> bool: @property def _wkt_coords(self) -> str: - ec = self.exterior._wkt_coords - ic = "".join(f",({interior._wkt_coords})" for interior in self.interiors) + ec = self.exterior._wkt_coords # noqa: SLF001 + ic = "".join( + f",({interior._wkt_coords})" for interior in self.interiors # noqa: SLF001 + ) return f"({ec}){ic}" @property def _wkt_inset(self) -> str: - return self.exterior._wkt_inset + return self.exterior._wkt_inset # noqa: SLF001 @property def __geo_interface__(self) -> GeoInterface: """Return the geo interface.""" geo_interface = super().__geo_interface__ - coords = (self.exterior.coords,) + tuple(hole.coords for hole in self.interiors) + coords = (self.exterior.coords, *tuple(hole.coords for hole in self.interiors)) geo_interface["coordinates"] = coords return geo_interface @@ -643,10 +661,10 @@ def _check_interior_bounds(self) -> bool: return True def _get_bounds(self) -> Bounds: - return self.exterior._get_bounds() + return self.exterior._get_bounds() # noqa: SLF001 def _prepare_hull(self) -> Iterable[Point2D]: - return self.exterior._prepare_hull() + return self.exterior._prepare_hull() # noqa: SLF001 class _MultiGeometry(_Geometry): @@ -663,8 +681,9 @@ def coords(self) -> NoReturn: Multi-part geometries do not provide a coordinate sequence. """ + msg = "Multi-part geometries do not provide a coordinate sequence" raise NotImplementedError( - "Multi-part geometries do not provide a coordinate sequence", + msg, ) @property @@ -754,7 +773,7 @@ def geoms(self) -> Iterator[Point]: @property def _wkt_coords(self) -> str: - return ", ".join(point._wkt_coords for point in self.geoms) + return ", ".join(point._wkt_coords for point in self.geoms) # noqa: SLF001 @property def __geo_interface__(self) -> GeoInterface: @@ -826,7 +845,9 @@ def geoms(self) -> Iterator[LineString]: @property def _wkt_coords(self) -> str: - return ",".join(f"({linestring._wkt_coords})" for linestring in self.geoms) + return ",".join( + f"({linestring._wkt_coords})" for linestring in self.geoms # noqa: SLF001 + ) @property def __geo_interface__(self) -> GeoInterface: @@ -905,7 +926,9 @@ def __init__(self, polygons: Sequence[PolygonType], unique: bool = False) -> Non self._geoms = tuple( Polygon( polygon[0], - polygon[1] if len(polygon) == 2 else None, # type: ignore [misc] + polygon[1] # type: ignore [misc] + if len(polygon) == 2 # noqa: PLR2004 + else None, ) for polygon in polygons ) @@ -925,14 +948,14 @@ def geoms(self) -> Iterator[Polygon]: @property def _wkt_coords(self) -> str: - return ",".join(f"({poly._wkt_coords})" for poly in self.geoms) + return ",".join(f"({poly._wkt_coords})" for poly in self.geoms) # noqa: SLF001 @property def __geo_interface__(self) -> GeoInterface: """Return the geo interface.""" geo_interface = super().__geo_interface__ coords = tuple( - (geom.exterior.coords,) + tuple(hole.coords for hole in geom.interiors) + (geom.exterior.coords, *tuple(hole.coords for hole in geom.interiors)) for geom in self.geoms ) geo_interface["coordinates"] = coords @@ -1069,7 +1092,9 @@ def __geo_interface__(self) -> GeoCollectionInterface: # type: ignore [override } def _prepare_hull(self) -> Iterable[Point2D]: - return chain.from_iterable(geom._prepare_hull() for geom in self.geoms) + return chain.from_iterable( + geom._prepare_hull() for geom in self.geoms # noqa: SLF001 + ) __all__ = [ diff --git a/pygeoif/types.py b/pygeoif/types.py index fd86d0ad..861781e8 100644 --- a/pygeoif/types.py +++ b/pygeoif/types.py @@ -80,7 +80,7 @@ class GeoFeatureInterface(GeoFeatureInterfaceBase, total=False): bbox: Bounds properties: Dict[str, Any] - id: Union[str, int] + id: Union[str, int] # noqa: A003 class GeoFeatureCollectionInterfaceBase(TypedDict): @@ -94,7 +94,7 @@ class GeoFeatureCollectionInterface(GeoFeatureCollectionInterfaceBase, total=Fal """Bbox and id are optional keys for the GeoFeatureCollectionInterface.""" bbox: Bounds - id: Union[str, int] + id: Union[str, int] # noqa: A003 class GeoType(Protocol): diff --git a/pyproject.toml b/pyproject.toml index 129efb13..4ed30516 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,9 @@ disallow_incomplete_defs = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_untyped_defs = true +enable_error_code = [ + "ignore-without-code", +] ignore_errors = false ignore_missing_imports = false implicit_reexport = false @@ -183,8 +186,30 @@ target-version = "py37" force-single-line = true [tool.ruff.per-file-ignores] +"pygeoif/factories.py" = [ + "SLF001", +] +"pygeoif/geometry.py" = [ + "D417", + "FBT001", + "FBT002", +] "tests/*.py" = [ + "ANN001", + "ANN201", + "D101", + "D102", "D103", + "FBT001", + "FBT003", + "PGH001", + "PGH003", + "PLR2004", + "RUF012", + "S101", + "S307", + "S311", + "SLF001", ] [tool.setuptools.dynamic.version] diff --git a/tests/test_factories.py b/tests/test_factories.py index f427cffb..1c29aaa7 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -176,7 +176,7 @@ def test_polygon_1(self) -> None: assert p.exterior.coords[0] == p.exterior.coords[-1] assert p.exterior.coords[0] == (1.0, 1.0) assert len(list(p.interiors)) == 1 - assert list(p.interiors)[0].coords == ( + assert next(iter(p.interiors)).coords == ( (2.0, 2.0), (3.0, 2.0), (3.0, 3.0), @@ -207,16 +207,16 @@ def test_polygon_3(self) -> None: def test_multipoint(self) -> None: p = factories.from_wkt("MULTIPOINT(3.5 5.6,4.8 10.5)") assert isinstance(p, geometry.MultiPoint) - assert list(p.geoms)[0].x == 3.5 + assert next(iter(p.geoms)).x == 3.5 assert list(p.geoms)[1].y == 10.5 assert p.wkt == "MULTIPOINT(3.5 5.6, 4.8 10.5)" p = factories.from_wkt("MULTIPOINT ((10 40), (40 30), (20 20), (30 10))") assert isinstance(p, geometry.MultiPoint) - assert list(p.geoms)[0].x == 10.0 + assert next(iter(p.geoms)).x == 10.0 assert list(p.geoms)[3].y == 10.0 p = factories.from_wkt("MULTIPOINT (10 40, 40 30, 20 20, 30 10)") assert isinstance(p, geometry.MultiPoint) - assert list(p.geoms)[0].x == 10.0 + assert next(iter(p.geoms)).x == 10.0 assert list(p.geoms)[3].y == 10.0 def test_multilinestring(self) -> None: @@ -225,7 +225,7 @@ def test_multilinestring(self) -> None: ) assert isinstance(p, geometry.MultiLineString) - assert list(p.geoms)[0].coords == (((3, 4), (10, 50), (20, 25))) + assert next(iter(p.geoms)).coords == (((3, 4), (10, 50), (20, 25))) assert list(p.geoms)[1].coords == (((-5, -8), (-10, -8), (-15, -4))) assert ( p.wkt == "MULTILINESTRING((3 4, 10 50, " @@ -254,13 +254,13 @@ def test_multipolygon(self) -> None: assert isinstance(p, geometry.MultiPolygon) # two polygons: the first one has an interior ring assert len(list(p.geoms)) == 2 - assert list(p.geoms)[0].exterior.coords == ( + assert next(iter(p.geoms)).exterior.coords == ( (0.0, 0.0), (10.0, 20.0), (30.0, 40.0), (0.0, 0.0), ) - assert list(list(p.geoms)[0].interiors)[0].coords == ( + assert next(iter(next(iter(p.geoms)).interiors)).coords == ( (1.0, 1.0), (2.0, 2.0), (3.0, 3.0), @@ -320,7 +320,7 @@ def test_geometrycollection(self) -> None: assert isinstance(gc, geometry.GeometryCollection) assert len(list(gc.geoms)) == 2 - assert isinstance(list(gc.geoms)[0], geometry.Point) + assert isinstance(next(iter(gc.geoms)), geometry.Point) assert isinstance(list(gc.geoms)[1], geometry.LineString) assert gc.wkt == "GEOMETRYCOLLECTION(POINT (4 6), LINESTRING (4 6, 7 10))" diff --git a/tests/test_point.py b/tests/test_point.py index e456753f..33a3fb93 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -64,7 +64,7 @@ def test_xy_raises_error_accessing_z() -> None: DimensionError, match=r"^The Point\(1, 0\) geometry does not have z values$", ): - point.z + assert point.z def test_xyz() -> None: diff --git a/tests/test_polygon.py b/tests/test_polygon.py index b0dedc20..4ef462d3 100644 --- a/tests/test_polygon.py +++ b/tests/test_polygon.py @@ -118,7 +118,7 @@ def test_interiors() -> None: i = [(1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)] polygon = geometry.Polygon(e, [i]) - assert list(polygon.interiors)[0].coords == ( + assert next(iter(polygon.interiors)).coords == ( (1, 0), (0.5, 0.5), (1, 1), diff --git a/tox.ini b/tox.ini index 74ef1378..2079b885 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ min_python_version = 3.7 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist max_line_length = 89 ignore= - W503,I900,IF100 + W503,I900,IF100,NQA102, RST212 per-file-ignores = tests/*:S101,D103,S307,DALL000,S311,ECE001,FKA100 tests/test_geometrycollection.py: ECE001,S101,D103,S307,DALL000 From c60c834254aef8c154add71ad05f05180fc46327 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 24 Oct 2023 19:57:44 +0100 Subject: [PATCH 10/10] avoid Any, use a protocol --- mutmut_config.py | 9 +++++++-- tox.ini | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mutmut_config.py b/mutmut_config.py index 8f7e1749..d99f99de 100644 --- a/mutmut_config.py +++ b/mutmut_config.py @@ -1,5 +1,5 @@ """Mutmut configuration.""" -from typing import Any +from typing import Protocol files_to_mutate = [ "pygeoif/geometry.py", @@ -9,7 +9,12 @@ ] -def pre_mutation(context: Any) -> None: +class Context(Protocol): + filename: str + skip: bool + + +def pre_mutation(context: Context) -> None: """Only include the files specified above.""" if context.filename not in files_to_mutate: context.skip = True diff --git a/tox.ini b/tox.ini index 2079b885..8d560795 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ per-file-ignores = tests/test_feature.py: ECE001,S10,D10,S307,DALL000,PT009,T003,P103 feature.py: A003 types.py: A003 + mutmut_config.py: TYP001 kwargs_ignore_function_pattern_extend = '^cast$' literal_inline_quotes = double literal_multiline_quotes = double