diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml index b464b69..4585084 100644 --- a/.github/workflows/build-and-release.yaml +++ b/.github/workflows/build-and-release.yaml @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -83,8 +83,8 @@ jobs: if: "! contains(github.ref_name,'b') && matrix.os == 'ubuntu-20.04' && matrix.python-version == '3.8'" run: | source .venv/bin/activate - coverage html -d dist/tests/ - rm dist/tests/.gitignore + coverage html -d target/docs/tests/ + rm target/docs/tests/.gitignore - name: Build package if: "matrix.os == 'ubuntu-20.04' && matrix.python-version == '3.8'" @@ -97,18 +97,38 @@ jobs: run: | source .venv/bin/activate pip install -e .[doc] - pdoc3 --html --output-dir dist/ rok4 - cp README.md CHANGELOG.md dist/ - - - name: Upload packages, tests results and documentation + pdoc3 --html --output-dir target/docs/ rok4 + cp docs/mkdocs.yml target/mkdocs.yml + cp -r docs/overrides target/ + cp docs/unit-tests.md target/docs/unit-tests.md + cp docs/documentation.md target/docs/documentation.md + cp docs/README.hdr.md target/docs/README.md + cp docs/CHANGELOG.hdr.md target/docs/CHANGELOG.md + cp docs/CONTRIBUTING.hdr.md target/docs/CONTRIBUTING.md + sed "s#x.y.z#${{ github.ref_name }}#g" README.md >>target/docs/README.md + cat CHANGELOG.md >>target/docs/CHANGELOG.md + cat CONTRIBUTING.md >>target/docs/CONTRIBUTING.md + + - name: Upload packages if: "matrix.os == 'ubuntu-20.04' && matrix.python-version == '3.8'" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: dist-py3 path: dist/ if-no-files-found: error retention-days: 1 + - name: Publish documentation + if: "! contains(github.ref_name,'b') && matrix.os == 'ubuntu-20.04' && matrix.python-version == '3.8'" + run: | + source .venv/bin/activate + pip install -r docs/requirements.txt + git config user.name github-actions + git config user.email github-actions@github.com + cd target/ + mike deploy --push --update-aliases --branch gh-pages -t "Version ${{ github.ref_name }}" ${{ github.ref_name }} latest + mike set-default --push --branch gh-pages ${{ github.ref_name }} + publish_artefacts: name: Add built artefacts to release and PyPI needs: [create_release, build_and_test] @@ -117,7 +137,7 @@ jobs: steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: dist-py3 path: dist/ @@ -155,56 +175,6 @@ jobs: password: ${{ secrets.PYPI_API_TOKEN }} packages_dir: to_publish/ - commit_documentation: - name: Add documentation and unit tests results into gh-pages branch - needs: build_and_test - if: "always() && ! contains(github.ref_name,'b') && needs.create_release.outputs.job_status == 'success' && needs.build_and_test.outputs.job_status == 'success'" - runs-on: ubuntu-latest - - steps: - - - name: Checkout project on gh-pages - uses: actions/checkout@v4 - with: - ref: 'gh-pages' - token: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/download-artifact@v3 - with: - name: dist-py3 - path: artifact/ - - - name: Copy version elements to final location - run: | - mkdir -p docs/versions/${{ github.ref_name }} - cp -r artifact/rok4 docs/versions/${{ github.ref_name }}/ - cp -r artifact/tests docs/versions/${{ github.ref_name }}/ - - - name: Add pages from templates - run: | - sed "s#__version__#${{ github.ref_name }}#" templates/mkdocs.template.yml >mkdocs.yml - - sed "s#__version__#${{ github.ref_name }}#" templates/unit-tests.template.md >docs/versions/${{ github.ref_name }}/unit-tests.md - sed "s#__version__#${{ github.ref_name }}#" templates/documentation.template.md >docs/versions/${{ github.ref_name }}/documentation.md - - sed "s#__version__#${{ github.ref_name }}#" templates/index-version.template.md >docs/versions/${{ github.ref_name }}/index.md - cat artifact/README.md >>docs/versions/${{ github.ref_name }}/index.md - sed -i "s#x.y.z#${{ github.ref_name }}#g" docs/versions/${{ github.ref_name }}/index.md - - cp templates/index-versions.template.md docs/versions/index.md - sed "s/^## \(.*\)$/## \1 \n\n[➔ Lien vers la documentation](\1\/index.md) /" artifact/CHANGELOG.md >>docs/versions/index.md - - sed "s#__version__#${{ github.ref_name }}#" templates/latest.template.html >docs/versions/latest/index.html - rm -r artifact - - - name: Publish on gh-pages branch - run: | - git config user.name github-actions - git config user.email github-actions@github.com - git add -v docs/versions/${{ github.ref_name }}/ docs/versions/latest/ docs/versions/index.md mkdocs.yml - git commit -m "Add documentation for version ${{ github.ref_name }}" - git push - delete_version: name: Remove release and tag if error occured needs: build_and_test diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml deleted file mode 100644 index 53fd700..0000000 --- a/.github/workflows/build-docs.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Build doc site with mkdocs and deploy - -on: - workflow_dispatch: - -jobs: - - build: - - name: Build doc site with mkdocs - - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@v4 - with: - ref: 'gh-pages' - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: 'pip' - - - name: Install Mkdocs - run: pip install -r requirements.txt - - - name: Run mkdocs and archive - run : | - mkdocs build - tar \ - --dereference --hard-dereference \ - --directory "site" \ - -cvf "${{ runner.temp }}/site.tar" \ - --exclude=.git \ - --exclude=.github \ - . - - - name: Upload pages - uses: actions/upload-artifact@main - with: - name: github-pages - path: ${{ runner.temp }}/site.tar - retention-days: 1 - - deploy: - - name: Deploy site to github pages - - needs: build - - permissions: - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source - - # Deploy to the github-pages environment - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 diff --git a/.github/workflows/pr-auto-labeler.yml b/.github/workflows/pr-auto-labeler.yml index 42ec571..a4c9c73 100644 --- a/.github/workflows/pr-auto-labeler.yml +++ b/.github/workflows/pr-auto-labeler.yml @@ -10,6 +10,6 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e31a0..f656d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.1.0 + +### [Added] + +* Pyramid + * Ajout de fonctions pour récupérer la tile_limits et le nombre de canaux de cette pyramide + * Ajout de fonctions pour ajouter ou supprimer des niveaux dans une pyramide +* TileMatrixSet + * Ajout de fonctions pour récupérer la hauteur et la largeur de tuiles d'un TileMatrixSet + +### [Changed] + +* Pyramid + * Ajout d'un paramètre optionnel "mask" pour le constructeur from other afin de pouvoir conserver ou non les masques de la pyramide servant de base à la nouvellle +* Gestion des documentations des différentes versions avec l'outil [mike](https://github.com/jimporter/mike) + ## 2.0.1 ### [Added] diff --git a/README.md b/README.md index 2117b06..bad3a97 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,14 @@ except Exception as exc: Plus d'exemple dans la documentation développeur. + ## Contribuer * Installer les dépendances de développement : ```sh python3 -m pip install -e .[dev] + pre-commit install ``` * Consulter les [directives de contribution](./CONTRIBUTING.md) @@ -46,7 +48,7 @@ apt install python3-venv python3-rados python3-gdal python3 -m venv .venv source .venv/bin/activate python3 -m pip install --upgrade build bump2version -bump2version --allow-dirty --current-version 0.0.0 --new-version x.y.z patch pyproject.toml src/rok4/__init__.py +bump2version --current-version 0.0.0 --new-version x.y.z patch # Run unit tests python3 -m pip install -e .[test] diff --git a/docs/CHANGELOG.hdr.md b/docs/CHANGELOG.hdr.md new file mode 100644 index 0000000..e0257d2 --- /dev/null +++ b/docs/CHANGELOG.hdr.md @@ -0,0 +1,4 @@ +--- +hide: + - navigation +--- diff --git a/docs/CONTRIBUTING.hdr.md b/docs/CONTRIBUTING.hdr.md new file mode 100644 index 0000000..e0257d2 --- /dev/null +++ b/docs/CONTRIBUTING.hdr.md @@ -0,0 +1,4 @@ +--- +hide: + - navigation +--- diff --git a/docs/README.hdr.md b/docs/README.hdr.md new file mode 100644 index 0000000..e0257d2 --- /dev/null +++ b/docs/README.hdr.md @@ -0,0 +1,4 @@ +--- +hide: + - navigation +--- diff --git a/docs/documentation.md b/docs/documentation.md new file mode 100644 index 0000000..59f441e --- /dev/null +++ b/docs/documentation.md @@ -0,0 +1,9 @@ +--- +hide: + - navigation + - toc +--- + +# Documentation technique + + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..a173ee6 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,28 @@ +site_name: "Projet ROK4 - Librairies python" +site_url: https://rok4.github.io/core-python + +nav: + - Accueil: README.md + - Documentation technique: documentation.md + - Rapport des tests unitaires: unit-tests.md + - Historique des versions: CHANGELOG.md + - Contribuer: CONTRIBUTING.md + +theme: + logo: https://rok4.github.io/assets/images/rok4-carre.png + favicon: https://rok4.github.io/assets/images/rok4-carre.png + name: material + features: + - navigation.tabs + - navigation.tabs.sticky + language: fr + custom_dir: overrides + +extra_css: + - https://rok4.github.io/assets/css/commun.css + +extra: + homepage: https://rok4.github.io + version: + provider: mike + default: latest diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 0000000..7c55859 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block outdated %} + 🚨 Vous êtes sur la documentation d'une ancienne version. 🚨 + + Cliquez ici pour aller sur la dernière version. + +{% endblock %} diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..f5f7010 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +mkdocs +mkdocs-material +mike diff --git a/docs/unit-tests.md b/docs/unit-tests.md new file mode 100644 index 0000000..091c6bd --- /dev/null +++ b/docs/unit-tests.md @@ -0,0 +1,9 @@ +--- +hide: + - navigation + - toc +--- + +# Rapport des tests unitaires + + diff --git a/pyproject.toml b/pyproject.toml index 9731107..3e9fcf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ test = [ [project.urls] "Homepage" = "https://rok4.github.io/core-python" "Bug Reports" = "https://github.com/rok4/core-python/issues" -"Changelog" = "https://rok4.github.io/core-python/versions/" +"Changelog" = "https://rok4.github.io/core-python/latest/CHANGELOG/" "Source" = "https://github.com/rok4/core-python" [build-system] diff --git a/src/rok4/pyramid.py b/src/rok4/pyramid.py index f55433f..09ea0e4 100644 --- a/src/rok4/pyramid.py +++ b/src/rok4/pyramid.py @@ -331,6 +331,10 @@ def slab_width(self) -> int: def slab_height(self) -> int: return self.__slab_size[1] + @property + def tile_limits(self) -> Dict[str, int]: + return self.__tile_limits + def is_in_limits(self, column: int, row: int) -> bool: """Is the tile indices in limits ? @@ -475,13 +479,14 @@ def from_descriptor(cls, descriptor: str) -> "Pyramid": return pyramid @classmethod - def from_other(cls, other: "Pyramid", name: str, storage: Dict) -> "Pyramid": + def from_other(cls, other: "Pyramid", name: str, storage: Dict, **kwargs) -> "Pyramid": """Create a pyramid from another one Args: other (Pyramid): pyramid to clone name (str): new pyramid's name storage (Dict[str, Union[str, int]]): new pyramid's storage informations + **mask (bool) : Presence or not of mask (only for RASTER) Raises: FormatError: Provided path or the TMS is not a well formed JSON @@ -493,7 +498,8 @@ def from_other(cls, other: "Pyramid", name: str, storage: Dict) -> "Pyramid": """ try: # On convertit le type de stockage selon l'énumération - storage["type"] = StorageType[storage["type"]] + if type(storage["type"]) is str: + storage["type"] = StorageType[storage["type"]] if storage["type"] == StorageType.FILE and name.find("/") != -1: raise Exception(f"A FILE stored pyramid's name cannot contain '/' : '{name}'") @@ -519,7 +525,9 @@ def from_other(cls, other: "Pyramid", name: str, storage: Dict) -> "Pyramid": # Attributs d'une pyramide raster if pyramid.type == PyramidType.RASTER: - if other.own_masks: + if "mask" in kwargs: + pyramid.__masks = kwargs["mask"] + elif other.own_masks: pyramid.__masks = True else: pyramid.__masks = False @@ -668,6 +676,10 @@ def own_masks(self) -> bool: def format(self) -> str: return self.__format + @property + def channels(self) -> str: + return self.raster_specifications["channels"] + @property def tile_extension(self) -> str: if self.__format in [ @@ -735,10 +747,14 @@ def load_list(self) -> None: self.__content["loaded"] = True - def list_generator(self) -> Iterator[Tuple[Tuple[SlabType, str, int, int], Dict]]: + def list_generator( + self, level_id: str = None + ) -> Iterator[Tuple[Tuple[SlabType, str, int, int], Dict]]: """Get list content List is copied as temporary file, roots are read and informations about each slab is returned. If list is already loaded, we yield the cached content + Args : + level_id (str) : id of the level for load only one level Examples: @@ -776,7 +792,11 @@ def list_generator(self) -> Iterator[Tuple[Tuple[SlabType, str, int, int], Dict] """ if self.__content["loaded"]: for slab, infos in self.__content["cache"].items(): - yield slab, infos + if level_id is not None: + if slab[1] == level_id: + yield slab, infos + else: + yield slab, infos else: # Copie de la liste dans un fichier temporaire (cette liste peut être un objet) list_obj = tempfile.NamedTemporaryFile(mode="r", delete=False) @@ -824,7 +844,11 @@ def list_generator(self) -> Iterator[Tuple[Tuple[SlabType, str, int, int], Dict] "md5": slab_md5, } - yield ((slab_type, level, column, row), infos) + if level_id is not None: + if level == level_id: + yield ((slab_type, level, column, row), infos) + else: + yield ((slab_type, level, column, row), infos) remove(f"file://{list_file}") @@ -1372,6 +1396,58 @@ def get_tile_indices( return (level_object.id,) + level_object.tile_matrix.point_to_indices(x, y) + def delete_level(self, level_id: str) -> None: + """Delete the given level in the description of the pyramid + + Args: + level_id: Level identifier + + Raises: + Exception: Cannot find level + """ + + try: + del self.__levels[level_id] + except Exception: + raise Exception(f"The level {level_id} don't exist in the pyramid") + + def add_level( + self, + level_id: str, + tiles_per_width: int, + tiles_per_height: int, + tile_limits: Dict[str, int], + ) -> None: + """Add a level in the description of the pyramid + + Args: + level_id: Level identifier + tiles_per_width : Number of tiles in width by slab + tiles_per_height : Number of tiles in height by slab + tile_limits : Minimum and maximum tiles' columns and rows of pyramid's content + """ + + data = { + "id": level_id, + "tile_limits": tile_limits, + "tiles_per_width": tiles_per_width, + "tiles_per_height": tiles_per_height, + "storage": {"type": self.storage_type.name}, + } + if self.own_masks: + data["storage"]["mask_prefix"] = True + if self.storage_type == StorageType.FILE: + data["storage"]["path_depth"] = self.storage_depth + + lev = Level.from_descriptor(data, self) + + if self.__tms.get_level(lev.id) is None: + raise Exception( + f"Pyramid {self.name} owns a level with the ID '{lev.id}', not defined in the TMS '{self.tms.name}'" + ) + else: + self.__levels[lev.id] = lev + @property def size(self) -> int: """Get the size of the pyramid diff --git a/src/rok4/storage.py b/src/rok4/storage.py index a3c6975..1cf0f6f 100644 --- a/src/rok4/storage.py +++ b/src/rok4/storage.py @@ -11,9 +11,9 @@ Readings uses a LRU cache system with a TTL. It's possible to configure it with environment variables : - ROK4_READING_LRU_CACHE_SIZE : Number of cached element. Default 64. Set 0 or a negative integer to configure a cache without bound. A power of two make cache more efficient. -- ROK4_READING_LRU_CACHE_TTL : Validity duration of cached element, in seconds. Default 300. 0 or negative integer to disable time validity. +- ROK4_READING_LRU_CACHE_TTL : Validity duration of cached element, in seconds. Default 300. 0 or negative integer to get cache without expiration date. -To disable cache, set ROK4_READING_LRU_CACHE_SIZE to 1 and ROK4_READING_LRU_CACHE_TTL to 0. +To disable cache (always read data on storage), set ROK4_READING_LRU_CACHE_SIZE to 1 and ROK4_READING_LRU_CACHE_TTL to 1. Using CEPH storage requires environment variables : - ROK4_CEPH_CONFFILE @@ -24,7 +24,6 @@ - ROK4_S3_KEY - ROK4_S3_SECRETKEY - ROK4_S3_URL -- ROK4_SSL_NO_VERIFY (optionnal) with a non empty value disables certificate check.. Define PYTHONWARNINGS to "ignore:Unverified HTTPS request" to disable warnings logs To use several S3 clusters, each environment variable have to contain a list (comma-separated), with the same number of elements @@ -74,7 +73,6 @@ __OBJECT_SYMLINK_SIGNATURE = "SYMLINK#" __S3_CLIENTS = {} __S3_DEFAULT_CLIENT = None - __LRU_SIZE = 64 __LRU_TTL = 300 @@ -97,10 +95,10 @@ pass -def __get_ttl_hash(): +def __get_ttl_hash() -> int: """Return the time string rounded according to time-to-live value""" if __LRU_TTL == 0: - return time.time() + return 0 else: return round(time.time() / __LRU_TTL) @@ -127,7 +125,6 @@ def __get_s3_client(bucket_name: str) -> Tuple[Dict[str, Union["boto3.client", s verify = True if "ROK4_SSL_NO_VERIFY" in os.environ and os.environ["ROK4_SSL_NO_VERIFY"] != "": verify = False - # C'est la première fois qu'on cherche à utiliser le stockage S3, chargeons les informations depuis les variables d'environnement try: keys = os.environ["ROK4_S3_KEY"].split(",") @@ -1033,6 +1030,12 @@ def link(target_path: str, link_path: str, hard: bool = False) -> None: elif target_type == StorageType.FILE: try: + to_tray = get_infos_from_path(link_path)[2] + if to_tray != "": + os.makedirs(to_tray, exist_ok=True) + + if exists(link_path): + remove(link_path) if hard: os.link(target_path, link_path) else: @@ -1081,7 +1084,7 @@ def get_osgeo_path(path: str) -> str: def size_path(path: str) -> int: - """Return the size of the path given (or, for the CEPH, the sum of the size of each object of the .list) + """Return the size of the given path (or, for the CEPH, the sum of the size of each object of the .list) Args: path (str): Source path diff --git a/src/rok4/tile_matrix_set.py b/src/rok4/tile_matrix_set.py index 84a3d22..a51d717 100644 --- a/src/rok4/tile_matrix_set.py +++ b/src/rok4/tile_matrix_set.py @@ -177,6 +177,14 @@ def point_to_indices(self, x: float, y: float) -> Tuple[int, int, int, int]: absolute_pixel_row % self.tile_size[1], ) + @property + def tile_width(self) -> int: + return self.tile_size[0] + + @property + def tile_heigth(self) -> int: + return self.tile_size[1] + class TileMatrixSet: """A tile matrix set is multi levels grid definition diff --git a/src/rok4/utils.py b/src/rok4/utils.py index 40c7884..050d679 100644 --- a/src/rok4/utils.py +++ b/src/rok4/utils.py @@ -1,5 +1,6 @@ """Provide functions to manipulate OGR / OSR entities """ + # -- IMPORTS -- # standard library diff --git a/tests/test_pyramid.py b/tests/test_pyramid.py index f44eed7..007ef45 100644 --- a/tests/test_pyramid.py +++ b/tests/test_pyramid.py @@ -1,13 +1,13 @@ import os from unittest import mock -from unittest.mock import * +from unittest.mock import MagicMock import pytest from rok4.enums import SlabType, StorageType -from rok4.exceptions import * -from rok4.pyramid import * -from rok4.utils import * +from rok4.exceptions import FormatError, MissingAttributeError, StorageError +from rok4.pyramid import Pyramid, b36_path_decode, b36_path_encode +from rok4.utils import srs_to_spatialreference @mock.patch("rok4.pyramid.get_data_str", side_effect=StorageError("FILE", "Not found")) diff --git a/tests/test_storage.py b/tests/test_storage.py index f5c4889..dd7dddd 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -1,13 +1,30 @@ import os from unittest import mock -from unittest.mock import * +from unittest.mock import MagicMock, mock_open, patch import botocore.exceptions import pytest from rok4.enums import StorageType -from rok4.exceptions import * -from rok4.storage import * +from rok4.exceptions import MissingEnvironmentError, StorageError +from rok4.storage import ( + copy, + disconnect_ceph_clients, + disconnect_s3_clients, + exists, + get_data_binary, + get_data_str, + get_infos_from_path, + get_osgeo_path, + get_path_from_infos, + get_size, + hash_file, + link, + put_data_str, + rados, + remove, + size_path, +) @mock.patch.dict(os.environ, {}, clear=True) @@ -48,7 +65,7 @@ def test_get_path_from_infos(): assert get_path_from_infos(StorageType.CEPH, "toto", "titi/tutu") == "ceph://toto/titi/tutu" -############ get_data_str +# -- get_data_str @mock.patch.dict(os.environ, {}, clear=True) @@ -196,7 +213,7 @@ def test_http_read_ok(mock_http): assert False, f"HTTP read raises an exception: {exc}" -############ put_data_str +# -- put_data_str @mock.patch.dict( @@ -252,7 +269,7 @@ def test_ceph_write_ok(mocked_rados_client): assert False, f"CEPH write raises an exception: {exc}" -############ copy +# -- copy @mock.patch.dict(os.environ, {}, clear=True) @@ -571,7 +588,7 @@ def test_copy_http_s3_ok(mock_remove, mock_tempfile, mock_requests, mocked_s3_cl assert False, f"HTTP -> CEPH copy raises an exception: {exc}" -############ link +# -- link def test_link_type_nok(): @@ -586,9 +603,11 @@ def test_link_hard_nok(): @mock.patch.dict(os.environ, {}, clear=True) @mock.patch("os.symlink", return_value=None) -def test_link_file_ok(mock_link): +@mock.patch("os.makedirs", return_value=None) +def test_link_file_ok(mock_makedirs, mock_link): try: link("file:///path/to/target.ext", "file:///path/to/link.ext") + mock_makedirs.assert_called_once_with("/path/to", exist_ok=True) mock_link.assert_called_once_with("/path/to/target.ext", "/path/to/link.ext") except Exception as exc: assert False, f"FILE link raises an exception: {exc}" @@ -596,9 +615,11 @@ def test_link_file_ok(mock_link): @mock.patch.dict(os.environ, {}, clear=True) @mock.patch("os.link", return_value=None) -def test_hlink_file_ok(mock_link): +@mock.patch("os.makedirs", return_value=None) +def test_hlink_file_ok(mock_makedirs, mock_link): try: link("file:///path/to/target.ext", "file:///path/to/link.ext", True) + mock_makedirs.assert_called_once_with("/path/to", exist_ok=True) mock_link.assert_called_once_with("/path/to/target.ext", "/path/to/link.ext") except Exception as exc: assert False, f"FILE hard link raises an exception: {exc}" @@ -658,7 +679,7 @@ def test_link_s3_nok(mocked_s3_client): link("s3://bucket1@a/target.ext", "s3://bucket2@b/link.ext") -############ get_size +# -- get_size @mock.patch.dict(os.environ, {}, clear=True) @@ -726,7 +747,7 @@ def test_size_http_ok(mock_requests): assert False, f"HTTP size raises an exception: {exc}" -############ exists +# --exists @mock.patch.dict(os.environ, {}, clear=True) @@ -817,7 +838,7 @@ def test_exists_http_ok(mock_requests): assert False, f"HTTP exists raises an exception: {exc}" -############ remove +# -- remove @mock.patch.dict(os.environ, {}, clear=True) @@ -880,7 +901,7 @@ def test_remove_s3_ok(mocked_s3_client): assert False, f"S3 deletion raises an exception: {exc}" -############ get_osgeo_path +# -- get_osgeo_path @mock.patch.dict(os.environ, {}, clear=True) @@ -913,7 +934,7 @@ def test_get_osgeo_path_nok(): get_osgeo_path("ceph://pool/data.ext") -############ size_path +# -- size_path def test_size_path_file_ok(): try: size = size_path("file://tests/fixtures/TIFF_PBF_MVT") diff --git a/tests/test_tile_matrix_set.py b/tests/test_tile_matrix_set.py index 94f89f8..4209daa 100644 --- a/tests/test_tile_matrix_set.py +++ b/tests/test_tile_matrix_set.py @@ -4,7 +4,12 @@ import pytest -from rok4.exceptions import * +from rok4.exceptions import ( + FormatError, + MissingAttributeError, + MissingEnvironmentError, + StorageError, +) from rok4.tile_matrix_set import TileMatrixSet diff --git a/tests/test_utils.py b/tests/test_utils.py index 584e34d..696a3d4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,13 +1,20 @@ import math import random -from unittest import mock -from unittest.mock import * +from unittest.mock import MagicMock, Mock, patch import pytest from osgeo import gdal, osr -from rok4.exceptions import * -from rok4.utils import * +# from rok4.exceptions import * +from rok4.utils import ( + ColorFormat, + bbox_to_geometry, + compute_bbox, + compute_format, + reproject_bbox, + reproject_point, + srs_to_spatialreference, +) def test_srs_to_spatialreference_ignf_ok(): @@ -217,10 +224,10 @@ def test_compute_bbox_no_srs_ok(): # Tests for the rok4.utils.compute_format function. -@mock.patch("rok4.utils.gdal.Info") -@mock.patch("rok4.utils.gdal.GetColorInterpretationName", return_value="Palette") -@mock.patch("rok4.utils.gdal.GetDataTypeSize", return_value=8) -@mock.patch("rok4.utils.gdal.GetDataTypeName", return_value="Byte") +@patch("rok4.utils.gdal.Info") +@patch("rok4.utils.gdal.GetColorInterpretationName", return_value="Palette") +@patch("rok4.utils.gdal.GetDataTypeSize", return_value=8) +@patch("rok4.utils.gdal.GetDataTypeName", return_value="Byte") def test_compute_format_bit_ok( mocked_GetDataTypeName, mocked_GetDataTypeSize, mocked_GetColorInterpretationName, mocked_Info ): @@ -246,10 +253,10 @@ def test_compute_format_bit_ok( assert False, f"Color format computation raises an exception: {exc}" -@mock.patch("rok4.utils.gdal.Info") -@mock.patch("rok4.utils.gdal.GetColorInterpretationName") -@mock.patch("rok4.utils.gdal.GetDataTypeSize", return_value=8) -@mock.patch("rok4.utils.gdal.GetDataTypeName", return_value="Byte") +@patch("rok4.utils.gdal.Info") +@patch("rok4.utils.gdal.GetColorInterpretationName") +@patch("rok4.utils.gdal.GetDataTypeSize", return_value=8) +@patch("rok4.utils.gdal.GetDataTypeName", return_value="Byte") def test_compute_format_uint8_ok( mocked_GetDataTypeName, mocked_GetDataTypeSize, mocked_GetColorInterpretationName, mocked_Info ): @@ -283,10 +290,10 @@ def test_compute_format_uint8_ok( assert False, f"Color format computation raises an exception: {exc}" -@mock.patch("rok4.utils.gdal.Info") -@mock.patch("rok4.utils.gdal.GetColorInterpretationName") -@mock.patch("rok4.utils.gdal.GetDataTypeSize", return_value=32) -@mock.patch("rok4.utils.gdal.GetDataTypeName", return_value="Float32") +@patch("rok4.utils.gdal.Info") +@patch("rok4.utils.gdal.GetColorInterpretationName") +@patch("rok4.utils.gdal.GetDataTypeSize", return_value=32) +@patch("rok4.utils.gdal.GetDataTypeName", return_value="Float32") def test_compute_format_float32_ok( mocked_GetDataTypeName, mocked_GetDataTypeSize, mocked_GetColorInterpretationName, mocked_Info ): @@ -320,10 +327,10 @@ def test_compute_format_float32_ok( assert False, f"Color format computation raises an exception: {exc}" -@mock.patch("rok4.utils.gdal.Info") -@mock.patch("rok4.utils.gdal.GetColorInterpretationName") -@mock.patch("rok4.utils.gdal.GetDataTypeSize", return_value=16) -@mock.patch("rok4.utils.gdal.GetDataTypeName", return_value="UInt16") +@patch("rok4.utils.gdal.Info") +@patch("rok4.utils.gdal.GetColorInterpretationName") +@patch("rok4.utils.gdal.GetDataTypeSize", return_value=16) +@patch("rok4.utils.gdal.GetDataTypeName", return_value="UInt16") def test_compute_format_unsupported_nok( mocked_GetDataTypeName, mocked_GetDataTypeSize, mocked_GetColorInterpretationName, mocked_Info ): @@ -357,10 +364,10 @@ def test_compute_format_unsupported_nok( assert False, f"Color format computation raises an exception: {exc}" -@mock.patch("rok4.utils.gdal.Info") -@mock.patch("rok4.utils.gdal.GetColorInterpretationName") -@mock.patch("rok4.utils.gdal.GetDataTypeSize", return_value=16) -@mock.patch("rok4.utils.gdal.GetDataTypeName", return_value="UInt16") +@patch("rok4.utils.gdal.Info") +@patch("rok4.utils.gdal.GetColorInterpretationName") +@patch("rok4.utils.gdal.GetDataTypeSize", return_value=16) +@patch("rok4.utils.gdal.GetDataTypeName", return_value="UInt16") def test_compute_format_no_band_nok( mocked_GetDataTypeName, mocked_GetDataTypeSize, mocked_GetColorInterpretationName, mocked_Info ): diff --git a/tests/test_vector.py b/tests/test_vector.py index 72e9c50..cac0048 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -1,12 +1,14 @@ +# standard library import os from unittest import mock -from unittest.mock import * +# 3rd party import pytest -from rok4.exceptions import * +# package +from rok4.exceptions import MissingEnvironmentError, StorageError from rok4.storage import disconnect_ceph_clients -from rok4.vector import * +from rok4.vector import Vector @mock.patch.dict(os.environ, {}, clear=True)