[Git][debian-gis-team/pycoast][master] 13 commits: Update the list of excluded files in d/copyright

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sun Aug 29 18:56:23 BST 2021



Antonio Valentino pushed to branch master at Debian GIS Project / pycoast


Commits:
35b39da7 by Antonio Valentino at 2021-08-29T15:50:55+00:00
Update the list of excluded files in d/copyright

- - - - -
0eddb7c4 by Antonio Valentino at 2021-08-29T15:52:04+00:00
New upstream version 1.5.0+dfsg
- - - - -
f817e667 by Antonio Valentino at 2021-08-29T15:52:39+00:00
Update upstream source from tag 'upstream/1.5.0+dfsg'

Update to upstream version '1.5.0+dfsg'
with Debian dir 22fb15378d3f8bbff1ed35df7c5b437f12533554
- - - - -
5a70caeb by Antonio Valentino at 2021-08-29T15:54:22+00:00
New upstream release

- - - - -
6eda0270 by Antonio Valentino at 2021-08-29T15:55:28+00:00
Refresh all patches

- - - - -
ec9fbcb8 by Antonio Valentino at 2021-08-29T15:59:07+00:00
Update copyright dates

- - - - -
190c80a0 by Antonio Valentino at 2021-08-29T16:06:00+00:00
Update dependencies in d/control

- - - - -
8bd50fa5 by Antonio Valentino at 2021-08-29T16:51:25+00:00
Fix pytest args in d/ruels

- - - - -
fc25215e by Antonio Valentino at 2021-08-29T16:51:53+00:00
Add fonts-dejavu-core to build dependencies (for testing)

- - - - -
e4d11a32 by Antonio Valentino at 2021-08-29T17:11:16+00:00
New 0002-Fix-font-path.patch

- - - - -
70005567 by Antonio Valentino at 2021-08-29T17:15:59+00:00
Typo

- - - - -
0e07e9df by Antonio Valentino at 2021-08-29T17:17:19+00:00
No need to forward 0001-Skip-tests-that-use-shapes.patch

- - - - -
0617d6c1 by Antonio Valentino at 2021-08-29T17:23:35+00:00
Set distribution to unstable

- - - - -


25 changed files:

- − .bumpversion.cfg
- .github/PULL_REQUEST_TEMPLATE.md
- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- − .travis.yml
- CHANGELOG.md
- README.rst
- RELEASING.md
- + continuous_integration/environment.yaml
- debian/changelog
- debian/control
- debian/copyright
- debian/patches/0001-Skip-tests-that-use-shapes.patch
- + debian/patches/0002-Fix-font-path.patch
- debian/patches/series
- debian/rules
- pycoast/__init__.py
- pycoast/cw_agg.py
- pycoast/cw_base.py
- pycoast/cw_pil.py
- pycoast/tests/__init__.py
- pycoast/tests/test_pycoast.py
- pycoast/version.py
- setup.cfg
- setup.py


Changes:

=====================================
.bumpversion.cfg deleted
=====================================
@@ -1,7 +0,0 @@
-[bumpversion]
-current_version = 1.1.0
-commit = True
-tag = True
-
-[bumpversion:file:pycoast/version.py]
-


=====================================
.github/PULL_REQUEST_TEMPLATE.md
=====================================
@@ -1,9 +1,7 @@
-<!-- Please make the PR against the `master` branch. -->
-
 <!-- Describe what your PR does, and why -->
 
  - [ ] Closes #xxxx <!-- remove if there is no corresponding issue, which should only be the case for minor changes -->
  - [ ] Tests added <!-- for all bug fixes or enhancements -->
  - [ ] Tests passed <!-- for all non-documentation changes) -->
- - [ ] Passes ``git diff origin/master **/*py | flake8 --diff`` <!-- remove if you did not edit any Python files -->
+ - [ ] Passes ``git diff origin/main **/*py | flake8 --diff`` <!-- remove if you did not edit any Python files -->
  - [ ] Fully documented <!-- remove if this change should not be visible to users, e.g., if it is an internal clean-up, or if this is part of a larger project that will be documented later -->


=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,135 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+  lint:
+    name: lint and style checks
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+      - name: Set up Python
+        uses: actions/setup-python at v2
+        with:
+          python-version: 3.9
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install flake8 flake8-docstrings flake8-debugger flake8-bugbear pytest
+      - name: Install Pycoast
+        run: |
+          pip install -e .
+      - name: Run linting
+        run: |
+          flake8 pycoast/
+
+  website:
+    name: build website
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+        with:
+          fetch-depth: 0
+
+#      - name: Set up Python 3.9
+#        uses: actions/setup-python at v2
+#        with:
+#          python-version: 3.9
+      - name: Setup Conda Environment
+        uses: conda-incubator/setup-miniconda at v2
+        with:
+          miniforge-variant: Mambaforge
+          miniforge-version: latest
+          python-version: 3.9
+          use-mamba: true
+          environment-file: continuous_integration/environment.yaml
+          activate-environment: test-environment
+
+      - name: Install Pycoast
+        shell: bash -l {0}
+        run: |
+          python -m pip install --no-deps -e .
+
+      - name: Run Sphinx Build
+        shell: bash -l {0}
+        run: |
+          cd docs; \
+          make html SPHINXOPTS="-W"
+
+  test:
+    runs-on: ${{ matrix.os }}
+    continue-on-error: ${{ matrix.experimental }}
+    needs: [lint]
+    strategy:
+      fail-fast: true
+      matrix:
+        os: ["windows-latest", "ubuntu-latest", "macos-latest"]
+        python-version: ["3.7", "3.9"]
+        experimental: [false]
+        include:
+          - python-version: "3.9"
+            os: "ubuntu-latest"
+            experimental: true
+
+    env:
+      PYTHON_VERSION: ${{ matrix.python-version }}
+      OS: ${{ matrix.os }}
+      UNSTABLE: ${{ matrix.experimental }}
+      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+
+#      - name: Set up Python ${{ matrix.python-version }}
+#        uses: actions/setup-python at v2
+#        with:
+#          python-version: ${{ matrix.python-version }}
+
+      - name: Setup Conda Environment
+        uses: conda-incubator/setup-miniconda at v2
+        with:
+          miniforge-variant: Mambaforge
+          miniforge-version: latest
+          python-version: ${{ matrix.python-version }}
+          use-mamba: true
+          environment-file: continuous_integration/environment.yaml
+
+      - name: Install unstable dependencies
+        shell: bash -l {0}
+        if: matrix.experimental == true
+        run: |
+          python -m pip install \
+          --index-url https://pypi.anaconda.org/scipy-wheels-nightly/simple/ \
+          --trusted-host pypi.anaconda.org \
+          --no-deps --pre --upgrade \
+          numpy;
+
+      - name: Install pycoast
+        shell: bash -l {0}
+        run: |
+          python -m pip install --no-deps -e .
+
+      - name: Run unit tests
+        shell: bash -l {0}
+        run: |
+          pytest --cov=pycoast pycoast/tests
+
+      - name: Coveralls Parallel
+        uses: AndreMiras/coveralls-python-action at develop
+        with:
+          flag-name: run-${{ matrix.test_number }}
+          parallel: true
+        if: runner.os == 'Linux'
+
+  coveralls:
+    needs: [test]
+    runs-on: ubuntu-latest
+    steps:
+      - name: Coveralls Finished
+        uses: AndreMiras/coveralls-python-action at develop
+        with:
+          parallel-finished: true
+


=====================================
.github/workflows/deploy-sdist.yaml
=====================================
@@ -0,0 +1,25 @@
+name: Deploy sdist
+
+on:
+  release:
+    types:
+      - published
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+
+      - name: Create sdist
+        shell: bash -l {0}
+        run: python setup.py sdist
+
+      - name: Publish package to PyPI
+        if: github.event.action == 'published'
+        uses: pypa/gh-action-pypi-publish at v1.4.1
+        with:
+          user: __token__
+          password: ${{ secrets.pypi_password }}
\ No newline at end of file


=====================================
.travis.yml deleted
=====================================
@@ -1,55 +0,0 @@
-language: generic
-env:
-  global:
-  # Set defaults to avoid repeating in most cases
-  - PYTHON_VERSION=$TRAVIS_PYTHON_VERSION
-  - MAIN_CMD='python setup.py'
-  - CONDA_DEPENDENCIES='sphinx pillow pyproj coveralls coverage mock aggdraw six pyshp pyresample'
-  - PIP_DEPENDENCIES=''
-  - SETUP_XVFB=False
-  - EVENT_TYPE='push pull_request'
-  - SETUP_CMD='test'
-  - CONDA_CHANNELS='conda-forge'
-  - CONDA_CHANNEL_PRIORITY='True'
-matrix:
-  include:
-  - env:
-    - PYTHON_VERSION=2.7
-    - NUMPY_VERSION=1.16
-    os: linux
-  - env:
-    - PYTHON_VERSION=2.7
-    - NUMPY_VERSION=1.16
-    os: osx
-  - env: PYTHON_VERSION=3.6
-    os: linux
-  - env: PYTHON_VERSION=3.6
-    os: osx
-  - env: PYTHON_VERSION=3.6
-    os: windows
-    language: bash
-  - env: PYTHON_VERSION=3.7
-    os: linux
-  - env: PYTHON_VERSION=3.7
-    os: osx
-  - env: PYTHON_VERSION=3.7
-    os: windows
-    language: bash
-install:
-  - git clone --depth 1 git://github.com/astropy/ci-helpers.git
-  - source ci-helpers/travis/setup_conda.sh
-script: coverage run --source=pycoast setup.py test
-after_success:
-- if [[ $PYTHON_VERSION == 3.6 ]]; then coveralls; fi
-deploy:
-- provider: pypi
-  user: dhoese
-  password:
-    secure: M3wCEnirvwNDncuoLj8DpPFwNypLXcDGlAinNHfwZLIEzDTNrC5XhGZ3xbfmAeBGOIgJ2I6xDWJ6hYevv3HxBdgln9nJfO7tsnqisK1lxyF4/dntCJm5CLkJqM8To1o9zeht0dyZJ66fUAHfxwjXGBDoaPyEnsygWXH6vpEZ8K0=
-  distributions: sdist
-  skip_existing: true
-  on:
-    tags: true
-    repo: pytroll/pycoast
-notifications:
-  slack: pytroll:96mNSYSI1dBjGyzVXkBT6qFt


=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,22 @@
+## Version 1.5.0 (2021/08/18)
+
+### Issues Closed
+
+* [Issue 48](https://github.com/pytroll/pycoast/issues/48) - Raster images without map projection
+* [Issue 46](https://github.com/pytroll/pycoast/issues/46) - add_coastlines call is incorrect in documentation
+
+In this release 2 issues were closed.
+
+### Pull Requests Merged
+
+#### Features added
+
+* [PR 54](https://github.com/pytroll/pycoast/pull/54) - Add area and parameter hashing to overlay caching
+* [PR 53](https://github.com/pytroll/pycoast/pull/53) - Switch to GitHub Actions for CI
+
+In this release 2 pull requests were closed.
+
+
 ## Version 1.4.0 (2020/06/08)
 
 ### Issues Closed
@@ -13,7 +32,11 @@ In this release 1 issue was closed.
 * [PR 45](https://github.com/pytroll/pycoast/pull/45) - Fix cached overlays always being regenerated
 * [PR 44](https://github.com/pytroll/pycoast/pull/44) - Fix adding textbox in the add_points method
 
-In this release 2 pull requests were closed.
+#### Features added
+
+* [PR 42](https://github.com/pytroll/pycoast/pull/42) - Add "add_points" method for point/symbol data ([36](https://github.com/pytroll/pycoast/issues/36))
+
+In this release 3 pull requests were closed.
 
 
 ## Version 1.3.2 (2019/12/06)


=====================================
README.rst
=====================================
@@ -1,8 +1,11 @@
 PyCoast
 =======
 
-.. image:: https://travis-ci.org/pytroll/pycoast.svg?branch=master
-    :target: https://travis-ci.org/pytroll/pycoast
+.. image:: https://github.com/pytroll/pycoast/workflows/CI/badge.svg?branch=main
+    :target: https://github.com/pytroll/pycoast/actions?query=workflow%3A%22CI%22
+
+.. image:: https://coveralls.io/repos/github/pytroll/pycoast/badge.svg?branch=main
+    :target: https://coveralls.io/github/pytroll/pycoast?branch=main
 
 .. image:: https://img.shields.io/pypi/v/pycoast.svg
         :target: https://pypi.python.org/pypi/pycoast


=====================================
RELEASING.md
=====================================
@@ -1,28 +1,47 @@
 # Releasing PyCoast
 
-1. checkout master
+1. checkout main branch
 2. pull from repo
 3. run the unittests
 4. run `loghub` and update the `CHANGELOG.md` file:
 
-```
-loghub pytroll/pycoast -u <username> -st v<last-tag> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes"
-```
+   ```
+   loghub pytroll/pycoast --token $LOGHUB_GITHUB_TOKEN -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backward incompatible changes" -plg refactor "Refactoring"
+   ```
 
-Don't forget to commit!
+   This uses a `LOGHUB_GITHUB_TOKEN` environment variable. This must be created
+   on GitHub and it is recommended that you add it to your `.bashrc` or
+   `.bash_profile` or equivalent.
+
+   This command will create a CHANGELOG.temp file which need to be added
+   to the top of the CHANGLOG.md file.  The same content is also printed
+   to terminal, so that can be copy-pasted, too.  Remember to update also
+   the version number to the same given in step 5. Don't forget to commit
+   CHANGELOG.md!
 
 5. Create a tag with the new version number, starting with a 'v', eg:
 
-```
-git tag -a v0.22.45 -m "Version 0.22.45"
-```
+   ```
+   git tag -a v<new version> -m "Version <new version>"
+   ```
 
-See [semver.org](http://semver.org/) on how to write a version number.
+   For example if the previous tag was `v0.9.0` and the new release is a
+   patch release, do:
 
+   ```
+   git tag -a v0.9.1 -m "Version 0.9.1"
+   ```
 
+   See [semver.org](http://semver.org/) on how to write a version number..
 
-6. push changes to github `git push --follow-tags`
 
-Make sure the new tag has been pushed.
+6. push changes to github `git push --follow-tags`
+7. Verify github action unittests passed.
+8. Create a "Release" on GitHub by going to
+   https://github.com/pytroll/pycoast/releases and clicking "Draft a new release".
+   On the next page enter the newly created tag in the "Tag version" field,
+   "Version X.Y.Z" in the "Release title" field, and paste the markdown from
+   the changelog (the portion under the version section header) in the
+   "Describe this release" box. Finally click "Publish release".
+9. Verify the GitHub actions for deployment succeed and the release is on PyPI.
 
-7. Verify travis tests passed and deployed sdist to PyPI


=====================================
continuous_integration/environment.yaml
=====================================
@@ -0,0 +1,14 @@
+name: test-environment
+channels:
+  - conda-forge
+dependencies:
+  - sphinx
+  - pillow
+  - pyproj
+  - coveralls
+  - coverage
+  - aggdraw
+  - pyshp
+  - pyresample
+  - pytest
+  - pytest-cov


=====================================
debian/changelog
=====================================
@@ -1,11 +1,27 @@
-pycoast (1.4.0+dfsg-2) UNRELEASED; urgency=medium
+pycoast (1.5.0+dfsg-1) unstable; urgency=medium
 
-  * Team upload.
+  [ Bas Couwenberg ]
   * Bump watch file version to 4.
   * Bump Standards-Version to 4.5.1, no changes.
   * Update watch file for GitHub URL changes.
 
- -- Bas Couwenberg <sebastic at debian.org>  Fri, 06 Nov 2020 19:48:07 +0100
+  [ Antonio Valentino ]
+  * New upstream release.
+  * debian/copyright
+    - update the list excluded files: pre-built documentation
+      is no longer included in the upstream tarball
+    - update copyright dates.
+  * debian/patches
+    - refresh all patches
+    - new 0002-Fix-font-path.patch (only needed for testing on
+      debian systems)
+  * debian/control
+    - update dependencies: drop six, add pytest and
+      fonts-dejavu-core (build only).
+  * debian/rules
+    - fix pytest args.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Sun, 29 Aug 2021 17:23:17 +0000
 
 pycoast (1.4.0+dfsg-1) unstable; urgency=medium
 


=====================================
debian/control
=====================================
@@ -7,6 +7,7 @@ Rules-Requires-Root: no
 Priority: optional
 Build-Depends: debhelper-compat (= 12),
                dh-python,
+               fonts-dejavu-core,
                python3-all,
                python3-aggdraw,
                python3-numpy,
@@ -14,8 +15,8 @@ Build-Depends: debhelper-compat (= 12),
                python3-pyproj,
                python3-pyresample,
                python3-pyshp,
+               python3-pytest,
                python3-setuptools,
-               python3-six,
                python3-sphinx
 Standards-Version: 4.5.1
 Vcs-Browser: https://salsa.debian.org/debian-gis-team/pycoast
@@ -29,10 +30,10 @@ Depends: python3-aggdraw,
          python3-pil,
          python3-pyproj,
          python3-pyshp,
-         python3-six,
          ${python3:Depends},
          ${misc:Depends}
-Suggests: python-pycoast-doc
+Suggests: fonts-dejavu-core,
+          python-pycoast-doc
 Description: Draw coastlines, borders and rivers on images (for Python 3)
  Pycoast is a Python package to add coastlines, borders and rivers to
  raster images using data from the GSHHG (previously known as GSHHS)


=====================================
debian/copyright
=====================================
@@ -1,18 +1,17 @@
 Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: pycoast
 Source: https://github.com/pytroll/pycoast
- The upstream tarball is repacked to exclude pre-built documentation and
- test data without unclear license. From the test data README:
+ The upstream tarball is repacked to exclude test data without
+ unclear license. From the test data README:
  "
   This data is preliminary.
   We await permission from the data provider
   to include these shapes in pycoast testing.
  "
-Files-Excluded: docs/build/*
- pycoast/tests/test_data/shapes/*
+Files-Excluded: pycoast/tests/test_data/shapes/*
 
 Files: *
-Copyright: 2011-2020, PyCoast Developers
+Copyright: 2011-2021, PyCoast Developers
            2011-2016, Esben S. Nielsen <esn at dmi.dk>
            2011-2015, Hróbjartur Þorsteinsson
            2011-2015, Stefano Cerino
@@ -42,7 +41,7 @@ Copyright: 1991-2008, P. Wessel & W. H. F. Smith
 License: GPL-2+
 
 Files: debian/*
-Copyright: 2014-2020, Antonio Valentino <antonio.valentino at tiscali.it>
+Copyright: 2014-2021, Antonio Valentino <antonio.valentino at tiscali.it>
 License: GPL-3+
 
 License: CC0-1.0


=====================================
debian/patches/0001-Skip-tests-that-use-shapes.patch
=====================================
@@ -2,15 +2,16 @@ From: Antonio Valentino <antonio.valentino at tiscali.it>
 Date: Sun, 3 Jul 2016 17:17:27 +0000
 Subject: Skip tests that use shapes
 
+Forwarded: not-needed
 ---
  pycoast/tests/test_pycoast.py | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/pycoast/tests/test_pycoast.py b/pycoast/tests/test_pycoast.py
-index 82adcb0..b95edba 100644
+index b599ff2..0533cc4 100644
 --- a/pycoast/tests/test_pycoast.py
 +++ b/pycoast/tests/test_pycoast.py
-@@ -397,6 +397,7 @@ class TestPIL(TestPycoast):
+@@ -392,6 +392,7 @@ class TestPIL(TestPycoast):
          self.assertTrue(fft_metric(grid_data, res),
                          'Writing of nh points failed')
  
@@ -18,7 +19,7 @@ index 82adcb0..b95edba 100644
      def test_add_shapefile_shapes(self):
          from pycoast import ContourWriterPIL
          grid_img = Image.open(os.path.join(os.path.dirname(__file__),
-@@ -749,6 +750,7 @@ class TestPILAGG(TestPycoast):
+@@ -745,6 +746,7 @@ class TestPILAGG(TestPycoast):
          res = np.array(img)
          self.assertTrue(fft_metric(grid_data, res), 'Writing of nh points failed')
  


=====================================
debian/patches/0002-Fix-font-path.patch
=====================================
@@ -0,0 +1,54 @@
+From: Antonio Valentino <antonio.valentino at tiscali.it>
+Date: Sun, 29 Aug 2021 16:53:41 +0000
+Subject: Fix font path
+
+The patch is only needed for testing on debian systems.
+
+Forwarded: not-needed
+---
+ pycoast/tests/coasts_and_grid_agg.ini | 3 ++-
+ pycoast/tests/nh_points_agg.ini       | 3 ++-
+ pycoast/tests/nh_points_pil.ini       | 3 ++-
+ 3 files changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/pycoast/tests/coasts_and_grid_agg.ini b/pycoast/tests/coasts_and_grid_agg.ini
+index 674334f..26b0389 100644
+--- a/pycoast/tests/coasts_and_grid_agg.ini
++++ b/pycoast/tests/coasts_and_grid_agg.ini
+@@ -13,6 +13,7 @@ minor_outline = blue
+ outline = blue
+ lon_placement = tblr
+ lat_placement = ''
+-font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++# font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++font = '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf'
+ font_size = 10
+ 
+diff --git a/pycoast/tests/nh_points_agg.ini b/pycoast/tests/nh_points_agg.ini
+index 5a2252f..37d22a7 100644
+--- a/pycoast/tests/nh_points_agg.ini
++++ b/pycoast/tests/nh_points_agg.ini
+@@ -11,7 +11,8 @@ resolution = c
+ 
+ [points]
+ points_list = ((2.3522, 48.8566), 'Paris'), ((0.1278, 51.5074), 'London')
+-font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++# font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++font = '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf'
+ symbol = circle
+ ptsize = 16
+ outline = black
+diff --git a/pycoast/tests/nh_points_pil.ini b/pycoast/tests/nh_points_pil.ini
+index a33738d..7f26982 100644
+--- a/pycoast/tests/nh_points_pil.ini
++++ b/pycoast/tests/nh_points_pil.ini
+@@ -11,7 +11,8 @@ resolution = c
+ 
+ [points]
+ points_list = ((13.4050, 52.5200), 'Berlin'), ((12.4964, 41.9028), 'Rome')
+-font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++# font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
++font = '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf'
+ symbol = square
+ ptsize = 6
+ outline = red


=====================================
debian/patches/series
=====================================
@@ -1 +1,2 @@
 0001-Skip-tests-that-use-shapes.patch
+0002-Fix-font-path.patch


=====================================
debian/rules
=====================================
@@ -1,12 +1,11 @@
 #!/usr/bin/make -f
 # -*- makefile -*-
 
-# Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
 
 export PYBUILD_NAME=pycoast
-#export PYBUILD_TEST_ARGS=test
+export PYBUILD_TEST_ARGS=$(CURDIR)/pycoast/tests
 
 
 %:


=====================================
pycoast/__init__.py
=====================================
@@ -1,8 +1,9 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
+"""Pycoast package for adding geographic-based decorations to images."""
 from .cw_pil import ContourWriterPIL
-from .cw_agg import ContourWriterAGG
-from pycoast.cw_base import get_resolution_from_area
+from .cw_agg import ContourWriterAGG  # noqa
+from pycoast.cw_base import get_resolution_from_area  # noqa
 from .version import get_versions
 __version__ = get_versions()['version']
 del get_versions


=====================================
pycoast/cw_agg.py
=====================================
@@ -16,6 +16,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""ContourWriter based on the aggdraw library."""
 
 from PIL import Image
 import logging
@@ -35,13 +36,14 @@ class ContourWriterAGG(ContourWriterBase):
             Path to root dir of GSHHS and WDBII shapefiles
 
     """
+
     _draw_module = "AGG"
     # This is a flag to make _add_grid aware of which text draw routine
     # from PIL or from aggdraw should be used
     # (unfortunately they are not fully compatible)
 
     def _get_canvas(self, image):
-        """Returns AGG image object."""
+        """Return AGG image object."""
         return aggdraw.Draw(image)
 
     def _engine_text_draw(self, draw, x_pos, y_pos, txt, font, **kwargs):
@@ -85,7 +87,6 @@ class ContourWriterAGG(ContourWriterBase):
     def _draw_text_box(self, draw, text_position, text, font, outline,
                        box_outline, box_opacity, **kwargs):
         """Add a text box at position (x,y)."""
-
         if box_outline is not None:
             text_size = draw.textsize(text, font)
             margin = 2
@@ -112,7 +113,7 @@ class ContourWriterAGG(ContourWriterBase):
         draw.line(coordinates, pen)
 
     def _draw_asterisk(self, draw, pt_size, coordinate, **kwargs):
-        """Draw a asterisk sign '*' at the given coordinate. """
+        """Draw a asterisk sign '*' at the given coordinate."""
         half_ptsize = int(round(pt_size / 2.))
         x, y = coordinate
 
@@ -403,7 +404,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Use tick minor line style (True) or full minor line style (False)
 
         """
-
         image = Image.open(filename)
         image = image.convert('RGBA')
         self.add_grid(image, area_def, Dlonlat, dlonlat,
@@ -450,7 +450,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'polygon', 'GSHHS',
                           resolution=resolution, level=level,
                           fill=fill, fill_opacity=fill_opacity,
@@ -491,7 +490,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         image = Image.open(filename)
         image = image.convert('RGBA')
         self.add_coastlines(image, area_def, resolution=resolution,
@@ -529,7 +527,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'line', 'WDBII', tag='border',
                           resolution=resolution, level=level, outline=outline,
                           width=width, outline_opacity=outline_opacity,
@@ -599,7 +596,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'line', 'WDBII', tag='river',
                           zero_pad=True, resolution=resolution, level=level,
                           outline=outline, width=width,
@@ -634,7 +630,6 @@ class ContourWriterAGG(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         image = Image.open(filename)
         image = image.convert("RGBA")
         self.add_rivers(image, area_def, resolution=resolution, level=level,


=====================================
pycoast/cw_base.py
=====================================
@@ -16,8 +16,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Base class for contour writers."""
 
 import os
+import hashlib
+import json
 import shapefile
 import numpy as np
 from PIL import Image
@@ -25,10 +28,7 @@ import pyproj
 import logging
 import ast
 
-try:
-    import configparser
-except ImportError:
-    from six.moves import configparser
+import configparser
 
 logger = logging.getLogger(__name__)
 
@@ -44,12 +44,8 @@ def get_resolution_from_area(area_def):
         x_resolution = (x_ur - x_ll) / x_size
         y_resolution = (y_ur - y_ll) / y_size
     else:
-        x_resolution = ((area_def.area_extent[2] -
-                         area_def.area_extent[0]) /
-                        x_size)
-        y_resolution = ((area_def.area_extent[3] -
-                         area_def.area_extent[1]) /
-                        y_size)
+        x_resolution = (area_def.area_extent[2] - area_def.area_extent[0]) / x_size
+        y_resolution = (area_def.area_extent[3] - area_def.area_extent[1]) / y_size
     res = min(x_resolution, y_resolution)
 
     if res > 25000:
@@ -64,6 +60,14 @@ def get_resolution_from_area(area_def):
         return "f"
 
 
+def hash_dict(dict_to_hash: dict) -> str:
+    """Hash dict object by serializing with json."""
+    dhash = hashlib.sha256()
+    encoded = json.dumps(dict_to_hash, sort_keys=True).encode()
+    dhash.update(encoded)
+    return dhash.hexdigest()
+
+
 class Proj(pyproj.Proj):
     """Wrapper around pyproj to add in 'is_latlong'."""
 
@@ -95,8 +99,7 @@ class ContourWriterBase(object):
             self.db_root_path = db_root_path
 
     def _draw_text(self, draw, position, txt, font, align='cc', **kwargs):
-        """Draw text with agg module
-        """
+        """Draw text with agg module."""
         txt_width, txt_height = draw.textsize(txt, font)
         x_pos, y_pos = position
         ax, ay = align.lower()
@@ -116,8 +119,7 @@ class ContourWriterBase(object):
         raise NotImplementedError('Text drawing undefined for render engine')
 
     def _draw_grid_labels(self, draw, xys, linetype, txt, font, **kwargs):
-        """Draw text with default PIL module
-        """
+        """Draw text with default PIL module."""
         if font is None:
             # NOTE: Default font does not use font size in PIL writer
             font = self._get_font(kwargs.get('outline', 'black'), font, 12)
@@ -130,8 +132,11 @@ class ContourWriterBase(object):
                 self._draw_text(draw, xy[0], txt, font, align=xy[1], **kwargs)
 
     def _find_line_intercepts(self, xys, size, margins):
-        """Finds intercepts of poly-line xys with image boundaries
-        offset by margins and returns an array of coordinates"""
+        """Find intercepts of poly-line xys with image boundaries offset by margins.
+
+        Returns an array of coordinates.
+
+        """
         x_size, y_size = size
 
         def is_in_box(x_y, extents):
@@ -196,9 +201,7 @@ class ContourWriterBase(object):
                   Dlon, Dlat,
                   dlon, dlat,
                   font=None, write_text=True, **kwargs):
-        """Add a lat lon grid to image
-        """
-
+        """Add a lat lon grid to image."""
         try:
             proj_def = area_def.crs if hasattr(area_def, 'crs') else area_def.proj_dict
             area_extent = area_def.area_extent
@@ -489,16 +492,20 @@ class ContourWriterBase(object):
 
     def _add_shapefile_shape(self, image, area_def, filename, shape_id,
                              feature_type=None, **kwargs):
-        """ for drawing a single shape (polygon/poly-line) definiton with id,
-        shape_id from a custom shape file onto a PIL image
+        """Draw a single shape (polygon/poly-line) definition.
+
+        Accesses single shape using shape_id from a custom shape file.
+
         """
         sf = shapefile.Reader(filename)
         shape = sf.shape(shape_id)
         return self.add_shapes(image, area_def, [shape], feature_type=feature_type, **kwargs)
 
     def _add_line(self, image, area_def, lonlats, **kwargs):
-        """ For drawing a custom polyline. Lon and lat coordinates given by the
-        list lonlat.
+        """Draw a custom polyline.
+
+        Lon and lat coordinates given by the list lonlat.
+
         """
         # create dummpy shapelike object
         shape = type("", (), {})()
@@ -508,8 +515,10 @@ class ContourWriterBase(object):
         self.add_shapes(image, area_def, [shape], feature_type="line", **kwargs)
 
     def _add_polygon(self, image, area_def, lonlats, **kwargs):
-        """ For drawing a custom polygon. Lon and lat coordinates given by the
-        list lonlat.
+        """Draw a custom polygon.
+
+        Lon and lat coordinates given by the list lonlat.
+
         """
         # create dummpy shapelike object
         shape = type("", (), {})()
@@ -584,10 +593,9 @@ class ContourWriterBase(object):
 
             # Check if polygon is possibly relevant
             s_lon_ll, s_lat_ll, s_lon_ur, s_lat_ur = shape.bbox
-            if lon_min > lon_max:
-                pass
-            elif (lon_max < s_lon_ll or lon_min > s_lon_ur or
-                  lat_max < s_lat_ll or lat_min > s_lat_ur):
+            shape_is_outside_lon = lon_max < s_lon_ll or lon_min > s_lon_ur
+            shape_is_outside_lat = lat_max < s_lat_ll or lat_min > s_lat_ur
+            if lon_min <= lon_max and (shape_is_outside_lon or shape_is_outside_lat):
                 # Polygon is irrelevant
                 continue
 
@@ -625,8 +633,7 @@ class ContourWriterBase(object):
                      db_name, tag=None, zero_pad=False, resolution='c',
                      level=1, x_offset=0, y_offset=0, db_root_path=None,
                      **kwargs):
-        """Add a contour feature to image
-        """
+        """Add a contour feature to image."""
         shape_generator = self._iterate_db(
             db_name, tag, resolution, level, zero_pad,
             db_root_path=db_root_path
@@ -636,8 +643,7 @@ class ContourWriterBase(object):
                                x_offset=x_offset, y_offset=y_offset, **kwargs)
 
     def _iterate_db(self, db_name, tag, resolution, level, zero_pad, db_root_path=None):
-        """Iterate through datasets
-        """
+        """Iterate through datasets."""
         if db_root_path is None:
             db_root_path = self.db_root_path
         if db_root_path is None:
@@ -680,7 +686,6 @@ class ContourWriterBase(object):
 
     def _finalize(self, draw):
         """Do any need finalization of the drawing."""
-
         pass
 
     def _config_to_dict(self, config_file):
@@ -712,8 +717,16 @@ class ContourWriterBase(object):
         return overlays
 
     def add_overlay_from_dict(self, overlays, area_def, cache_epoch=None, background=None):
-        """Create and return a transparent image adding all the overlays contained in
-           the `overlays` dict.
+        """Create and return a transparent image adding all the overlays contained in the `overlays` dict.
+
+        Optionally caches overlay results for faster rendering of images with
+        the same provided AreaDefinition and parameters. Cached results are
+        identified by hashing the AreaDefinition and the overlays dictionary.
+
+        .. warning::
+
+            Font objects are ignored in parameter hashing as they can't be easily hashed.
+            Therefore font changes will not trigger a new rendering for the cache.
 
         :Parameters:
             overlays : dict
@@ -748,28 +761,18 @@ class ContourWriterBase(object):
                   of an already cached file.
 
         """
-
         # Cache management
         cache_file = None
         if 'cache' in overlays:
-            cache_file = (overlays['cache']['file'] + '_' +
-                          area_def.area_id + '.png')
-
-            try:
-                config_time = cache_epoch or 0
-                cache_time = os.path.getmtime(cache_file)
-                # Cache file will be used only if it's newer than config file
-                if ((config_time is not None and config_time < cache_time)
-                        and not overlays['cache'].get('regenerate', False)):
-                    foreground = Image.open(cache_file)
-                    logger.info('Using image in cache %s', cache_file)
-                    if background is not None:
-                        background.paste(foreground, mask=foreground.split()[-1])
-                    return foreground
-                else:
-                    logger.info("Regenerating cache file.")
-            except OSError:
-                logger.info("No overlay image found, new overlay image will be saved in cache.")
+            cache_file = self._generate_cache_filename(
+                overlays['cache']['file'],
+                area_def,
+                overlays,
+            )
+            regenerate = overlays['cache'].get('regenerate', False)
+            foreground = self._apply_cached_image(cache_file, cache_epoch, background, regenerate=regenerate)
+            if foreground is not None:
+                return foreground
 
         x_size = area_def.width
         y_size = area_def.height
@@ -794,24 +797,22 @@ class ContourWriterBase(object):
                    'resolution': default_resolution}
 
         for section, fun in zip(['coasts', 'rivers', 'borders'],
-                                [self.add_coastlines,
-                                 self.add_rivers,
-                                 self.add_borders]):
-            if section in overlays:
-
-                params = DEFAULT.copy()
-                params.update(overlays[section])
+                                [self.add_coastlines, self.add_rivers, self.add_borders]):
+            if section not in overlays:
+                continue
+            params = DEFAULT.copy()
+            params.update(overlays[section])
 
-                if section != "coasts":
-                    params.pop('fill_opacity', None)
-                    params.pop('fill', None)
+            if section != "coasts":
+                params.pop('fill_opacity', None)
+                params.pop('fill', None)
 
-                if not is_agg:
-                    for key in ['width', 'outline_opacity', 'fill_opacity']:
-                        params.pop(key, None)
+            if not is_agg:
+                for key in ['width', 'outline_opacity', 'fill_opacity']:
+                    params.pop(key, None)
 
-                fun(foreground, area_def, **params)
-                logger.info("%s added", section.capitalize())
+            fun(foreground, area_def, **params)
+            logger.info("%s added", section.capitalize())
 
         # Cities management
         if 'cities' in overlays:
@@ -862,6 +863,10 @@ class ContourWriterBase(object):
             lat_minor = float(overlays['grid'].get('lat_minor', 2.0))
             font = overlays['grid'].get('font', None)
             font_size = int(overlays['grid'].get('font_size', 10))
+            grid_kwargs = {}
+            if is_agg:
+                width = float(overlays['grid'].get('width', 1.0))
+                grid_kwargs["width"] = width
 
             write_text = overlays['grid'].get('write_text', True)
             if isinstance(write_text, str):
@@ -888,20 +893,62 @@ class ContourWriterBase(object):
                           outline=outline, minor_outline=minor_outline,
                           minor_is_tick=minor_is_tick,
                           lon_placement=lon_placement,
-                          lat_placement=lat_placement)
+                          lat_placement=lat_placement,
+                          **grid_kwargs)
 
         if cache_file is not None:
-            try:
-                foreground.save(cache_file)
-            except IOError as e:
-                logger.error("Can't save cache: %s", str(e))
-            if background is not None:
-                background.paste(foreground, mask=foreground.split()[-1])
+            self._write_and_apply_new_cached_image(cache_file, foreground, background)
         return foreground
 
+    @staticmethod
+    def _apply_cached_image(cache_file, cache_epoch, background, regenerate=False):
+        try:
+            config_time = cache_epoch or 0
+            cache_time = os.path.getmtime(cache_file)
+            # Cache file will be used only if it's newer than config file
+            if config_time is not None and config_time < cache_time and not regenerate:
+                foreground = Image.open(cache_file)
+                logger.info('Using image in cache %s', cache_file)
+                if background is not None:
+                    background.paste(foreground, mask=foreground.split()[-1])
+                return foreground
+            logger.info("Regenerating cache file.")
+        except OSError:
+            logger.info("No overlay image found, new overlay image will be saved in cache.")
+        return None
+
+    @staticmethod
+    def _write_and_apply_new_cached_image(cache_file, foreground, background):
+        try:
+            foreground.save(cache_file)
+        except IOError as e:
+            logger.error("Can't save cache: %s", str(e))
+        if background is not None:
+            background.paste(foreground, mask=foreground.split()[-1])
+
+    def _generate_cache_filename(self, cache_prefix, area_def, overlays_dict):
+        area_hash = hash(area_def)
+        base_dir, file_prefix = os.path.split(cache_prefix)
+        params_to_hash = self._prepare_hashable_dict(overlays_dict)
+        param_hash = hash_dict(params_to_hash)
+        return os.path.join(base_dir, f"{file_prefix}_{area_hash}_{param_hash}.png")
+
+    @staticmethod
+    def _prepare_hashable_dict(nonhashable_dict):
+        params_to_hash = {}
+        # avoid wasteful deep copy by only doing two levels of copying
+        for overlay_name, overlay_dict in nonhashable_dict.items():
+            if overlay_name == 'cache':
+                continue
+            params_to_hash[overlay_name] = overlay_dict.copy()
+        # font objects are not hashable
+        for font_cat in ('cities', 'points', 'grid'):
+            if font_cat in params_to_hash:
+                params_to_hash[font_cat].pop('font', None)
+        return params_to_hash
+
     def add_overlay_from_config(self, config_file, area_def, background=None):
-        """Create and return a transparent image adding all the overlays contained in
-           a configuration file.
+        """Create and return a transparent image adding all the overlays contained in a configuration file.
 
         :Parameters:
             config_file : str
@@ -916,9 +963,7 @@ class ContourWriterBase(object):
 
     def add_cities(self, image, area_def, citylist, font_file, font_size,
                    ptsize, outline, box_outline, box_opacity, db_root_path=None):
-        """Add cities (point and name) to a PIL image object
-
-        """
+        """Add cities (point and name) to a PIL image object."""
         if db_root_path is None:
             db_root_path = self.db_root_path
         if db_root_path is None:
@@ -1094,9 +1139,7 @@ class ContourWriterBase(object):
 
 
 def _get_lon_lat_bounding_box(area_extent, x_size, y_size, prj):
-    """Get extreme lon and lat values
-    """
-
+    """Get extreme lon and lat values."""
     x_ll, y_ll, x_ur, y_ur = area_extent
     x_range = np.linspace(x_ll, x_ur, num=x_size)
     y_range = np.linspace(y_ll, y_ur, num=y_size)


=====================================
pycoast/cw_pil.py
=====================================
@@ -16,6 +16,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""PIL-based ContourWriter."""
 
 from PIL import Image, ImageFont
 from PIL import ImageDraw
@@ -41,8 +42,7 @@ class ContourWriterPIL(ContourWriterBase):
     # they are not fully compatible)
 
     def _get_canvas(self, image):
-        """Returns PIL image object."""
-
+        """Return PIL image object."""
         return ImageDraw.Draw(image)
 
     def _engine_text_draw(self, draw, x_pos, y_pos, txt, font, **kwargs):
@@ -75,7 +75,7 @@ class ContourWriterPIL(ContourWriterBase):
         draw.line(coordinates, fill=kwargs['outline'])
 
     def _draw_asterisk(self, draw, pt_size, coordinate, **kwargs):
-        """Draw a asterisk sign '*' center at the given coordinate """
+        """Draw a asterisk sign '*' center at the given coordinate."""
         half_ptsize = int(round(pt_size / 2.))
         x, y = coordinate
 
@@ -333,7 +333,6 @@ class ContourWriterPIL(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'polygon', 'GSHHS',
                           resolution=resolution, level=level,
                           fill=fill, outline=outline, x_offset=x_offset,
@@ -365,7 +364,6 @@ class ContourWriterPIL(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         image = Image.open(filename)
         self.add_coastlines(image, area_def,
                             resolution=resolution, level=level,
@@ -396,7 +394,6 @@ class ContourWriterPIL(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'line', 'WDBII',
                           tag='border', resolution=resolution, level=level,
                           outline=outline, x_offset=x_offset,
@@ -454,7 +451,6 @@ class ContourWriterPIL(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         self._add_feature(image, area_def, 'line', 'WDBII',
                           tag='river', zero_pad=True, resolution=resolution,
                           level=level, outline=outline, x_offset=x_offset,
@@ -483,7 +479,6 @@ class ContourWriterPIL(ContourWriterBase):
                 Pixel offset in y direction
 
         """
-
         image = Image.open(filename)
         self.add_rivers(image, area_def, resolution=resolution, level=level,
                         outline=outline, x_offset=x_offset, y_offset=y_offset)


=====================================
pycoast/tests/__init__.py
=====================================
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# pycoast, Writing of coastlines, borders and rivers to images in Python
+#
+# Copyright (C) 2021 PyCoast Developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Pycoast unit tests."""


=====================================
pycoast/tests/test_pycoast.py
=====================================
@@ -16,29 +16,24 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Main unit tests for pycoast."""
 
 import os
 import unittest
+from glob import glob
 
 import numpy as np
 from PIL import Image, ImageFont
 import time
 
 
-def tmp(f):
-    f.tmp = True
-    return f
-
-
 def fft_proj_rms(a1, a2):
-    """Compute the RMS of differences between FFT vectors of a1
-    and projection of FFT vectors of a2.
+    """Compute the RMS of differences between FFT vectors of a1 and projection of FFT vectors of a2.
+
     This metric is sensitive to large scale changes and image noise but
     insensitive to small rendering differences.
     """
-
     ms = 0
-
     for i in range(3):
         fr1 = np.fft.fft2(a1[:, :, i])
         fr2 = np.fft.fft2(a2[:, :, i])
@@ -50,8 +45,8 @@ def fft_proj_rms(a1, a2):
         p2 = np.arctan2(fr2.imag, fr2.real)
 
         theta = p2 - p1
-        l = ps2 * np.cos(theta)
-        ms += ((l - ps1) ** 2).sum() / float(ps1.size)
+        adjusted_ps2 = ps2 * np.cos(theta)
+        ms += ((adjusted_ps2 - ps1) ** 2).sum() / float(ps1.size)
 
     rms = np.sqrt(ms)
 
@@ -59,9 +54,7 @@ def fft_proj_rms(a1, a2):
 
 
 def fft_metric(data1, data2, max_value=0.1):
-    """Execute FFT metric
-    """
-
+    """Execute FFT metric."""
     rms = fft_proj_rms(data1, data2)
     return rms <= max_value
 
@@ -73,6 +66,7 @@ p_file_coasts = 'test_coasts_p_mode.png'
 
 
 class TestPycoast(unittest.TestCase):
+    """Base class for test classes that need example images."""
 
     def setUp(self):
         img = Image.new('RGB', (640, 480))
@@ -88,6 +82,7 @@ class TestPycoast(unittest.TestCase):
 
 
 class TestPIL(TestPycoast):
+    """Test PIL-based contour writer."""
 
     def test_europe(self):
         from pycoast import ContourWriterPIL
@@ -342,11 +337,11 @@ class TestPIL(TestPycoast):
                 (-39, 63.5), (-55 + 4 / 6.0, 63.5), (-57 + 45 / 60.0, 65),
                 (-76, 76), (-75, 78), (-60, 82), (0, 90),
                 (30, 82), (0, 82), (0, 73), (-20, 73), (-20, 70)),
-            'REYKJAVIK_ATC':   (
+            'REYKJAVIK_ATC': (
                 (0.0, 73.0), (0.0, 61.0), (-30.0, 61.0), (-39, 63.5),
                 (-55 + 4 / 6.0, 63.5), (-57 + 45 / 60.0, 65),
                 (-76, 76), (-75, 78), (-60, 82), (0, 90), (30, 82), (0, 82)),
-            'ICELAND_BOX':     ((-25, 62.5), (-25, 67), (-13, 67), (-13, 62.5))
+            'ICELAND_BOX': ((-25, 62.5), (-25, 67), (-13, 67), (-13, 62.5))
         }
 
         cw.add_polygon(img, area_def, polygons['REYKJAVIK_ATC'], outline='red')
@@ -482,6 +477,7 @@ class TestPIL(TestPycoast):
 
 
 class TestPILAGG(TestPycoast):
+    """Test AGG contour writer."""
 
     def test_europe_agg(self):
         from pycoast import ContourWriterAGG
@@ -852,7 +848,7 @@ class TestPILAGG(TestPycoast):
         self.assertTrue(image_mode == 'RGBA', 'Conversion to RGBA failed.')
 
 
-class FakeAreaDef():
+class FakeAreaDef:
     """A fake area definition object."""
 
     def __init__(self, proj4_string, area_extent, x_size, y_size):
@@ -863,7 +859,7 @@ class FakeAreaDef():
         self.area_id = 'fakearea'
 
 
-class TestFromConfig(TestPycoast):
+class TestFromConfig:
     """Test burning overlays from a config file."""
 
     def test_foreground(self):
@@ -883,8 +879,7 @@ class TestFromConfig(TestPycoast):
         img = cw.add_overlay_from_config(config_file, area_def)
 
         res = np.array(img)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
 
         overlays = {'coasts': {'level': [1, 2, 3, 4], 'resolution': 'l'},
                     'borders': {'outline': (255, 0, 0), 'resolution': 'c'},
@@ -892,96 +887,127 @@ class TestFromConfig(TestPycoast):
 
         img = cw.add_overlay_from_dict(overlays, area_def)
         res = np.array(img)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
 
-    def test_cache(self):
+    def test_cache(self, tmpdir):
         """Test generating a transparent foreground and cache it."""
         from pycoast import ContourWriterPIL
-        from tempfile import gettempdir
         euro_img = Image.open(os.path.join(os.path.dirname(__file__),
                                            'contours_europe_alpha.png'))
         euro_data = np.array(euro_img)
 
-        # img = Image.new('RGB', (640, 480))
         proj4_string = \
             '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
         area_extent = (-3363403.31, -2291879.85, 2630596.69, 2203620.1)
         area_def = FakeAreaDef(proj4_string, area_extent, 640, 480)
         cw = ContourWriterPIL(gshhs_root_dir)
 
-        tmp = gettempdir()
-
-        overlays = {'cache': {'file': os.path.join(tmp, 'pycoast_cache')},
+        overlays = {'cache': {'file': os.path.join(tmpdir, 'pycoast_cache')},
                     'coasts': {'level': 4, 'resolution': 'l'},
                     'borders': {'outline': (255, 0, 0), 'resolution': 'c'},
                     'rivers': {'outline': 'blue', 'resolution': 'c', 'level': 5}}
 
         # Create the original cache file
-        cache_filename = os.path.join(tmp, 'pycoast_cache_fakearea.png')
         img = cw.add_overlay_from_dict(overlays, area_def)
         res = np.array(img)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
-        self.assertTrue(os.path.isfile(cache_filename))
+        cache_glob = glob(os.path.join(tmpdir, 'pycoast_cache_*.png'))
+        assert len(cache_glob) == 1
+        cache_filename = cache_glob[0]
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
+        assert os.path.isfile(cache_filename)
         mtime = os.path.getmtime(cache_filename)
 
         # Reuse the generated cache file
         img = cw.add_overlay_from_dict(overlays, area_def)
         res = np.array(img)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
-        self.assertTrue(os.path.isfile(cache_filename))
-        self.assertEqual(os.path.getmtime(cache_filename), mtime)
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
+        assert os.path.isfile(cache_filename)
+        assert os.path.getmtime(cache_filename) == mtime
 
         # Regenerate cache file
         current_time = time.time()
         cw.add_overlay_from_dict(overlays, area_def, current_time)
         mtime = os.path.getmtime(cache_filename)
-        self.assertGreater(mtime, current_time)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
+        assert mtime > current_time
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
 
         cw.add_overlay_from_dict(overlays, area_def, current_time)
-        self.assertEqual(os.path.getmtime(cache_filename), mtime)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
+        assert os.path.getmtime(cache_filename) == mtime
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
         overlays['cache']['regenerate'] = True
         cw.add_overlay_from_dict(overlays, area_def)
 
-        self.assertNotEqual(os.path.getmtime(cache_filename), mtime)
-        self.assertTrue(fft_metric(euro_data, res),
-                        'Writing of contours failed')
+        assert os.path.getmtime(cache_filename) != mtime
+        assert fft_metric(euro_data, res), 'Writing of contours failed'
 
         overlays.pop('cache')
         overlays['grid'] = {'outline': (255, 255, 255), 'outline_opacity': 175,
                             'minor_outline': (200, 200, 200), 'minor_outline_opacity': 127,
                             'width': 1.0, 'minor_width': 0.5, 'minor_is_tick': True,
                             'write_text': True, 'lat_placement': 'lr', 'lon_placement': 'b'}
-        img = cw.add_overlay_from_dict(overlays, area_def)
-        os.remove(os.path.join(tmp, 'pycoast_cache_fakearea.png'))
+        cw.add_overlay_from_dict(overlays, area_def)
+        os.remove(cache_filename)
 
-    def test_get_resolution(self):
-        """Get the automagical resolution computation."""
-        from pycoast import get_resolution_from_area
+    def test_caching_with_param_changes(self, tmpdir):
+        """Testing caching when changing parameters."""
+        from pycoast import ContourWriterPIL
+
+        # img = Image.new('RGB', (640, 480))
         proj4_string = \
             '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
         area_extent = (-3363403.31, -2291879.85, 2630596.69, 2203620.1)
         area_def = FakeAreaDef(proj4_string, area_extent, 640, 480)
-        self.assertEqual(get_resolution_from_area(area_def), 'l')
-        area_def = FakeAreaDef(proj4_string, area_extent, 6400, 4800)
-        self.assertEqual(get_resolution_from_area(area_def), 'h')
-
+        cw = ContourWriterPIL(gshhs_root_dir)
 
-def suite():
-    loader = unittest.TestLoader()
-    mysuite = unittest.TestSuite()
-    mysuite.addTest(loader.loadTestsFromTestCase(TestPIL))
-    mysuite.addTest(loader.loadTestsFromTestCase(TestPILAGG))
-    mysuite.addTest(loader.loadTestsFromTestCase(TestFromConfig))
+        font = ImageFont.truetype(os.path.join(
+            os.path.dirname(__file__), 'test_data', 'DejaVuSerif.ttf'))
+        overlays = {'cache': {'file': os.path.join(tmpdir, 'pycoast_cache')},
+                    'grid': {'font': font}}
 
-    return mysuite
+        # Create the original cache file
+        cw.add_overlay_from_dict(overlays, area_def)
+        cache_glob = glob(os.path.join(tmpdir, 'pycoast_cache_*.png'))
+        assert len(cache_glob) == 1
+        cache_filename = cache_glob[0]
+        assert os.path.isfile(cache_filename)
+        mtime = os.path.getmtime(cache_filename)
 
+        # Reuse the generated cache file
+        cw.add_overlay_from_dict(overlays, area_def)
+        cache_glob = glob(os.path.join(tmpdir, 'pycoast_cache_*.png'))
+        assert len(cache_glob) == 1
+        assert os.path.isfile(cache_filename)
+        assert os.path.getmtime(cache_filename) == mtime
+
+        # Remove the font option, should produce the same result
+        # font is not considered when caching
+        del overlays['grid']['font']
+        cw.add_overlay_from_dict(overlays, area_def)
+        cache_glob = glob(os.path.join(tmpdir, 'pycoast_cache_*.png'))
+        assert len(cache_glob) == 1
+        assert os.path.isfile(cache_filename)
+        assert os.path.getmtime(cache_filename) == mtime
+
+        # Changing a parameter should create a new cache file
+        overlays = {'cache': {'file': os.path.join(tmpdir, 'pycoast_cache')},
+                    'grid': {'width': 2.0}}
+        cw.add_overlay_from_dict(overlays, area_def)
+        cache_glob = glob(os.path.join(tmpdir, 'pycoast_cache_*.png'))
+        assert len(cache_glob) == 2
+        assert os.path.isfile(cache_filename)
+        new_cache_filename = cache_glob[0] if cache_glob[0] != cache_filename else cache_glob[1]
+        # original cache file should be unchanged
+        assert os.path.getmtime(cache_filename) == mtime
+        # new cache file should be...new
+        assert os.path.getmtime(new_cache_filename) != mtime
 
-if __name__ == "__main__":
-    suite()
+    def test_get_resolution(self):
+        """Get the automagical resolution computation."""
+        from pycoast import get_resolution_from_area
+        proj4_string = \
+            '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
+        area_extent = (-3363403.31, -2291879.85, 2630596.69, 2203620.1)
+        area_def = FakeAreaDef(proj4_string, area_extent, 640, 480)
+        assert get_resolution_from_area(area_def) == 'l'
+        area_def = FakeAreaDef(proj4_string, area_extent, 6400, 4800)
+        assert get_resolution_from_area(area_def) == 'h'


=====================================
pycoast/version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (tag: v1.4.0)"
-    git_full = "c96f05892af2bdc7148dbfdea7930385ca39aa1e"
-    git_date = "2020-06-08 14:34:54 -0500"
+    git_refnames = " (HEAD -> main, tag: v1.5.0)"
+    git_full = "42ba82e32251fe48d62af1ebf0ad81d0577d74e4"
+    git_date = "2021-08-18 09:01:22 -0500"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
setup.cfg
=====================================
@@ -5,6 +5,9 @@ doc_files = docs/Makefile docs/source/*.rst
 
 [flake8]
 max-line-length = 120
+ignore = D107,D102
+exclude =
+    pycoast/version.py
 
 [versioneer]
 VCS = git


=====================================
setup.py
=====================================
@@ -18,9 +18,12 @@
 from setuptools import setup
 import versioneer
 
-requires = ['aggdraw', 'pyshp', 'numpy', 'pyproj', 'pillow', 'six']
-tests_require = ['aggdraw', 'pyshp', 'numpy', 'pyproj', 'pillow', 'six',
-                 'pyresample']
+requires = ['aggdraw', 'pyshp', 'numpy', 'pyproj', 'pillow']
+
+extras_require = {
+    "docs": ["sphinx", 'pyresample', 'pytest'],
+    "tests": ['pyresample', 'pytest', 'pytest-cov', 'coverage', 'coveralls'],
+}
 
 with open('README', 'r') as readme_file:
     long_description = readme_file.read()
@@ -34,8 +37,8 @@ setup(name='pycoast',
       author_email='esn at dmi.dk',
       packages=['pycoast', 'pycoast.tests'],
       install_requires=requires,
-      tests_require=tests_require,
-      test_suite='pycoast.tests.test_pycoast.suite',
+      extras_require=extras_require,
+      python_requires='>3.7',
       zip_safe=False,
       classifiers=[
           'Development Status :: 5 - Production/Stable',



View it on GitLab: https://salsa.debian.org/debian-gis-team/pycoast/-/compare/27161b5282ad876a999337e9ba1e7f4314b3b93d...0617d6c1ac0b13a6ada1a19ff1e2a3da0c19ea6d

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pycoast/-/compare/27161b5282ad876a999337e9ba1e7f4314b3b93d...0617d6c1ac0b13a6ada1a19ff1e2a3da0c19ea6d
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210829/0f033d2d/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list