Repository: EOA-team/eodal (language: python)

Issue:
Retry when STAC query fails
Due to some problems at Microsoft Planetary Computer (see [here](https://github.com/microsoft/PlanetaryComputer/discussions/222)) the REST-API calls to STAC using the pystac-client should be made more robust to avoid HTTPS errors when querying the STAC catalog.

As proposed [here](https://github.com/stac-utils/pystac-client/issues/520) we will add the Retry operator to the HTTP Adapter when working with pystac-client.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index b2e8064c..16527dab 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -16,13 +16,13 @@ jobs:
   docs-html:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Check if README.md needs to be converted
       id: check_readme
       run: |
         if [ ! -f "README.rst" ] && [ -f "README.md" ]; then
-            echo '::set-output name=convert::true';
+            echo "convert=true" >> $GITHUB_OUTPUT
         fi
 
     - name: Convert README.md to README.rst
diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
index 3031cc6d..c65d0ea1 100644
--- a/.github/workflows/pre-commit.yaml
+++ b/.github/workflows/pre-commit.yaml
@@ -12,6 +12,10 @@ jobs:
   pre-commit:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
-    - uses: pre-commit/action@v2.0.3
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v4
+      with:
+        python-version: "3.10"
+        cache: pip
+    - uses: pre-commit/action@v3.0.0
+  
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d00ae741..e72835ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -238,3 +238,6 @@ Temporary Items
 .apdisk
 
 eodal.code-workspace
+.vscode/extensions.json
+
+*.xml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0c69340a..80f2534d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/PyCQA/isort
-    rev: 5.10.1
+    rev: 5.12.0
     hooks:
       - id: isort
         name: isort
@@ -11,37 +11,45 @@ repos:
         args: ["--profile=black"]
 
   - repo: https://github.com/psf/black
-    rev: 22.3.0
+    rev: 23.1.0
     hooks:
       - id: black
         args:
+            - "--target-version=py37"
+            - "--target-version=py38"
+            - "--target-version=py39"
             - "--target-version=py310"
+            - "--target-version=py311"
         types: [python]
 
   - repo: https://github.com/asottile/blacken-docs
-    rev: v1.12.1
+    rev: 1.13.0
     hooks:
       - id: blacken-docs
-        additional_dependencies: ["black==22.3.0"]
+        additional_dependencies: ["black==23.1.0"]
 
   - repo: https://github.com/kynan/nbstripout
-    rev: 0.5.0
+    rev: 0.6.1
     hooks:
       - id: nbstripout
 
   - repo: https://github.com/nbQA-dev/nbQA
-    rev: 1.3.1
+    rev: 1.6.4
     hooks:
       - id: nbqa-isort
         args: ["--profile=black"]
       - id: nbqa-black
         args:
+            - "--target-version=py37"
+            - "--target-version=py38"
+            - "--target-version=py39"
             - "--target-version=py310"
-        additional_dependencies: ["black==22.3.0"]
+            - "--target-version=py311"
+        additional_dependencies: ["black==23.1.0"]
       - id: nbqa-flake8
 
   - repo: https://github.com/pre-commit/pygrep-hooks
-    rev: v1.9.0
+    rev: v1.10.0
     hooks:
       - id: python-check-blanket-noqa
       - id: python-check-blanket-type-ignore
@@ -54,7 +62,7 @@ repos:
         types: [text]
 
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.2.0
+    rev: v4.4.0
     hooks:
       - id: check-added-large-files
       - id: check-ast
@@ -80,12 +88,12 @@ repos:
         exclude: \.(html|svg)$
 
   - repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.20.1
+    rev: v2.2.0
     hooks:
       - id: setup-cfg-fmt
 
   - repo: https://github.com/PyCQA/flake8
-    rev: 4.0.1
+    rev: 6.0.0
     hooks:
       - id: flake8
         additional_dependencies:
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000..f40b1fa3
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,33 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.10"
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+   configuration: docs/conf.py
+
+# If using Sphinx, optionally build your docs in additional formats such as PDF
+# formats:
+#    - pdf
+
+# Optionally declare the Python requirements required to build your docs
+python:
+   version: 3.10
+   install:
+      - requirements: requirements.txt
+      - method: pip
+        path: .
+        extra_requirements:
+            - docs
+      - method: setuptools
+        path: .
+   system_packages: true
diff --git a/docs/packages/eodal.config.rst b/docs/packages/eodal.config.rst
new file mode 100644
index 00000000..40dd1146
--- /dev/null
+++ b/docs/packages/eodal.config.rst
@@ -0,0 +1,16 @@
+eodal.config package
+====================
+
+.. automodule:: eodal.config
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.config.settings
+   eodal.config.stac_providers
diff --git a/docs/packages/eodal.config.settings.rst b/docs/packages/eodal.config.settings.rst
new file mode 100644
index 00000000..e60a08f2
--- /dev/null
+++ b/docs/packages/eodal.config.settings.rst
@@ -0,0 +1,7 @@
+eodal.config.settings module
+============================
+
+.. automodule:: eodal.config.settings
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.config.stac_providers.rst b/docs/packages/eodal.config.stac_providers.rst
new file mode 100644
index 00000000..44e2e4cb
--- /dev/null
+++ b/docs/packages/eodal.config.stac_providers.rst
@@ -0,0 +1,7 @@
+eodal.config.stac\_providers module
+===================================
+
+.. automodule:: eodal.config.stac_providers
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.algorithms.rst b/docs/packages/eodal.core.algorithms.rst
new file mode 100644
index 00000000..bfda6b08
--- /dev/null
+++ b/docs/packages/eodal.core.algorithms.rst
@@ -0,0 +1,7 @@
+eodal.core.algorithms module
+============================
+
+.. automodule:: eodal.core.algorithms
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.band.rst b/docs/packages/eodal.core.band.rst
new file mode 100644
index 00000000..7d07f4b8
--- /dev/null
+++ b/docs/packages/eodal.core.band.rst
@@ -0,0 +1,7 @@
+eodal.core.band module
+======================
+
+.. automodule:: eodal.core.band
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.operators.rst b/docs/packages/eodal.core.operators.rst
new file mode 100644
index 00000000..1de1fea3
--- /dev/null
+++ b/docs/packages/eodal.core.operators.rst
@@ -0,0 +1,7 @@
+eodal.core.operators module
+===========================
+
+.. automodule:: eodal.core.operators
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.raster.rst b/docs/packages/eodal.core.raster.rst
new file mode 100644
index 00000000..0bb853ce
--- /dev/null
+++ b/docs/packages/eodal.core.raster.rst
@@ -0,0 +1,7 @@
+eodal.core.raster module
+========================
+
+.. automodule:: eodal.core.raster
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.rst b/docs/packages/eodal.core.rst
new file mode 100644
index 00000000..c7079a86
--- /dev/null
+++ b/docs/packages/eodal.core.rst
@@ -0,0 +1,29 @@
+eodal.core package
+==================
+
+.. automodule:: eodal.core
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.core.sensors
+   eodal.core.utils
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.core.algorithms
+   eodal.core.band
+   eodal.core.operators
+   eodal.core.raster
+   eodal.core.scene
+   eodal.core.spectral_indices
diff --git a/docs/packages/eodal.core.scene.rst b/docs/packages/eodal.core.scene.rst
new file mode 100644
index 00000000..a0cb0ed7
--- /dev/null
+++ b/docs/packages/eodal.core.scene.rst
@@ -0,0 +1,7 @@
+eodal.core.scene module
+=======================
+
+.. automodule:: eodal.core.scene
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.sensors.planet_scope.rst b/docs/packages/eodal.core.sensors.planet_scope.rst
new file mode 100644
index 00000000..7bbcabbe
--- /dev/null
+++ b/docs/packages/eodal.core.sensors.planet_scope.rst
@@ -0,0 +1,7 @@
+eodal.core.sensors.planet\_scope module
+=======================================
+
+.. automodule:: eodal.core.sensors.planet_scope
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.sensors.rst b/docs/packages/eodal.core.sensors.rst
new file mode 100644
index 00000000..978cfb36
--- /dev/null
+++ b/docs/packages/eodal.core.sensors.rst
@@ -0,0 +1,17 @@
+eodal.core.sensors package
+==========================
+
+.. automodule:: eodal.core.sensors
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.core.sensors.planet_scope
+   eodal.core.sensors.sentinel1
+   eodal.core.sensors.sentinel2
diff --git a/docs/packages/eodal.core.sensors.sentinel1.rst b/docs/packages/eodal.core.sensors.sentinel1.rst
new file mode 100644
index 00000000..403f524f
--- /dev/null
+++ b/docs/packages/eodal.core.sensors.sentinel1.rst
@@ -0,0 +1,7 @@
+eodal.core.sensors.sentinel1 module
+===================================
+
+.. automodule:: eodal.core.sensors.sentinel1
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.sensors.sentinel2.rst b/docs/packages/eodal.core.sensors.sentinel2.rst
new file mode 100644
index 00000000..a222128f
--- /dev/null
+++ b/docs/packages/eodal.core.sensors.sentinel2.rst
@@ -0,0 +1,7 @@
+eodal.core.sensors.sentinel2 module
+===================================
+
+.. automodule:: eodal.core.sensors.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.spectral_indices.rst b/docs/packages/eodal.core.spectral_indices.rst
new file mode 100644
index 00000000..c4b12ad6
--- /dev/null
+++ b/docs/packages/eodal.core.spectral_indices.rst
@@ -0,0 +1,7 @@
+eodal.core.spectral\_indices module
+===================================
+
+.. automodule:: eodal.core.spectral_indices
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.utils.geometry.rst b/docs/packages/eodal.core.utils.geometry.rst
new file mode 100644
index 00000000..9919d3fd
--- /dev/null
+++ b/docs/packages/eodal.core.utils.geometry.rst
@@ -0,0 +1,7 @@
+eodal.core.utils.geometry module
+================================
+
+.. automodule:: eodal.core.utils.geometry
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.utils.raster.rst b/docs/packages/eodal.core.utils.raster.rst
new file mode 100644
index 00000000..af281d43
--- /dev/null
+++ b/docs/packages/eodal.core.utils.raster.rst
@@ -0,0 +1,7 @@
+eodal.core.utils.raster module
+==============================
+
+.. automodule:: eodal.core.utils.raster
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.core.utils.rst b/docs/packages/eodal.core.utils.rst
new file mode 100644
index 00000000..95c3e512
--- /dev/null
+++ b/docs/packages/eodal.core.utils.rst
@@ -0,0 +1,17 @@
+eodal.core.utils package
+========================
+
+.. automodule:: eodal.core.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.core.utils.geometry
+   eodal.core.utils.raster
+   eodal.core.utils.sentinel2
diff --git a/docs/packages/eodal.core.utils.sentinel2.rst b/docs/packages/eodal.core.utils.sentinel2.rst
new file mode 100644
index 00000000..af7fc68a
--- /dev/null
+++ b/docs/packages/eodal.core.utils.sentinel2.rst
@@ -0,0 +1,7 @@
+eodal.core.utils.sentinel2 module
+=================================
+
+.. automodule:: eodal.core.utils.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.planet_scope.client.rst b/docs/packages/eodal.downloader.planet_scope.client.rst
new file mode 100644
index 00000000..9526cf02
--- /dev/null
+++ b/docs/packages/eodal.downloader.planet_scope.client.rst
@@ -0,0 +1,7 @@
+eodal.downloader.planet\_scope.client module
+============================================
+
+.. automodule:: eodal.downloader.planet_scope.client
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.planet_scope.rst b/docs/packages/eodal.downloader.planet_scope.rst
new file mode 100644
index 00000000..19ba665f
--- /dev/null
+++ b/docs/packages/eodal.downloader.planet_scope.rst
@@ -0,0 +1,15 @@
+eodal.downloader.planet\_scope package
+======================================
+
+.. automodule:: eodal.downloader.planet_scope
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.downloader.planet_scope.client
diff --git a/docs/packages/eodal.downloader.rst b/docs/packages/eodal.downloader.rst
new file mode 100644
index 00000000..05b2f04f
--- /dev/null
+++ b/docs/packages/eodal.downloader.rst
@@ -0,0 +1,18 @@
+eodal.downloader package
+========================
+
+.. automodule:: eodal.downloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.downloader.planet_scope
+   eodal.downloader.sentinel1
+   eodal.downloader.sentinel2
+   eodal.downloader.utils
diff --git a/docs/packages/eodal.downloader.sentinel1.creodias.rst b/docs/packages/eodal.downloader.sentinel1.creodias.rst
new file mode 100644
index 00000000..3712cd57
--- /dev/null
+++ b/docs/packages/eodal.downloader.sentinel1.creodias.rst
@@ -0,0 +1,7 @@
+eodal.downloader.sentinel1.creodias module
+==========================================
+
+.. automodule:: eodal.downloader.sentinel1.creodias
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.sentinel1.rst b/docs/packages/eodal.downloader.sentinel1.rst
new file mode 100644
index 00000000..411370bd
--- /dev/null
+++ b/docs/packages/eodal.downloader.sentinel1.rst
@@ -0,0 +1,15 @@
+eodal.downloader.sentinel1 package
+==================================
+
+.. automodule:: eodal.downloader.sentinel1
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.downloader.sentinel1.creodias
diff --git a/docs/packages/eodal.downloader.sentinel2.copernicus.rst b/docs/packages/eodal.downloader.sentinel2.copernicus.rst
new file mode 100644
index 00000000..fc6c3867
--- /dev/null
+++ b/docs/packages/eodal.downloader.sentinel2.copernicus.rst
@@ -0,0 +1,7 @@
+eodal.downloader.sentinel2.copernicus module
+============================================
+
+.. automodule:: eodal.downloader.sentinel2.copernicus
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.sentinel2.creodias.rst b/docs/packages/eodal.downloader.sentinel2.creodias.rst
new file mode 100644
index 00000000..2aa2cc73
--- /dev/null
+++ b/docs/packages/eodal.downloader.sentinel2.creodias.rst
@@ -0,0 +1,7 @@
+eodal.downloader.sentinel2.creodias module
+==========================================
+
+.. automodule:: eodal.downloader.sentinel2.creodias
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.sentinel2.rst b/docs/packages/eodal.downloader.sentinel2.rst
new file mode 100644
index 00000000..61bbafaf
--- /dev/null
+++ b/docs/packages/eodal.downloader.sentinel2.rst
@@ -0,0 +1,16 @@
+eodal.downloader.sentinel2 package
+==================================
+
+.. automodule:: eodal.downloader.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.downloader.sentinel2.copernicus
+   eodal.downloader.sentinel2.creodias
diff --git a/docs/packages/eodal.downloader.utils.creodias.rst b/docs/packages/eodal.downloader.utils.creodias.rst
new file mode 100644
index 00000000..68396c5b
--- /dev/null
+++ b/docs/packages/eodal.downloader.utils.creodias.rst
@@ -0,0 +1,7 @@
+eodal.downloader.utils.creodias module
+======================================
+
+.. automodule:: eodal.downloader.utils.creodias
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.downloader.utils.rst b/docs/packages/eodal.downloader.utils.rst
new file mode 100644
index 00000000..ca5b891b
--- /dev/null
+++ b/docs/packages/eodal.downloader.utils.rst
@@ -0,0 +1,16 @@
+eodal.downloader.utils package
+==============================
+
+.. automodule:: eodal.downloader.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.downloader.utils.creodias
+   eodal.downloader.utils.unzip_datasets
diff --git a/docs/packages/eodal.downloader.utils.unzip_datasets.rst b/docs/packages/eodal.downloader.utils.unzip_datasets.rst
new file mode 100644
index 00000000..404484cd
--- /dev/null
+++ b/docs/packages/eodal.downloader.utils.unzip_datasets.rst
@@ -0,0 +1,7 @@
+eodal.downloader.utils.unzip\_datasets module
+=============================================
+
+.. automodule:: eodal.downloader.utils.unzip_datasets
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.mapper.feature.rst b/docs/packages/eodal.mapper.feature.rst
new file mode 100644
index 00000000..2a0445f9
--- /dev/null
+++ b/docs/packages/eodal.mapper.feature.rst
@@ -0,0 +1,7 @@
+eodal.mapper.feature module
+===========================
+
+.. automodule:: eodal.mapper.feature
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.mapper.filter.rst b/docs/packages/eodal.mapper.filter.rst
new file mode 100644
index 00000000..dc2369a4
--- /dev/null
+++ b/docs/packages/eodal.mapper.filter.rst
@@ -0,0 +1,7 @@
+eodal.mapper.filter module
+==========================
+
+.. automodule:: eodal.mapper.filter
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.mapper.mapper.rst b/docs/packages/eodal.mapper.mapper.rst
new file mode 100644
index 00000000..7dd094be
--- /dev/null
+++ b/docs/packages/eodal.mapper.mapper.rst
@@ -0,0 +1,7 @@
+eodal.mapper.mapper module
+==========================
+
+.. automodule:: eodal.mapper.mapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.mapper.rst b/docs/packages/eodal.mapper.rst
new file mode 100644
index 00000000..edc1f74c
--- /dev/null
+++ b/docs/packages/eodal.mapper.rst
@@ -0,0 +1,17 @@
+eodal.mapper package
+====================
+
+.. automodule:: eodal.mapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.mapper.feature
+   eodal.mapper.filter
+   eodal.mapper.mapper
diff --git a/docs/packages/eodal.metadata.database.db_model.rst b/docs/packages/eodal.metadata.database.db_model.rst
new file mode 100644
index 00000000..2f4823bd
--- /dev/null
+++ b/docs/packages/eodal.metadata.database.db_model.rst
@@ -0,0 +1,7 @@
+eodal.metadata.database.db\_model module
+========================================
+
+.. automodule:: eodal.metadata.database.db_model
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.database.ingestion.rst b/docs/packages/eodal.metadata.database.ingestion.rst
new file mode 100644
index 00000000..87299180
--- /dev/null
+++ b/docs/packages/eodal.metadata.database.ingestion.rst
@@ -0,0 +1,7 @@
+eodal.metadata.database.ingestion module
+========================================
+
+.. automodule:: eodal.metadata.database.ingestion
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.database.querying.rst b/docs/packages/eodal.metadata.database.querying.rst
new file mode 100644
index 00000000..82819a36
--- /dev/null
+++ b/docs/packages/eodal.metadata.database.querying.rst
@@ -0,0 +1,7 @@
+eodal.metadata.database.querying module
+=======================================
+
+.. automodule:: eodal.metadata.database.querying
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.database.rst b/docs/packages/eodal.metadata.database.rst
new file mode 100644
index 00000000..17951271
--- /dev/null
+++ b/docs/packages/eodal.metadata.database.rst
@@ -0,0 +1,17 @@
+eodal.metadata.database package
+===============================
+
+.. automodule:: eodal.metadata.database
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.database.db_model
+   eodal.metadata.database.ingestion
+   eodal.metadata.database.querying
diff --git a/docs/packages/eodal.metadata.planet_scope.database.ingestion.rst b/docs/packages/eodal.metadata.planet_scope.database.ingestion.rst
new file mode 100644
index 00000000..11efee41
--- /dev/null
+++ b/docs/packages/eodal.metadata.planet_scope.database.ingestion.rst
@@ -0,0 +1,7 @@
+eodal.metadata.planet\_scope.database.ingestion module
+======================================================
+
+.. automodule:: eodal.metadata.planet_scope.database.ingestion
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.planet_scope.database.querying.rst b/docs/packages/eodal.metadata.planet_scope.database.querying.rst
new file mode 100644
index 00000000..e40e7ec3
--- /dev/null
+++ b/docs/packages/eodal.metadata.planet_scope.database.querying.rst
@@ -0,0 +1,7 @@
+eodal.metadata.planet\_scope.database.querying module
+=====================================================
+
+.. automodule:: eodal.metadata.planet_scope.database.querying
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.planet_scope.database.rst b/docs/packages/eodal.metadata.planet_scope.database.rst
new file mode 100644
index 00000000..d8e0bba5
--- /dev/null
+++ b/docs/packages/eodal.metadata.planet_scope.database.rst
@@ -0,0 +1,16 @@
+eodal.metadata.planet\_scope.database package
+=============================================
+
+.. automodule:: eodal.metadata.planet_scope.database
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.planet_scope.database.ingestion
+   eodal.metadata.planet_scope.database.querying
diff --git a/docs/packages/eodal.metadata.planet_scope.parsing.rst b/docs/packages/eodal.metadata.planet_scope.parsing.rst
new file mode 100644
index 00000000..3e14e7e6
--- /dev/null
+++ b/docs/packages/eodal.metadata.planet_scope.parsing.rst
@@ -0,0 +1,7 @@
+eodal.metadata.planet\_scope.parsing module
+===========================================
+
+.. automodule:: eodal.metadata.planet_scope.parsing
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.planet_scope.rst b/docs/packages/eodal.metadata.planet_scope.rst
new file mode 100644
index 00000000..90719cfe
--- /dev/null
+++ b/docs/packages/eodal.metadata.planet_scope.rst
@@ -0,0 +1,23 @@
+eodal.metadata.planet\_scope package
+====================================
+
+.. automodule:: eodal.metadata.planet_scope
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.planet_scope.database
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.planet_scope.parsing
diff --git a/docs/packages/eodal.metadata.rst b/docs/packages/eodal.metadata.rst
new file mode 100644
index 00000000..29a28860
--- /dev/null
+++ b/docs/packages/eodal.metadata.rst
@@ -0,0 +1,27 @@
+eodal.metadata package
+======================
+
+.. automodule:: eodal.metadata
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.database
+   eodal.metadata.planet_scope
+   eodal.metadata.sentinel1
+   eodal.metadata.sentinel2
+   eodal.metadata.stac
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.utils
diff --git a/docs/packages/eodal.metadata.sentinel1.database.ingestion.rst b/docs/packages/eodal.metadata.sentinel1.database.ingestion.rst
new file mode 100644
index 00000000..3fcc50f9
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel1.database.ingestion.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel1.database.ingestion module
+==================================================
+
+.. automodule:: eodal.metadata.sentinel1.database.ingestion
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel1.database.querying.rst b/docs/packages/eodal.metadata.sentinel1.database.querying.rst
new file mode 100644
index 00000000..22019a6c
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel1.database.querying.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel1.database.querying module
+=================================================
+
+.. automodule:: eodal.metadata.sentinel1.database.querying
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel1.database.rst b/docs/packages/eodal.metadata.sentinel1.database.rst
new file mode 100644
index 00000000..66988b84
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel1.database.rst
@@ -0,0 +1,16 @@
+eodal.metadata.sentinel1.database package
+=========================================
+
+.. automodule:: eodal.metadata.sentinel1.database
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel1.database.ingestion
+   eodal.metadata.sentinel1.database.querying
diff --git a/docs/packages/eodal.metadata.sentinel1.parsing.rst b/docs/packages/eodal.metadata.sentinel1.parsing.rst
new file mode 100644
index 00000000..d9116bd1
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel1.parsing.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel1.parsing module
+=======================================
+
+.. automodule:: eodal.metadata.sentinel1.parsing
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel1.rst b/docs/packages/eodal.metadata.sentinel1.rst
new file mode 100644
index 00000000..24e31015
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel1.rst
@@ -0,0 +1,23 @@
+eodal.metadata.sentinel1 package
+================================
+
+.. automodule:: eodal.metadata.sentinel1
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel1.database
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel1.parsing
diff --git a/docs/packages/eodal.metadata.sentinel2.database.ingestion.rst b/docs/packages/eodal.metadata.sentinel2.database.ingestion.rst
new file mode 100644
index 00000000..9ec63a0e
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.database.ingestion.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel2.database.ingestion module
+==================================================
+
+.. automodule:: eodal.metadata.sentinel2.database.ingestion
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel2.database.querying.rst b/docs/packages/eodal.metadata.sentinel2.database.querying.rst
new file mode 100644
index 00000000..e8256489
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.database.querying.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel2.database.querying module
+=================================================
+
+.. automodule:: eodal.metadata.sentinel2.database.querying
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel2.database.rst b/docs/packages/eodal.metadata.sentinel2.database.rst
new file mode 100644
index 00000000..1255f330
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.database.rst
@@ -0,0 +1,16 @@
+eodal.metadata.sentinel2.database package
+=========================================
+
+.. automodule:: eodal.metadata.sentinel2.database
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel2.database.ingestion
+   eodal.metadata.sentinel2.database.querying
diff --git a/docs/packages/eodal.metadata.sentinel2.parsing.rst b/docs/packages/eodal.metadata.sentinel2.parsing.rst
new file mode 100644
index 00000000..f5a821e1
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.parsing.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel2.parsing module
+=======================================
+
+.. automodule:: eodal.metadata.sentinel2.parsing
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.sentinel2.rst b/docs/packages/eodal.metadata.sentinel2.rst
new file mode 100644
index 00000000..0f00d219
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.rst
@@ -0,0 +1,24 @@
+eodal.metadata.sentinel2 package
+================================
+
+.. automodule:: eodal.metadata.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel2.database
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.sentinel2.parsing
+   eodal.metadata.sentinel2.utils
diff --git a/docs/packages/eodal.metadata.sentinel2.utils.rst b/docs/packages/eodal.metadata.sentinel2.utils.rst
new file mode 100644
index 00000000..60488f2f
--- /dev/null
+++ b/docs/packages/eodal.metadata.sentinel2.utils.rst
@@ -0,0 +1,7 @@
+eodal.metadata.sentinel2.utils module
+=====================================
+
+.. automodule:: eodal.metadata.sentinel2.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.stac.client.rst b/docs/packages/eodal.metadata.stac.client.rst
new file mode 100644
index 00000000..1857d044
--- /dev/null
+++ b/docs/packages/eodal.metadata.stac.client.rst
@@ -0,0 +1,7 @@
+eodal.metadata.stac.client module
+=================================
+
+.. automodule:: eodal.metadata.stac.client
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.metadata.stac.rst b/docs/packages/eodal.metadata.stac.rst
new file mode 100644
index 00000000..a645d2b3
--- /dev/null
+++ b/docs/packages/eodal.metadata.stac.rst
@@ -0,0 +1,15 @@
+eodal.metadata.stac package
+===========================
+
+.. automodule:: eodal.metadata.stac
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.metadata.stac.client
diff --git a/docs/packages/eodal.metadata.utils.rst b/docs/packages/eodal.metadata.utils.rst
new file mode 100644
index 00000000..91b71bb8
--- /dev/null
+++ b/docs/packages/eodal.metadata.utils.rst
@@ -0,0 +1,7 @@
+eodal.metadata.utils module
+===========================
+
+.. automodule:: eodal.metadata.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.rst b/docs/packages/eodal.rst
new file mode 100644
index 00000000..0742addf
--- /dev/null
+++ b/docs/packages/eodal.rst
@@ -0,0 +1,20 @@
+eodal package
+=============
+
+.. automodule:: eodal
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.config
+   eodal.core
+   eodal.downloader
+   eodal.mapper
+   eodal.metadata
+   eodal.utils
diff --git a/docs/packages/eodal.utils.arrays.rst b/docs/packages/eodal.utils.arrays.rst
new file mode 100644
index 00000000..d94dd70e
--- /dev/null
+++ b/docs/packages/eodal.utils.arrays.rst
@@ -0,0 +1,7 @@
+eodal.utils.arrays module
+=========================
+
+.. automodule:: eodal.utils.arrays
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.constants.planet_scope.rst b/docs/packages/eodal.utils.constants.planet_scope.rst
new file mode 100644
index 00000000..603bd8b7
--- /dev/null
+++ b/docs/packages/eodal.utils.constants.planet_scope.rst
@@ -0,0 +1,7 @@
+eodal.utils.constants.planet\_scope module
+==========================================
+
+.. automodule:: eodal.utils.constants.planet_scope
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.constants.rst b/docs/packages/eodal.utils.constants.rst
new file mode 100644
index 00000000..03d002b6
--- /dev/null
+++ b/docs/packages/eodal.utils.constants.rst
@@ -0,0 +1,16 @@
+eodal.utils.constants package
+=============================
+
+.. automodule:: eodal.utils.constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.utils.constants.planet_scope
+   eodal.utils.constants.sentinel2
diff --git a/docs/packages/eodal.utils.constants.sentinel2.rst b/docs/packages/eodal.utils.constants.sentinel2.rst
new file mode 100644
index 00000000..31487306
--- /dev/null
+++ b/docs/packages/eodal.utils.constants.sentinel2.rst
@@ -0,0 +1,7 @@
+eodal.utils.constants.sentinel2 module
+======================================
+
+.. automodule:: eodal.utils.constants.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.decorators.rst b/docs/packages/eodal.utils.decorators.rst
new file mode 100644
index 00000000..b84c95b0
--- /dev/null
+++ b/docs/packages/eodal.utils.decorators.rst
@@ -0,0 +1,7 @@
+eodal.utils.decorators module
+=============================
+
+.. automodule:: eodal.utils.decorators
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.exceptions.rst b/docs/packages/eodal.utils.exceptions.rst
new file mode 100644
index 00000000..5b920f85
--- /dev/null
+++ b/docs/packages/eodal.utils.exceptions.rst
@@ -0,0 +1,7 @@
+eodal.utils.exceptions module
+=============================
+
+.. automodule:: eodal.utils.exceptions
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.geometry.rst b/docs/packages/eodal.utils.geometry.rst
new file mode 100644
index 00000000..0c12aead
--- /dev/null
+++ b/docs/packages/eodal.utils.geometry.rst
@@ -0,0 +1,7 @@
+eodal.utils.geometry module
+===========================
+
+.. automodule:: eodal.utils.geometry
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.reprojection.rst b/docs/packages/eodal.utils.reprojection.rst
new file mode 100644
index 00000000..87a3acd9
--- /dev/null
+++ b/docs/packages/eodal.utils.reprojection.rst
@@ -0,0 +1,7 @@
+eodal.utils.reprojection module
+===============================
+
+.. automodule:: eodal.utils.reprojection
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.rst b/docs/packages/eodal.utils.rst
new file mode 100644
index 00000000..674954a7
--- /dev/null
+++ b/docs/packages/eodal.utils.rst
@@ -0,0 +1,32 @@
+eodal.utils package
+===================
+
+.. automodule:: eodal.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.utils.constants
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal.utils.arrays
+   eodal.utils.decorators
+   eodal.utils.exceptions
+   eodal.utils.geometry
+   eodal.utils.reprojection
+   eodal.utils.sentinel1
+   eodal.utils.sentinel2
+   eodal.utils.stacking
+   eodal.utils.types
+   eodal.utils.warnings
diff --git a/docs/packages/eodal.utils.sentinel1.rst b/docs/packages/eodal.utils.sentinel1.rst
new file mode 100644
index 00000000..5cafd3e8
--- /dev/null
+++ b/docs/packages/eodal.utils.sentinel1.rst
@@ -0,0 +1,7 @@
+eodal.utils.sentinel1 module
+============================
+
+.. automodule:: eodal.utils.sentinel1
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.sentinel2.rst b/docs/packages/eodal.utils.sentinel2.rst
new file mode 100644
index 00000000..81e7616d
--- /dev/null
+++ b/docs/packages/eodal.utils.sentinel2.rst
@@ -0,0 +1,7 @@
+eodal.utils.sentinel2 module
+============================
+
+.. automodule:: eodal.utils.sentinel2
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.stacking.rst b/docs/packages/eodal.utils.stacking.rst
new file mode 100644
index 00000000..8d424c6f
--- /dev/null
+++ b/docs/packages/eodal.utils.stacking.rst
@@ -0,0 +1,7 @@
+eodal.utils.stacking module
+===========================
+
+.. automodule:: eodal.utils.stacking
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.types.rst b/docs/packages/eodal.utils.types.rst
new file mode 100644
index 00000000..74f6cfdd
--- /dev/null
+++ b/docs/packages/eodal.utils.types.rst
@@ -0,0 +1,7 @@
+eodal.utils.types module
+========================
+
+.. automodule:: eodal.utils.types
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/eodal.utils.warnings.rst b/docs/packages/eodal.utils.warnings.rst
new file mode 100644
index 00000000..14f389fb
--- /dev/null
+++ b/docs/packages/eodal.utils.warnings.rst
@@ -0,0 +1,7 @@
+eodal.utils.warnings module
+===========================
+
+.. automodule:: eodal.utils.warnings
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/packages/modules.rst b/docs/packages/modules.rst
new file mode 100644
index 00000000..66381339
--- /dev/null
+++ b/docs/packages/modules.rst
@@ -0,0 +1,7 @@
+API Reference
+=============
+
+.. toctree::
+   :maxdepth: 4
+
+   eodal
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 72d31939..5eab5434 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,3 @@
 -r ../requirements.txt
 -r ../requirements-docs.txt
+eodal
diff --git a/eodal/__meta__.py b/eodal/__meta__.py
index 4bcde461..213b28e3 100644
--- a/eodal/__meta__.py
+++ b/eodal/__meta__.py
@@ -6,8 +6,10 @@
 # https://semver.org
 # version = "0.1.dev0"
 author = (
-    "Crop Science, Institute of Agricultural Sciences, D-USYS, ETH Zurich, Zurich, Switzerland;\n"
-    "Earth Observation of Agroecosystems Team, Division Agroecology and Environment, Agroscope, Zurich, Switzerland"
+    "Crop Science, Institute of Agricultural Sciences, D-USYS, ETH Zurich, "
+    "Zurich, Switzerland;\n"
+    "Earth Observation of Agroecosystems Team, Division Agroecology and Environment, "
+    "Agroscope, Zurich, Switzerland"
 )
 author_email = ""
 description = "Earth Observation Data Analysis Library"  # One-liner
diff --git a/eodal/config/settings.py b/eodal/config/settings.py
index c3434762..e3d081f0 100644
--- a/eodal/config/settings.py
+++ b/eodal/config/settings.py
@@ -63,7 +63,8 @@ class Settings(BaseSettings):
     # define CREODIAS username and password
     CREODIAS_USER: str = ""
     CREODIAS_PASSWORD: str = ""
-    # maximum number of records per request: 2000 (CREODIAS currently does not allow more)
+    # maximum number of records per request: 2000 (CREODIAS currently does not allow
+    # more)
     CREODIAS_MAX_RECORDS: int = 2000
 
     # define Planet-API token
@@ -95,7 +96,10 @@ class Settings(BaseSettings):
 
     # path to custom CA_BUNDLE when calling the pystac_client behind a proxy server
     # when a path a custom certificate is required set this variable to a path
-    STAC_API_IO_CA_BUNDLE: bool | str = True
+    STAC_API_IO_CA_BUNDLE: bool = True
+
+    # maximum number of HTTPS retries
+    NUMBER_HTTPS_RETRIES: int = 5
 
     # define logger
     CURRENT_TIME: str = datetime.now().strftime("%Y%m%d-%H%M%S")
@@ -105,8 +109,8 @@ class Settings(BaseSettings):
     LOGGING_LEVEL: int = logging.INFO
 
     # processing checks
-    PROCESSING_CHECK_FILE_NO_BF: str = f"successful_scenes_noblackfill.txt"
-    PROCESSING_CHECK_FILE_BF: str = f"successful_scenes_blackfill.txt"
+    PROCESSING_CHECK_FILE_NO_BF: str = "successful_scenes_noblackfill.txt"
+    PROCESSING_CHECK_FILE_BF: str = "successful_scenes_blackfill.txt"
 
     # temporary working directory
     TEMP_WORKING_DIR: Path = Path(tempfile.gettempdir())
diff --git a/eodal/config/stac_providers.py b/eodal/config/stac_providers.py
index ee260149..15222dcc 100644
--- a/eodal/config/stac_providers.py
+++ b/eodal/config/stac_providers.py
@@ -50,7 +50,7 @@ class MSPC:
         URL: str = "https://planetarycomputer.microsoft.com/api/stac/v1"
         S2Level_1C: str = "sentinel-2-l1c"
         S2Level_2A: str = "sentinel-2-l2a"
-        S1RTC: str = "sentinel-1-rtc"  # radiometric and terrain corrected using PlanetDEM (IW mode)
+        S1RTC: str = "sentinel-1-rtc"  # radiometric and terrain corrected
         S1GRD: str = (
             "sentinel-1-grd"  # corrected to ground range using ellipsoid model WGS84
         )
diff --git a/eodal/core/algorithms.py b/eodal/core/algorithms.py
index 36988ecb..e088c4fc 100644
--- a/eodal/core/algorithms.py
+++ b/eodal/core/algorithms.py
@@ -102,8 +102,9 @@ def merge_datasets(
     :param kwargs:
         kwargs to pass to ``rasterio.warp.reproject``
     :returns:
-        depending on the kwargs passed either `None` (if output is written to file directly)
-        or a `RasterCollection` instance if the operation takes place in memory
+        depending on the kwargs passed either `None` (if output is written to file
+        directly) or a `RasterCollection` instance if the operation takes place
+        in memory
     """
     # check the CRS and attributes of the datasets first
     crs_list = []
diff --git a/eodal/core/band.py b/eodal/core/band.py
index 31b0b6ea..e0c62503 100644
--- a/eodal/core/band.py
+++ b/eodal/core/band.py
@@ -2,12 +2,12 @@
 A band is a two-dimensional array that can be located via a spatial coordinate system.
 Each band thus has a name and an array of values, which are usually numeric.
 
-It relies on ``rasterio`` for all in- and output operations to read data from files (or URIs)
-using ``GDAL`` drivers.
+It relies on ``rasterio`` for all in- and output operations to read data from files
+(or URIs) using ``GDAL`` drivers.
 
 eodal stores band data basically as ``numpy`` arrays. Masked arrays of the class
-`~numpy.ma.MaskedArray` are also supported. For very large data sets that exceed the RAM of the
-computer, ``zarr`` can be used.
+`~numpy.ma.MaskedArray` are also supported. For very large data sets that exceed the
+RAM of the computer, ``zarr`` can be used.
 
 Copyright (C) 2022 Lukas Valentin Graf
 
@@ -542,7 +542,8 @@ def __rlt__(self, other):
         return BandOperator.calc(a=self, other=other, operator="<", right_sided=True)
 
     def __repr__(self) -> str:
-        return f"EOdal Band\n---------.\nName:    {self.band_name}\nGeoInfo:    {self.geo_info}"
+        return f"EOdal Band\n---------.\nName:    {self.band_name}\nGeoInfo:    " + \
+            f"{self.geo_info}"
 
     @property
     def alias(self) -> Union[str, None]:
@@ -634,7 +635,8 @@ def _check_vector_features(vector_features: None | gpd.GeoDataFrame) -> None:
                 vector_features = gpd.read_file(vector_features)
             if vector_features.crs is None:
                 raise ValueError(
-                    f"Cannot handle vector features without spatial coordinate reference system"
+                    "Cannot handle vector features without spatial coordinate"
+                    "reference system"
                 )
 
     @staticmethod
@@ -955,14 +957,14 @@ def from_vector(
             in_gdf[band_name_src] = 1
         # otherwise check if the passed attribute exists
         else:
-            if not band_name_src in in_gdf.columns:
+            if band_name_src not in in_gdf.columns:
                 raise AttributeError(f"{band_name_src} not found")
 
         # infer the datatype (i.e., try if it is possible to cast the
         # attribute to float32, otherwise do not process the feature)
         try:
             in_gdf[band_name_src].astype(dtype_src)
-        except ValueError as e:
+        except ValueError:
             raise TypeError(f'Attribute "{band_name_src}" seems not to be numeric')
 
         # clip features to the spatial extent of a bounding box if available
@@ -1021,7 +1023,8 @@ def from_vector(
             pixres_y=geo_info.pixres_y,
         )
 
-        # rasterize the vector features. Point features work in another way than Polygons
+        # rasterize the vector features. Point features work in another way
+        # than Polygons
         if (in_gdf.geom_type.unique() == ["Point"]).all():
             try:
                 rasterized = array_from_points(
@@ -1199,15 +1202,15 @@ def clip(
 
         :param clipping_bounds:
             spatial bounds to clip the Band to. Can be either a vector file, a shapely
-            `Polygon` or `MultiPolygon`, a `GeoDataFrame`, `GeoSeries` or a coordinate tuple with
-            (xmin, ymin, xmax, ymax).
+            `Polygon` or `MultiPolygon`, a `GeoDataFrame`, `GeoSeries` or a coordinate
+            tuple with (xmin, ymin, xmax, ymax).
             Vector files and `GeoDataFrame` are reprojected into the bands' coordinate
             system if required, while the coordinate tuple and shapely geometry **MUST**
             be provided in the CRS of the band.
         :param full_bounding_box_only:
-            if False (default), clips to the bounding box of the geometry and masks values
-            outside the actual geometry boundaries. To obtain all values within the
-            bounding box set to True.
+            if False (default), clips to the bounding box of the geometry and masks
+            values outside the actual geometry boundaries. To obtain all values within
+            the bounding box set to True.
             .. versionadded:: 0.1.1
         :param inplace:
             if False (default) returns a copy of the ``Band`` instance
@@ -1263,7 +1266,7 @@ def clip(
             or self.bounds.overlaps(clip_shape)
             or clip_shape.covers(self.bounds)
         ):
-            raise ValueError(f"Clipping bounds do not overlap Band")
+            raise ValueError("Clipping bounds do not overlap Band")
         # then determine the extent in image coordinates by search for the closest image
         # pixels
         row_start, row_stop, col_start, col_stop = bounds_window(
@@ -1415,13 +1418,15 @@ def _find_nearest_array_index(array, value):
 
         # get column (x) indices
         gdf["col"] = gdf["x"].apply(
-            lambda x, coords=coords, find_nearest_array_index=_find_nearest_array_index: find_nearest_array_index(
+            lambda x, coords=coords, find_nearest_array_index=_find_nearest_array_index:
+            find_nearest_array_index(
                 coords["x"], x
             )
         )
         # get row (y) indices
         gdf["row"] = gdf["y"].apply(
-            lambda y, coords=coords, find_nearest_array_index=_find_nearest_array_index: find_nearest_array_index(
+            lambda y, coords=coords, find_nearest_array_index=_find_nearest_array_index:
+            find_nearest_array_index(
                 coords["y"], y
             )
         )
@@ -1431,7 +1436,8 @@ def _find_nearest_array_index(array, value):
 
         # loop over sample points and add them as new entries to the GeoDataFrame
         for _, record in gdf.iterrows():
-            # get array value for the current column and row, continue on out-of-bounds error
+            # get array value for the current column and row, continue on out-of-bounds
+            # error
             try:
                 array_value = self.values[record.row, record.col]
             except IndexError:
@@ -1695,7 +1701,7 @@ def mask(self, mask: np.ndarray, inplace: Optional[bool] = False):
         elif self.is_ndarray:
             masked_array = np.ma.MaskedArray(data=self.values, mask=mask)
         elif self.is_zarr:
-            raise NotImplemented()
+            raise NotImplementedError()
 
         if inplace:
             object.__setattr__(self, "values", masked_array)
@@ -1857,12 +1863,13 @@ def resample(
                 res_pixel_div = upsample_array(
                     in_array=band_data, scaling_factor=int(scaling_factor)
                 )
-            except Exception as e:
+            except Exception:
                 res_pixel_div = np.zeros(0)
 
             # replace NaNs with values from pixel division (if possible); thus we will
             # get all pixel values and the correct blackfill
-            # when working on spatial subsets this might fail because of shape mismatches;
+            # when working on spatial subsets this might fail because of shape
+            # mismatches;
             # in this case keep the cv2 output, which means loosing a few pixels
             if res.shape == res_pixel_div.shape:
                 res[np.isnan(res)] = res_pixel_div[np.isnan(res)]
@@ -2005,7 +2012,7 @@ def reproject(
     def reduce(
         self,
         method: Optional[
-            List[str | Callable[np.ndarray | np.ma.MaskedArray, Number]]
+            List[str | Callable[..., Number]]
         ] = ["min", "mean", "std", "max", "count"],
         by: Optional[Path | gpd.GeoDataFrame | Polygon | str] = None,
         keep_nans: Optional[bool] = False,
@@ -2028,8 +2035,9 @@ def reduce(
             about how to pass custom functions.
         :param by:
             define optional vector features by which to reduce the band. By passing
-            `'self'` the method uses the features with which the band was read, otherwise
-            specify a file-path to vector features or provide a GeoDataFrame.
+            `'self'` the method uses the features with which the band was read,
+            otherwise specify a file-path to vector features or provide a
+            GeoDataFrame.
         :param keep_nans:
             .. versionadded:: 0.2.0
             whether to keep or discard results that were `nan`. This could happen
@@ -2082,9 +2090,12 @@ def reduce(
                 # the usage of nan-functions is discouraged
                 if operator.startswith("nan"):
                     raise ValueError(
-                        "The usage of numpy-nan functions is discouraged and therefore raises an error."
-                        + "\nThe handling of NaNs is done by `rasterstats` internally and therefore does not"
-                        + '\n need to be specified. Please pass operators by their standard numpy names (e.g., "mean")'
+                        "The usage of numpy-nan functions is discouraged and therefore "
+                        "raises an error."
+                        "\nThe handling of NaNs is done by `rasterstats` internally and"
+                        " therefore does not"
+                        "\n need to be specified. Please pass operators by their "
+                        "standard numpy names (e.g., 'mean')"
                     )
                 # check if the operator is a standard rasterstats operator
                 # raises a ValueError if the passed operator is not implemented
@@ -2096,8 +2107,9 @@ def reduce(
             else:
                 raise ValueError(
                     f"Could not pass {operator} to rasterstats.\n"
-                    + "Please check the rasterstats docs how to pass user-defined statistics:\n"
-                    + "https://pythonhosted.org/rasterstats/manual.html#user-defined-statistics"
+                    "Please check the rasterstats docs how to "
+                    "pass user-defined statistics:\n"
+                    "https://pythonhosted.org/rasterstats/manual.html#user-defined-statistics"   # noqa: E501
                 )
 
             # get raster values from EOdal band
@@ -2124,7 +2136,8 @@ def reduce(
             )
             stats_operator_list.append(res)
 
-        # combine the list of stats into a format consistent with the standard zonal_stats call
+        # combine the list of stats into a format consistent with the standard
+        # zonal_stats call
         _stats = []
         for idx in range(features.shape[0]):
             feature_stats = {}
@@ -2191,7 +2204,7 @@ def scale_data(
                     + offset
                 )
         elif self.is_zarr:
-            raise NotImplemented()
+            raise NotImplementedError()
 
         if inplace:
             object.__setattr__(self, "values", scaled_array)
@@ -2231,7 +2244,7 @@ def to_dataframe(self) -> gpd.GeoDataFrame:
         elif self.is_ndarray:
             flattened = np.reshape(self.values, new_shape, order="F")
         elif self.is_zarr:
-            raise NotImplemented()
+            raise NotImplementedError()
 
         # convert the coordinates to shapely geometries
         coordinate_geoms = [
diff --git a/eodal/core/raster.py b/eodal/core/raster.py
index b6fb2bcf..68e7edb8 100644
--- a/eodal/core/raster.py
+++ b/eodal/core/raster.py
@@ -1,15 +1,15 @@
 """
-This module defines the ``RasterCollection`` class which is the basic class for reading, plotting,
-transforming, manipulating and writing (geo-referenced) raster data in an intuitive, object-oriented
-way (in terms of software philosophy).
+This module defines the ``RasterCollection`` class which is the basic class for reading,
+plotting, transforming, manipulating and writing (geo-referenced) raster data in an
+intuitive, object-oriented way (in terms of software philosophy).
 
-A ``RasterCollection`` is collection of to zero to N `~eodal.core.band.Band` instances, where each
-band denotes a two-dimensional array at its core. The ``RasterCollection`` class allows thereby
-to handle ``Band`` instances with different spatial reference systems, spatial resolutions (i.e.,
-grid cell sizes) and spatial extents.
+A ``RasterCollection`` is collection of to zero to N `~eodal.core.band.Band` instances,
+in which each band denotes a two-dimensional array at its core. The ``RasterCollection``
+class allows thereby to handle ``Band`` instances with different spatial reference
+systems, spatial resolutions (i.e., grid cell sizes) and spatial extents.
 
-Besides that, ``RasterCollection`` is a super class from which sensor-specific classes for reading
-(satellite) raster image data inherit.
+Besides that, ``RasterCollection`` is a super class from which sensor-specific classes
+for reading (satellite) raster image data inherit.
 
 .. highlight:: python
 .. code-block:: python
@@ -22,7 +22,7 @@
     # New collection from `numpy.ndarray`
     # Define GeoInfo and Array first and use them to initialize a new RasterCollection
     # instance:
-    
+
     # provide EPSG code
     epsg = 32633
     # provide upper left (ul) x and y coordinate (in units of the coordinate system
@@ -31,18 +31,18 @@
     # provide pixel size (spatial resolution). Note that resolution in y direction is
     # negative because we start at the upper left corner
     pixres_x, pixres_y = 10, -10
-    
+
     # get a new GeoInfo object
     geo_info = GeoInfo(epsg=epsg,ulx=ulx,uly=uly,pixres_x=pixres_x,pixres_y=pixres_y)
-    
+
     # define a band name for the band data to add
     band_name = 'random'
     # optionally, you can also asign a `band_alias` (e.g., color name)
     band_alias = 'blue'
-    
+
     # let's define some random numbers in a 2-d array
     values = np.random.random(size=(100,120))
-    
+
     # get the RasterCollection object
     raster = RasterCollection(
              band_constructor=Band,
@@ -87,7 +87,6 @@
 from matplotlib.pyplot import Figure
 from numbers import Number
 from pathlib import Path
-from rasterio import band
 from rasterio.drivers import driver_from_extension
 from typing import Any
 from typing import Callable
@@ -270,9 +269,9 @@ def calc(
         :param a:
             `RasterCollection` object with values (non-empty)
         :param other:
-            `Band` object, scalar, 3-dimensional `numpy.array`, or RasterCollection to use
-            on the right-hand side of the operator. If a `numpy.array` is passed the array
-            must have either shape `(1,nrows,ncols)` or `(nband,nrows,ncols)`
+            `Band` object, scalar, 3-dimensional `numpy.array`, or RasterCollection to
+            use on the right-hand side of the operator. If a `numpy.array` is passed
+            the array must have either shape `(1,nrows,ncols)` or `(nband,nrows,ncols)`
             where `nrows` is the number of rows in `a`, ncols the number of columns
             in `a` and `nbands` the number of bands in a or the selection thereof.
             The latter method does *not* work if the bands in `a` selected differ
@@ -307,8 +306,8 @@ def calc(
             if len(other.shape) == 2:
                 if other.shape != a.get_values(band_selection).shape[1::]:
                     raise ValueError(
-                        f"Passed array has wrong number of rows and columns. "
-                        + f"Expected {a.values.shape[1::]} - Got {other.shape}"
+                        "Passed array has wrong number of rows and columns. "
+                        f"Expected {a.values.shape[1::]} - Got {other.shape}"
                     )
             # or 3-d
             elif len(other.shape) == 3:
@@ -582,9 +581,10 @@ def __repr__(self) -> str:
             return "Empty EOdal RasterCollection"
         else:
             return (
-                f"EOdal RasterCollection\n----------------------\n"
-                + f'# Bands:    {len(self)}\nBand names:    {", ".join(self.band_names)}\n'
-                + f'Band aliases:    {", ".join(self.band_aliases)}'
+                "EOdal RasterCollection\n----------------------\n"
+                f'# Bands:    {len(self)}\nBand names:    '
+                f'{", ".join(self.band_names)}\n'
+                f'Band aliases:    {", ".join(self.band_aliases)}'
             )
 
     @property
@@ -872,8 +872,8 @@ def read_pixels(
             (default) all bands are loaded. If `band_idxs` and `band_names_src` are
             provided, the former is ignored.
         :param band_names_dst:
-            optional list of band names in the resulting collection.Must match the length
-            and order of `band_idxs` or `band_names_src`.
+            optional list of band names in the resulting collection.Must match the
+            length and order of `band_idxs` or `band_names_src`.
         :returns:
             ``GeoDataFrame`` with extracted pixel values. If the vector features
             defining the sampling points are not within the spatial extent of the
@@ -1293,8 +1293,8 @@ def band_summaries(
             `by` to get descriptive statistics by selected geometry features (e.g.,
             single polygons).
         :returns:
-            ``GeoDataFrame`` with descriptive statistics for all bands selected and geometry
-            features passed (optional)
+            ``GeoDataFrame`` with descriptive statistics for all bands selected and
+            geometry features passed (optional)
         """
         stats = []
         if band_selection is None:
@@ -1465,7 +1465,8 @@ def mask(
             # translate mask band into boolean array
             if mask_values is None:
                 raise ValueError(
-                    "When using a band as mask, you have to provide a list of mask values"
+                    "When using a band as mask, you have to provide a "
+                    "list of mask values"
                 )
             # convert the mask to a temporary binary mask
             tmp = np.zeros_like(_mask)
@@ -1495,7 +1496,8 @@ def mask(
         # check shapes of bands and mask before applying the mask
         if not self.is_bandstack(band_selection=bands_to_mask):
             raise ValueError(
-                "Can only mask bands that have the same spatial extent, pixel size and CRS"
+                "Can only mask bands that have the same spatial extent, "
+                "pixel size and CRS"
             )
 
         # initialize a new raster collection if inplace is False
diff --git a/eodal/core/scene.py b/eodal/core/scene.py
index 47ea4eed..270b9048 100644
--- a/eodal/core/scene.py
+++ b/eodal/core/scene.py
@@ -59,14 +59,16 @@ def __init__(
             optional callable returning an `~eodal.core.raster.RasterCollection`
             instance.
         :param indexed_by_timestamps:
-            if True, all scene indices are interpreted as timestamps (`datetime.datetime`).
+            if True, all scene indices are interpreted as timestamps
+            (`datetime.datetime`).
             Set to False if scene indices should be treated as different data types
         :param args:
             arguments to pass to `scene_constructor` or one of RasterCollection's
             class methods (e.g., `RasterCollection.from_multi_band_raster`)
         :param kwargs:
-            key-word arguments to pass to `scene_constructor` or one of RasterCollection's
-            class methods (e.g., `RasterCollection.from_multi_band_raster`)
+            key-word arguments to pass to `scene_constructor` or one of
+            RasterCollection's class methods (e.g.,
+            `RasterCollection.from_multi_band_raster`)
         """
         # mapper are stored in a dictionary like collection
         self._frozen = False
@@ -123,7 +125,8 @@ def _get_scene_from_key(key: str | Any) -> RasterCollection:
                 if isinstance(slice_end, datetime.date):
                     if not self.indexed_by_timestamps:
                         raise ValueError(
-                            "Cannot slice on timestamps when `indexed_by_timestamps` is False"
+                            "Cannot slice on timestamps when "
+                            "`indexed_by_timestamps` is False"
                         )
                     slice_start = list(self.collection.keys())[0].date()
                 else:
@@ -137,7 +140,8 @@ def _get_scene_from_key(key: str | Any) -> RasterCollection:
                 if isinstance(slice_start, datetime.date):
                     if not self.indexed_by_timestamps:
                         raise ValueError(
-                            "Cannot slice on timestamps when `indexed_by_timestamps` is False"
+                            "Cannot slice on timestamps when "
+                            "`indexed_by_timestamps` is False"
                         )
                     slice_end = list(self.collection.keys())[-1].date()
                 else:
@@ -163,7 +167,8 @@ def _get_scene_from_key(key: str | Any) -> RasterCollection:
             ):
                 if not self.indexed_by_timestamps:
                     raise ValueError(
-                        "Cannot slice on timestamps when `indexed_by_timestamps` is False"
+                        "Cannot slice on timestamps when "
+                        "`indexed_by_timestamps` is False"
                     )
                 out_scoll = SceneCollection()
                 for timestamp, scene in self:
@@ -193,7 +198,8 @@ def __setitem__(self, item: RasterCollection):
             raise TypeError("Only RasterCollection objects can be passed")
         if not item.is_scene:
             raise ValueError(
-                "Only RasterCollection with timestamps in their scene_properties can be passed"
+                "Only RasterCollection with timestamps in "
+                "their scene_properties can be passed"
             )
         # scenes are index by their acquisition time
         key = item.scene_properties.acquisition_time
@@ -239,7 +245,7 @@ def __repr__(self) -> str:
             else:
                 timestamps = ", ".join([str(x) for x in self.timestamps])
             return (
-                f"EOdal SceneCollection\n----------------------\n"
+                "EOdal SceneCollection\n----------------------\n"
                 + f"# Scenes:    {len(self)}\nTimestamps:    {timestamps}\n"
                 + f'Scene Identifiers:    {", ".join(self.identifiers)}'
             )
@@ -259,7 +265,8 @@ def _sort_keys(
         # check sort_direction passed
         if sort_direction not in ["asc", "desc"]:
             raise ValueError("Sort direction must be one of: `asc`, `desc`")
-        # get timestamps of the scenes and use np.argsort to bring them into the desired order
+        # get timestamps of the scenes and use np.argsort to bring them
+        # into the desired order
         timestamps = [x.scene_properties.acquisition_time for x in raster_collections]
         if sort_direction == "asc":
             sort_idx = np.argsort(timestamps)
@@ -350,13 +357,13 @@ def from_raster_collections(
         if not isinstance(raster_collections, list) and not isinstance(
             raster_collections, tuple
         ):
-            raise TypeError(f"Can only handle lists or tuples of RasterCollections")
+            raise TypeError("Can only handle lists or tuples of RasterCollections")
         if not np.array(
             [isinstance(x, RasterCollection) for x in raster_collections]
         ).all():
-            raise TypeError(f"All items passed must be RasterCollection instances")
+            raise TypeError("All items passed must be RasterCollection instances")
         if not np.array([x.is_scene for x in raster_collections]).all():
-            raise TypeError(f"All items passed must have an acquisition timestamp")
+            raise TypeError("All items passed must have an acquisition timestamp")
         # check if scenes shall be sorted
         if sort_scenes:
             sort_idx = cls._sort_keys(sort_direction, raster_collections)
@@ -479,7 +486,8 @@ def get_feature_timeseries(
             has a unique index. `False` by default.
         :param kwargs:
             key word arguments to pass to `~RasterCollection.get_pixels()` or
-            `~RasterCollection.band_summaries()` depending on the type of the input geometries.
+            `~RasterCollection.band_summaries()` depending on the type of the
+            input geometries.
         :returns:
             ``GeoDataFrame`` with extracted raster values per feature and time stamp
         """
diff --git a/eodal/core/sensors/planet_scope.py b/eodal/core/sensors/planet_scope.py
index 40e5c49b..33ecbc5d 100644
--- a/eodal/core/sensors/planet_scope.py
+++ b/eodal/core/sensors/planet_scope.py
@@ -141,9 +141,9 @@ def read_pixels(
         apply_scaling: Optional[bool] = True,
     ) -> gpd.GeoDataFrame:
         """
-        Extracts PlanetScope Super Dove raster values at locations defined by one or many
-        vector geometry features read from a vector file (e.g., ESRI shapefile) or
-        ``GeoDataFrame``.
+        Extracts PlanetScope Super Dove raster values at locations defined by one
+        or many vector geometry features read from a vector file
+        (e.g., ESRI shapefile) or ``GeoDataFrame``.
 
         :param in_dir:
             Planet Scope SuperDove scene from which to extract
@@ -195,22 +195,3 @@ def read_pixels(
                 fpath_raster=udm_file, vector_features=vector_features
             )
         return sr
-
-
-if __name__ == "__main__":
-    scene = Path(
-        "/home/graflu/public/Evaluation/Satellite_data/Planet/Eschikon/PSB_SD/analytic_8b_sr_udm2/2022/20220307_095705_17_2473"
-    )
-    band_selection = ["blue", "green", "red"]
-    parcel = Path(
-        "/home/graflu/public/Evaluation/Projects/KP0031_lgraf_PhenomEn/02_Field-Campaigns/Strickhof/WW_2022/Hohrueti.shp"
-    )
-    ds = SuperDove.from_analytic(
-        in_dir=scene, band_selection=band_selection, vector_features=parcel
-    )
-    ds.mask_non_clear_pixels()
-
-    fpath_pixels = Path(
-        "/home/graflu/public/Evaluation/Projects/KP0031_lgraf_PhenomEn/02_Field-Campaigns/Strickhof/Sampling_Points/Bramenwies.shp"
-    )
-    pixels = SuperDove.read_pixels(vector_features=fpath_pixels, in_dir=scene)
diff --git a/eodal/core/sensors/sentinel1.py b/eodal/core/sensors/sentinel1.py
index 116eea3c..0c07e876 100644
--- a/eodal/core/sensors/sentinel1.py
+++ b/eodal/core/sensors/sentinel1.py
@@ -83,7 +83,8 @@ def _get_band_files(
                     )
                 except Exception as e:
                     raise DataNotFoundError(
-                        f"Could not find data for polarization {polarization} in {in_dir}: {e}"
+                        "Could not find data for polarization "
+                        f"{polarization} in {in_dir}: {e}"
                     )
             item = {"polarization": polarization, "file_path": ref}
             band_items.append(item)
@@ -209,10 +210,3 @@ def read_pixels_from_safe(
         # to avoid (large) redundancies
         gdf = gdf.loc[:, ~gdf.columns.duplicated()]
         return gdf
-
-
-if __name__ == "__main__":
-    in_dir = Path(
-        "/home/graflu/public/Evaluation/Satellite_data/Sentinel-1/Rawdata/IW/CH/2021/S1A_IW_GRDH_1SDV_20210106T053505_20210106T053530_036013_043833_C728.SAFE"
-    )
-    s1 = Sentinel1.from_safe(in_dir=in_dir, epsg_code=4326)
diff --git a/eodal/core/sensors/sentinel2.py b/eodal/core/sensors/sentinel2.py
index 6a5b7f62..6a19c928 100644
--- a/eodal/core/sensors/sentinel2.py
+++ b/eodal/core/sensors/sentinel2.py
@@ -111,9 +111,9 @@ def _get_gain_and_offset(in_dir: Union[str, Path]) -> Tuple[Number, Number]:
         """
         # check the PDGS baseline
         baseline = get_S2_processing_baseline_from_safe(dot_safe_name=in_dir)
-        # starting with baseline N0400 (400) S2 reflectances have an offset value of -1000, i.e.,
-        # the values reported in the .jp2 files must be subtracted by 1000 to obtain the actual
-        # reflectance factor values
+        # starting with baseline N0400 (400) S2 reflectances have an offset
+        # value of -1000, i.e., the values reported in the .jp2 files must
+        # be subtracted by 1000 to obtain the actual reflectance factor values
         s2_offset = 0
         if baseline == 400:
             s2_offset = -1000
@@ -269,10 +269,11 @@ def from_safe(
         :param apply_scaling:
             apply Sentinel-2 gain and offset factor to derive reflectance values scaled
             between 0 (negative values are possible from baseline N0400 onwards) and 1
-            (default behavior). Because of the reflectance offset of -1000 introduced with
-            PDGS baseline N0400 in January 2022 applying the automatized scaling is recommended
-            to always obtain physically correct reflectance factor values - at the cost of
-            higher storage requirements because scaling converts the data to float32.
+            (default behavior). Because of the reflectance offset of -1000 introduced
+            with PDGS baseline N0400 in January 2022 applying the automatized scaling
+            is recommended to always obtain physically correct reflectance factor
+            values - at the cost of higher storage requirements because scaling converts
+            the data to float32.
         :param kwargs:
             optional key-word arguments to pass to `~eodal.core.band.Band.from_rasterio`
         :returns:
@@ -284,18 +285,17 @@ def from_safe(
         )
 
         # check the clipping extent of the raster with the lowest (coarsest) spatial
-        # resolution and remember it for all other bands with higher spatial resolutions.
+        # resolution and remember it for all other bands with higher spatial
+        # resolutions.
         # By doing so, it is ensured that all bands will be clipped to the same spatial
         # extent regardless of their pixel size. This works since all S2 bands share the
         # same coordinate origin.
         # get lowest spatial resolution (maximum pixel size) band
-        align_shapes = False
         masking_after_read_required = False
 
         if kwargs.get("vector_features") is not None:
             lowest_resolution = band_df_safe["band_resolution"].max()
             if band_df_safe["band_resolution"].unique().shape[0] > 1:
-                align_shapes = True
                 if kwargs.get("vector_features") is not None:
                     low_res_band = band_df_safe[
                         band_df_safe["band_resolution"] == lowest_resolution
@@ -317,7 +317,7 @@ def from_safe(
 
                     # drop Nones in geometry column
                     none_idx = vector_features_df[
-                        vector_features_df.geometry == None
+                        vector_features_df.geometry == None  # noqa: E711
                     ].index
                     vector_features_df.drop(index=none_idx, inplace=True)
 
@@ -396,8 +396,8 @@ def from_safe(
         kwargs.update({"area_or_point": "Area"})
         # set nodata to zero (unfortunately the S2 img metadata is incorrect here)
         kwargs.update({"nodata": 0})
-        # set correct scale factor (unfortunately not correct in S2 JP2 header but specified in
-        # the MTD_MSIL1C and MTD_MSIL2A.xml metadata document)
+        # set correct scale factor (unfortunately not correct in S2 JP2 header but
+        # specified in the MTD_MSIL1C and MTD_MSIL2A.xml metadata document)
         gain, offset = cls._get_gain_and_offset(in_dir=in_dir)
 
         # loop over bands and add them to the collection of bands
@@ -516,10 +516,11 @@ def read_pixels_from_safe(
         :param apply_scaling:
             apply Sentinel-2 gain and offset factor to derive reflectance values scaled
             between 0 (negative values are possible from baseline N0400 onwards) and 1
-            (default behavior). Because of the reflectance offset of -1000 introduced with
-            PDGS baseline N0400 in January 2022 applying the automatized scaling is recommended
-            to always obtain physically correct reflectance factor values - at the cost of
-            higher storage requirements because scaling converts the data to float32.
+            (default behavior). Because of the reflectance offset of -1000 introduced
+            with PDGS baseline N0400 in January 2022 applying the automatized scaling
+            is recommended to always obtain physically correct reflectance factor values
+            - at the cost of higher storage requirements because scaling converts the
+            data to float32.
         :returns:
             ``GeoDataFrame`` containing the extracted raster values. The band values
             are appended as columns to the dataframe. Existing columns of the input
@@ -618,7 +619,8 @@ def plot_scl(self, colormap: Optional[str] = "") -> Figure:
         # make a color map of fixed colors
         if colormap == "":
             # get only those colors required (classes in the layer)
-            # FIXME: plotting cannot really handle when values are missing in between, e.g., [0,2,3,4]
+            # FIXME: plotting cannot really handle when values are
+            # missing in between, e.g., [0,2,3,4]
             scl_colors = SCL_Classes.colors()
             scl_dict = SCL_Classes.values()
             scl_classes = list(np.unique(scl.values))
@@ -655,8 +657,8 @@ def mask_clouds_and_shadows(
             a custom classifier.
 
         NOTE:
-            You might also use the mask function from `eodal.core.raster.RasterCollection`
-            directly.
+            You might also use the mask function from
+            `eodal.core.raster.RasterCollection` directly.
 
         :param bands_to_mask:
             list of bands on which to apply the SCL mask. If not specified all bands
@@ -700,7 +702,7 @@ class of the scene classification layer. Works for data in
             class occurences.
         """
         # check if SCL is available
-        if not "scl" in self.band_names and not "SCL" in self.band_names:
+        if "scl" not in self.band_names and "SCL" not in self.band_names:
             raise BandNotFoundError(
                 "Could not find scene classification layer. Is scene L2A?"
             )
@@ -801,37 +803,3 @@ def get_cloudy_pixel_percentage(
         all_pixels = scl_stats_df["Class_Abs_Count"].sum()
         cloudy_pixel_percentage = num_cloudy_pixels / (all_pixels - nodata_pixels) * 100
         return cloudy_pixel_percentage
-
-
-if __name__ == "__main__":
-    in_dir = Path(
-        "/mnt/ides/Lukas/03_Debug/Sentinel2/S2A_MSIL2A_20171213T102431_N0206_R065_T32TMT_20171213T140708.SAFE"
-    )
-    vector_features = Path(
-        "/mnt/ides/Lukas/02_Research/PhenomEn/01_Data/01_ReferenceData/Strickhof/WW_2022/Bramenwies.shp"
-    )
-    full_bounding_box_only = True
-    s2 = Sentinel2.from_safe(
-        in_dir=in_dir,
-        vector_features=vector_features,
-        full_bounding_box_only=full_bounding_box_only,
-    )
-    resampled = s2.resample(target_resolution=10)
-    assert resampled.is_bandstack(), "raster extents still differ"
-    assert (
-        not s2.is_bandstack()
-    ), "original data must still differ in spatial resolution"
-
-    fpath_raster = in_dir.parent.joinpath("test_10m_full_bbox.jp2")
-    resampled.to_rasterio(fpath_raster, band_selection=["B03", "B12"])
-
-    full_bounding_box_only = False
-    s2 = Sentinel2.from_safe(
-        in_dir=in_dir,
-        vector_features=vector_features,
-        full_bounding_box_only=full_bounding_box_only,
-    )
-    resampled = s2.resample(target_resolution=10)
-    assert resampled.is_bandstack(), "raster extents still differ"
-    fpath_raster = in_dir.parent.joinpath("test_10m_mask.jp2")
-    resampled.to_rasterio(fpath_raster, band_selection=["B03", "B12"])
diff --git a/eodal/core/spectral_indices.py b/eodal/core/spectral_indices.py
index 7aacde70..782551b8 100644
--- a/eodal/core/spectral_indices.py
+++ b/eodal/core/spectral_indices.py
@@ -281,7 +281,7 @@ def CR(cls, collection):
         vh = collection.get(cls.vh).values.astype("float")
         vv = collection.get(cls.vv).values.astype("float")
         return vh / vv
-        
+
     @classmethod
     def NHI(cls, collection):
         """
diff --git a/eodal/core/utils/geometry.py b/eodal/core/utils/geometry.py
index c15667f7..ca89664e 100644
--- a/eodal/core/utils/geometry.py
+++ b/eodal/core/utils/geometry.py
@@ -81,13 +81,13 @@ def check_geometry_types(
 
     # check for None geometries (might happen when buffering polygons)
     if remove_empty_geoms:
-        num_none_type_geoms = gdf[gdf.geometry == None].shape[0]
+        num_none_type_geoms = gdf[gdf.geometry == None].shape[0]  # noqa: E711
         if num_none_type_geoms > 0:
             warnings.warn(
                 f"Ignoring {num_none_type_geoms} records where "
                 f"geometries are of type None"
             )
-            gdf = gdf.drop(gdf[gdf.geometry == None].index)
+            gdf = gdf.drop(gdf[gdf.geometry == None].index)  # noqa: E711
 
     # check for allowed geometry types
     gdf_aoi_geoms_types = list(gdf.geom_type.unique())
@@ -97,14 +97,16 @@ def check_geometry_types(
 
     if len(not_allowed_types) > 0:
         raise ValueError(
-            f"Encounter geometry types not allowed for reading band data: ({not_allowed_types})"
+            "Encounter geometry types not allowed for reading "
+            f"band data: ({not_allowed_types})"
         )
     return gdf
 
 
 def convert_3D_2D(geometry: gpd.GeoSeries) -> gpd.GeoSeries:
     """
-    Takes a GeoSeries of 3D Multi/Polygons (has_z) and returns a list of 2D Multi/Polygons.
+    Takes a GeoSeries of 3D Multi/Polygons (has_z) and returns a list of
+    2D Multi/Polygons.
     Snippet taken from https://gist.github.com/rmania/8c88377a5c902dfbc134795a7af538d8
     (accessed latest Jan 18th 2021)
 
diff --git a/eodal/downloader/planet_scope/client.py b/eodal/downloader/planet_scope/client.py
index de3fc388..2ad015f7 100644
--- a/eodal/downloader/planet_scope/client.py
+++ b/eodal/downloader/planet_scope/client.py
@@ -11,7 +11,7 @@
 .. code-block:: shell
 
     export PLANET_API_KEY = "<your-planet-api-key>"
-    
+
 Copyright (C) 2022 Samuel Wildhaber with some modifications by Lukas Valentin Graf
 
 This program is free software: you can redistribute it and/or modify
@@ -161,7 +161,8 @@ def quicksearch(session: Session, request_data: Dict[str, Any]) -> Response:
         response = session.post(quick_url, json=request_data)
         if response.status_code != 200:
             raise APIError(
-                f"[HTTP:{response.status_code}] Could not query {quick_url}: {response.text}"
+                f"[HTTP:{response.status_code}] Could not" +
+                f"query {quick_url}: {response.text}"
             )
         return response
 
@@ -378,7 +379,8 @@ def place_order(
 
         if not response.ok:
             raise APIError(
-                f"[HTTP:{response.status_code}]: Placing order failed: {response.content}"
+                f"[HTTP:{response.status_code}]: Placing order failed:" +
+                f"{response.content}"
             )
 
         # get order ID and return its URL
@@ -440,7 +442,8 @@ def download_order(
         for f in results:
             # split paths
             a = f["name"].rsplit("/", 1)[1]
-            # take the id part split it again and put together so we can extract the item id
+            # take the id part split it again and put together
+            # so we can extract the item id
             results_folders.append("_".join(a.split("_", 4)[:4]))
 
         # To construct the whole path, the file name is required
diff --git a/eodal/downloader/sentinel1/creodias.py b/eodal/downloader/sentinel1/creodias.py
index ad5d069f..537586fa 100644
--- a/eodal/downloader/sentinel1/creodias.py
+++ b/eodal/downloader/sentinel1/creodias.py
@@ -116,7 +116,7 @@ def query_creodias(
 
     # make sure datasets is not empty otherwise return
     if datasets.empty:
-        raise Exception(f"CREODIAS query returned empty set")
+        raise Exception("CREODIAS query returned empty set")
 
     # get *.SAFE dataset names
     datasets["dataset_name"] = datasets.properties.apply(
diff --git a/eodal/downloader/sentinel2/copernicus.py b/eodal/downloader/sentinel2/copernicus.py
index 8b62d572..af624876 100644
--- a/eodal/downloader/sentinel2/copernicus.py
+++ b/eodal/downloader/sentinel2/copernicus.py
@@ -107,7 +107,7 @@ def query_from_copernicus(
     # read file with footprint geometry and convert it to WKT
     try:
         gdf = gpd.read_file(footprint_file)
-    except Exception as e:
+    except Exception:
         logger.error(f"Could not read data from {footprint_file}")
         return pd.DataFrame()
 
diff --git a/eodal/downloader/sentinel2/creodias.py b/eodal/downloader/sentinel2/creodias.py
index 409e940b..e9597902 100644
--- a/eodal/downloader/sentinel2/creodias.py
+++ b/eodal/downloader/sentinel2/creodias.py
@@ -100,7 +100,8 @@ def query_creodias(
     processing_level_creodias = processing_level.value.replace("-", "").upper()
     # construct the REST query
     query = CREODIAS_FINDER_URL + f"maxRecords={max_records}&"
-    query += f"startDate={start_date_str}T00%3A00%3A00Z&completionDate={end_date_str}T23%3A59%3A59Z&"
+    query += f"startDate={start_date_str}T00%3A00%3A00Z&completionDate=" + \
+        f"{end_date_str}T23%3A59%3A59Z&"
     query += f"cloudCover=%5B0%2C{cloud_cover_threshold}%5D&"
     query += f"processingLevel={processing_level_creodias}&"
     query += f"geometry=POLYGON(({coord_str}))&"
@@ -117,7 +118,7 @@ def query_creodias(
 
     # make sure datasets is not empty otherwise return
     if datasets.empty:
-        raise DataNotFoundError(f"CREODIAS query returned empty set")
+        raise DataNotFoundError("CREODIAS query returned empty set")
 
     # get *.SAFE dataset names
     datasets["dataset_name"] = datasets.properties.apply(
diff --git a/eodal/downloader/utils/creodias.py b/eodal/downloader/utils/creodias.py
index c1a6f128..5e51e97e 100644
--- a/eodal/downloader/utils/creodias.py
+++ b/eodal/downloader/utils/creodias.py
@@ -112,12 +112,14 @@ def download_datasets(
             continue
 
         # download the data using the iter_content method (writes chunks to disk)
-        # check if the dataset exists already and overwrite it only if defined by the user
+        # check if the dataset exists already and overwrite it only if
+        # defined by the user
         fname = dataset.dataset_name.replace("SAFE", "zip")
         if Path(fname).exists():
             if not overwrite_existing_zips:
                 logger.info(
-                    f"{dataset.dataset_name} already downloaded - continue with next dataset"
+                    f"{dataset.dataset_name} already downloaded - " +
+                    "continue with next dataset"
                 )
                 continue
             else:
diff --git a/eodal/mapper/feature.py b/eodal/mapper/feature.py
index 86b12738..3ab85692 100644
--- a/eodal/mapper/feature.py
+++ b/eodal/mapper/feature.py
@@ -67,7 +67,7 @@ def __init__(
         """
         # check inputs
         if name == "":
-            raise ValueError(f"Empty feature names are not allowed")
+            raise ValueError("Empty feature names are not allowed")
         if type(geometry) not in allowed_geom_types:
             raise ValueError(f"geometry must of type {allowed_geom_types}")
         if type(epsg) != int or epsg <= 0:
diff --git a/eodal/mapper/mapper.py b/eodal/mapper/mapper.py
index 2e7b8b5f..e4e9795a 100644
--- a/eodal/mapper/mapper.py
+++ b/eodal/mapper/mapper.py
@@ -109,7 +109,8 @@ def __init__(
             raise ValueError("Collections must have at least 3 characters")
         if collection.count("-") > 2:
             raise ValueError(
-                f"Collections must obey the format <platform>-<sensor> where <sesor> is optional"
+                "Collections must obey the format <platform>-<sensor> where " +
+                "<sensor> is optional"
             )
         if not isinstance(feature, Feature):
             raise TypeError("Expected a Feature object")
@@ -128,8 +129,9 @@ def __init__(
     def __repr__(self) -> str:
         return (
             f"EOdal MapperConfig\n------------------\nCollection: {self.collection}"
-            + f"\nTime Range: {self.time_start} - {self.time_end}\nFeature:\n"
-            + f"{self.feature.__repr__()}\nMetadata Filters: {str(self.metadata_filters)}"
+            f"\nTime Range: {self.time_start} - {self.time_end}\nFeature:\n"
+            f"{self.feature.__repr__()}\nMetadata Filters: "
+            f"{str(self.metadata_filters)}"
         )
 
     @property
@@ -164,7 +166,7 @@ def metadata_filters(self) -> List[Filter] | None:
         return self._metadata_filters
 
     @classmethod
-    def from_yaml(cls, fpath: str | Path) -> cls:
+    def from_yaml(cls, fpath: str | Path):
         """
         Load mapping configurations from YAML file
 
@@ -253,7 +255,7 @@ def __init__(
             `sensing_time` by default.
         """
         if not isinstance(mapper_configs, MapperConfigs):
-            raise TypeError(f"Expected a MapperConfigs instance")
+            raise TypeError("Expected a MapperConfigs instance")
         self._mapper_configs = mapper_configs
         self._time_column = time_column
         self._metadata = None
@@ -441,7 +443,8 @@ def _load_scenes_collection(
             Callable used to read the scenes found into `RasterCollection` fulfilling
             the `is_scene` criterion (i.e., a time stamp is available). The callable is
             applied to all scenes found in the metadata query call.
-            By default the standard class-method call `~RasterCollection.from_multi_band_raster`
+            By default the standard class-method call
+            `~RasterCollection.from_multi_band_raster`
             is used. It can be replaced, however, with a custom-written callable that
             can be of any design except that it **MUST** accept a keyword argument
             `fpath_raster` used for reading the Scene data and `vector_features` for
@@ -524,7 +527,7 @@ def _load_scenes_collection(
                         if first_val != this_val:
                             # only string values can be merged (connected by '&&')
                             if isinstance(first_val, str):
-                                new_val = first_val + "&&" + this_val
+                                new_val = first_val + "&&" + this_val  # noqa: F841
                                 exec(f"merged_scene_properties.{scene_prop} = new_val")
 
                 scene.scene_properties = merged_scene_properties
@@ -537,7 +540,7 @@ def _load_scenes_collection(
                 except KeyError:
                     logger.warn(
                         f"Scene with ID {merged_scene_properties.product_uri} "
-                        + f"already added to SceneCollection - continue"
+                        "already added to SceneCollection - continue"
                     )
                     continue
                 update_scene_properties_list.append(merged_scene_properties)
@@ -548,9 +551,10 @@ def _load_scenes_collection(
                 subset=[self.time_column], keep="first", inplace=True
             )
             for updated_scene_properties in update_scene_properties_list:
-                # use the time stamp for finding the correct metadata records. There might be
-                # some disagreement in the milliseconds because of different precision levels
-                # therefore, an offset of less than 1 second is tolerated
+                # use the time stamp for finding the correct metadata
+                # records. Theremight be some disagreement in the milliseconds
+                # because of different precision levels therefore, an offset
+                # of less than 1 second is tolerated
                 idx = self.metadata[
                     abs(
                         self.metadata[self.time_column]
@@ -583,7 +587,7 @@ def _load_scenes_collection(
                 except KeyError:
                     logger.warn(
                         f"Scene with ID {merged_scene_properties.product_uri} "
-                        + f"already added to SceneCollection - continue"
+                        "already added to SceneCollection - continue"
                     )
                     continue
 
@@ -665,7 +669,8 @@ def load_scenes(
         # check if scenes have been queried and found
         if self.metadata is None:
             warnings.warn(
-                "No scenes are available - have you already executed Mapper.query_scenes()?"
+                "No scenes are available - have you already executed "
+                "Mapper.query_scenes()?"
             )
             return
         if self.metadata.empty:
@@ -683,9 +688,9 @@ def load_scenes(
         except KeyError as e:
             raise ValueError(f"Could not determine CRS of scenes: {e}")
 
-        # check if mosaicing scenes is required. This is done by checking the sensing_time
-        # time stamps. If there are multiple scenes with the same time stamp they must be
-        # mosaiced into a single scene
+        # check if mosaicing scenes is required. This is done by checking the
+        # sensing_time time stamps. If there are multiple scenes with the same
+        # time stamp they must be mosaiced into a single scene
         self.metadata["mosaicing"] = False
         duplicated_idx = self.metadata[
             self.metadata.duplicated([self.time_column])
diff --git a/eodal/metadata/database/db_model.py b/eodal/metadata/database/db_model.py
index 9fc39073..50f0780f 100644
--- a/eodal/metadata/database/db_model.py
+++ b/eodal/metadata/database/db_model.py
@@ -35,7 +35,9 @@
 metadata = MetaData(schema=Settings.DEFAULT_SCHEMA)
 Base = declarative_base(metadata=metadata)
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 
 
diff --git a/eodal/metadata/database/ingestion.py b/eodal/metadata/database/ingestion.py
index 8ed66fe0..9564fdbe 100644
--- a/eodal/metadata/database/ingestion.py
+++ b/eodal/metadata/database/ingestion.py
@@ -33,7 +33,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/database/querying.py b/eodal/metadata/database/querying.py
index c0bf4e6f..7fd68348 100644
--- a/eodal/metadata/database/querying.py
+++ b/eodal/metadata/database/querying.py
@@ -37,7 +37,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
@@ -103,9 +105,9 @@ def find_raw_data_by_bbox(
 
     # build the query using "db_model" as a placeholder for the actual db-model
     # of the platform
-    bounding_box_wkt = f"SRID=4326;{bounding_box.wkt}"
+    bounding_box_wkt = f"SRID=4326;{bounding_box.wkt}"  # noqa: F841
     query_statement_exc = (
-        f"""
+        """
         (session.query(db_model)
         .filter(ST_Intersects(db_model.geom, ST_GeomFromText(bounding_box_wkt)))
         .filter(
diff --git a/eodal/metadata/planet_scope/database/ingestion.py b/eodal/metadata/planet_scope/database/ingestion.py
index 9c254efc..ef74aaf7 100644
--- a/eodal/metadata/planet_scope/database/ingestion.py
+++ b/eodal/metadata/planet_scope/database/ingestion.py
@@ -29,7 +29,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/planet_scope/database/querying.py b/eodal/metadata/planet_scope/database/querying.py
index 04f3581b..05c27dbd 100644
--- a/eodal/metadata/planet_scope/database/querying.py
+++ b/eodal/metadata/planet_scope/database/querying.py
@@ -44,7 +44,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/sentinel1/database/ingestion.py b/eodal/metadata/sentinel1/database/ingestion.py
index 0eb50aaa..868dafc5 100644
--- a/eodal/metadata/sentinel1/database/ingestion.py
+++ b/eodal/metadata/sentinel1/database/ingestion.py
@@ -32,7 +32,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/sentinel1/database/querying.py b/eodal/metadata/sentinel1/database/querying.py
index 19dd5fb4..3e54a157 100644
--- a/eodal/metadata/sentinel1/database/querying.py
+++ b/eodal/metadata/sentinel1/database/querying.py
@@ -46,7 +46,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/sentinel1/parsing.py b/eodal/metadata/sentinel1/parsing.py
index b2a93f82..d21287f9 100644
--- a/eodal/metadata/sentinel1/parsing.py
+++ b/eodal/metadata/sentinel1/parsing.py
@@ -42,8 +42,8 @@
 
 def parse_s1_metadata(in_dir: Path) -> Dict:
     """
-    Parses the metadata found in the "manifest.safe" document of S1_IW_GRDH Level-1 products and
-    writes them into a Python dictionary
+    Parses the metadata found in the "manifest.safe" document of S1_IW_GRDH Level-1
+    products and writes them into a Python dictionary
 
     :param in_dir:
         file-path of the file you want to extract the metadata from
@@ -147,8 +147,8 @@ def extract_s1_footprint(in_dir: Path, use_gml: Optional[bool] = True) -> str:
     :param in_dir:
         Filepath to the S1 raw data .SAFE folder
     :param use_gml:
-        Should the GML coordinates (from the manifest.safe) be used, or the KML coordinates (from
-        the ./preview/map-overlay.kml)
+        Should the GML coordinates (from the manifest.safe) be used, or the KML
+        coordinates (from the ./preview/map-overlay.kml)
     :returns:
         Well-known-text (WKT) of the S1 mapper' footprint in geographic coordinates
         (WGS84, EPSG:4326).
@@ -188,7 +188,7 @@ def extract_s1_footprint(in_dir: Path, use_gml: Optional[bool] = True) -> str:
         kml_poly = Polygon(LinearRing(kml_coords))
         wkt = kml_poly.wkt
 
-    out_wkt = f"SRID=4326;"
+    out_wkt = "SRID=4326;"
     out_wkt += wkt
 
     return out_wkt
@@ -232,7 +232,8 @@ def loop_s1_archive(
     if get_newest_datasets:
         if last_execution_date is None:
             raise InputError(
-                "A timestamp must be provided when the only newest datasets shall be considered"
+                "A timestamp must be provided when the only newest datasets "
+                "shall be considered"
             )
 
     # search for .SAFE subdirectories identifying the single mapper
@@ -257,7 +258,8 @@ def loop_s1_archive(
         s1_scenes = filtered_scenes
         if len(s1_scenes) == 0:
             raise DataNotFoundError(
-                f'No mapper younger than {datetime.strftime(last_execution_date, "%Y-%m-%d")} found'
+                'No mapper younger than ' +
+                f'{datetime.strftime(last_execution_date, "%Y-%m-%d")} found'
             )
 
     # loop over the mapper
@@ -278,22 +280,3 @@ def loop_s1_archive(
 
     # convert to pandas dataframe and return
     return pd.DataFrame(metadata_scenes)
-
-
-# if __name__ == '__main__':
-#
-#     from eodal.metadata.sentinel1.parsing import parse_s1_metadata
-#     from eodal.metadata.sentinel1.database.ingestion import meta_df_to_database
-#     from pathlib import Path
-#
-#     years = [x for x in range(2019,2022)]
-#     instrument_mode = 'IW'
-#     for year in years:
-#         in_dir = Path(f'/home/graflu/public/Evaluation/Satellite_data/Sentinel-1/Rawdata/{instrument_mode}/CH/{year}')
-#         metadata = loop_s1_archive(in_dir)
-#
-#         metadata["storage_device_ip"] = "//hest.nas.ethz.ch/green_groups_kp_public"
-#         metadata["storage_device_ip_alias"] = "//nas12.ethz.ch/green_groups_kp_public"
-#         metadata["storage_share"] = f"Evaluation/Satellite_data/Sentinel-1/Rawdata/{instrument_mode}/CH/{year}"
-#
-#         meta_df_to_database(metadata)
diff --git a/eodal/metadata/sentinel2/database/ingestion.py b/eodal/metadata/sentinel2/database/ingestion.py
index 9e9d95c6..911c5335 100644
--- a/eodal/metadata/sentinel2/database/ingestion.py
+++ b/eodal/metadata/sentinel2/database/ingestion.py
@@ -35,7 +35,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/sentinel2/database/querying.py b/eodal/metadata/sentinel2/database/querying.py
index 2026d9e0..c1b770c7 100644
--- a/eodal/metadata/sentinel2/database/querying.py
+++ b/eodal/metadata/sentinel2/database/querying.py
@@ -48,7 +48,9 @@
 Settings = get_settings()
 logger = Settings.logger
 
-DB_URL = f"postgresql://{Settings.DB_USER}:{Settings.DB_PW}@{Settings.DB_HOST}:{Settings.DB_PORT}/{Settings.DB_NAME}"
+DB_URL = f"postgresql://{Settings.DB_USER}:" + \
+    f"{Settings.DB_PW}@{Settings.DB_HOST}:" + \
+    f"{Settings.DB_PORT}/{Settings.DB_NAME}"
 engine = create_engine(DB_URL, echo=Settings.ECHO_DB)
 session = sessionmaker(bind=engine)()
 
diff --git a/eodal/metadata/sentinel2/parsing.py b/eodal/metadata/sentinel2/parsing.py
index 6bb57df7..253bed4c 100644
--- a/eodal/metadata/sentinel2/parsing.py
+++ b/eodal/metadata/sentinel2/parsing.py
@@ -379,7 +379,7 @@ def get_scene_footprint(sensor_data: dict) -> str:
     lly, llx = transformer.transform(xx=llx, yy=lly)
     lry, lrx = transformer.transform(xx=lrx, yy=lry)
 
-    wkt = f"SRID=4326;"
+    wkt = "SRID=4326;"
     wkt += f"POLYGON(({ulx} {uly},{urx} {ury},{lrx} {lry},{llx} {lly},{ulx} {uly}))"
 
     return wkt
@@ -495,7 +495,8 @@ def loop_s2_archive(
     if get_newest_datasets:
         if last_execution_date is None:
             raise InputError(
-                "A timestamp must be provided when the only newest datasets shall be considered"
+                "A timestamp must be provided when the only newest " +
+                "datasets shall be considered"
             )
 
     # search for .SAFE subdirectories identifying the single mapper
@@ -523,7 +524,8 @@ def loop_s2_archive(
         s2_scenes = filtered_scenes
         if len(s2_scenes) == 0:
             raise NothingToDo(
-                f'No mapper younger than {datetime.strftime(last_execution_date, "%Y-%m-%d")} found'
+                'No mapper younger than ' +
+                f'{datetime.strftime(last_execution_date, "%Y-%m-%d")} found'
             )
 
     # loop over the mapper
diff --git a/eodal/metadata/stac/client.py b/eodal/metadata/stac/client.py
index 7a98d7f6..23f37a74 100644
--- a/eodal/metadata/stac/client.py
+++ b/eodal/metadata/stac/client.py
@@ -11,6 +11,7 @@
 from datetime import datetime
 from pystac_client import Client
 from pystac_client.stac_api_io import StacApiIO
+from requests.adapters import HTTPAdapter, Retry
 from shapely.geometry import box, Polygon
 from typing import Any, Dict, List
 
@@ -49,8 +50,23 @@ def query_stac(
     """
     # open connection to STAC server (specify custom CA_BUNDLE if required)
     stac_api_io = StacApiIO()
+
+    # ..versionadd:: 0.2.1
+    # add retries in case of HTTP 502, 503 and 504 errors
+    # TODO this should be changed once
+    # https://github.com/stac-utils/pystac-client/issues/520 is solved
+    retries = Retry(
+        total=Settings.NUMBER_HTTPS_RETRIES,
+        backoff_factor=1,
+        status_forcelist=[502, 503, 504])
+    stac_api_io.session.mount("http://", HTTPAdapter(max_retries=retries))
+
+    # handle certificate bundle
     stac_api_io.session.verify = Settings.STAC_API_IO_CA_BUNDLE
+
+    # setup the client
     cat = Client.from_file(Settings.STAC_BACKEND.URL, stac_io=stac_api_io)
+
     # transform dates into the required format (%Y-%m-%d)
     datestr = f'{time_start.strftime("%Y-%m-%d")}/{time_end.strftime("%Y-%m-%d")}'
     # search datasets on catalog
diff --git a/eodal/metadata/utils.py b/eodal/metadata/utils.py
index 63444c31..db843e72 100644
--- a/eodal/metadata/utils.py
+++ b/eodal/metadata/utils.py
@@ -101,7 +101,8 @@ def reconstruct_path(
 
             share = mount_point.joinpath(record.storage_share)
 
-        # Windows does not know about mount points, it should be able to work with network paths
+        # Windows does not know about mount points, it should be able to work
+        # with network paths
         elif os.name == "nt":
             share = Path(record.storage_device_ip).joinpath(record.storage_share)
             # if share is not available test alias if available
diff --git a/eodal/utils/constants/sentinel2.py b/eodal/utils/constants/sentinel2.py
index 27177b26..6bbb0f23 100644
--- a/eodal/utils/constants/sentinel2.py
+++ b/eodal/utils/constants/sentinel2.py
@@ -22,7 +22,7 @@
 
 
 # available processing levels
-class ProcessingLevels(Enum):
+class ProcessingLevels(Enum):  # noqa: F811
     L1C = "LEVEL1C"
     L2A = "LEVEL2A"
 
diff --git a/eodal/utils/decorators.py b/eodal/utils/decorators.py
index ce8136a0..9899f84c 100644
--- a/eodal/utils/decorators.py
+++ b/eodal/utils/decorators.py
@@ -89,7 +89,7 @@ def wrapper(*args, **kwargs):
         if kwargs != {}:
             processing_level = kwargs.get("processing_level", processing_level)
 
-        if not processing_level in Settings.PROCESSING_LEVELS:
+        if processing_level not in Settings.PROCESSING_LEVELS:
             raise UnknownProcessingLevel(
                 f"{processing_level} is not part of {Settings.PROCESSING_LEVELS}"
             )
@@ -158,7 +158,7 @@ def wrapper(self, *args, **kwargs):
         # if no band aliasing is enabled the passed name must be in band names
         else:
             if isinstance(band_names, str):
-                if not band_names in self.band_names:
+                if band_names not in self.band_names:
                     raise BandNotFoundError(f"{band_names} not found in collection")
             elif isinstance(band_names, list):
                 if not set(band_names).issubset(self.band_names):
diff --git a/eodal/utils/geometry.py b/eodal/utils/geometry.py
index 2118b7cf..f137aec3 100644
--- a/eodal/utils/geometry.py
+++ b/eodal/utils/geometry.py
@@ -38,7 +38,7 @@ def box_to_geojson(gdf: gpd.GeoDataFrame | Polygon) -> str:
     # GeoJSON should be in geographic coordinates
     if isinstance(gdf, gpd.GeoDataFrame):
         _gdf = deepcopy(gdf)
-        gdf_wgs84 = gdf.to_crs(epsg=4326)
+        gdf_wgs84 = _gdf.to_crs(epsg=4326)
         bbox = gdf_wgs84.total_bounds
         bbox_poly = box(*bbox)
     elif isinstance(gdf, Polygon) or isinstance(gdf, Point):
diff --git a/eodal/utils/sentinel2.py b/eodal/utils/sentinel2.py
index 39166b2b..2a3e6a9e 100644
--- a/eodal/utils/sentinel2.py
+++ b/eodal/utils/sentinel2.py
@@ -169,7 +169,7 @@ def get_S2_bandfiles(
         if is_L2A:
             search_pattern = f"GRANULE/*/IM*/R{int(resolution)}m/*B*.jp2"
         else:
-            search_pattern = f"GRANULE/*/IM*/*B*.jp2"
+            search_pattern = "GRANULE/*/IM*/*B*.jp2"
     files = glob.glob(str(in_dir.joinpath(search_pattern)))
     return [Path(x) for x in files]
 
@@ -267,14 +267,16 @@ def get_S2_bandfiles_with_res(
         else:
             # search expression for the file depends on the processing level
             if is_l2a:
-                search_expr = f"GRANULE/*/IMG_DATA/R{int(band_res)}m/*_{band_name.upper()}_{int(band_res)}m.jp2"
+                search_expr = f"GRANULE/*/IMG_DATA/R{int(band_res)}m/*_" + \
+                                f"{band_name.upper()}_{int(band_res)}m.jp2"
             else:
                 search_expr = f"GRANULE/*/IMG_DATA/T*_{band_name.upper()}.jp2"
             try:
                 band_fpath = next(in_dir.glob(search_expr))
             except Exception as e:
                 raise BandNotFoundError(
-                    f"Could not determine file-path of {band_name} from {in_dir.name}: {e}"
+                    f"Could not determine file-path of {band_name} "
+                    f"from {in_dir.name}: {e}"
                 )
         band_props["band_name"] = band_name
         band_props["band_path"] = band_fpath
diff --git a/pyproject.toml b/pyproject.toml
index 673163f0..a4a96d84 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["setuptools", "wheel", "setuptools_scm"]
+requires = ["setuptools", "wheel"]
 build-backend = "setuptools.build_meta"
 
 # pyproject.toml
diff --git a/requirements-docs.txt b/requirements-docs.txt
index 728f48c1..b5a51baf 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -1,9 +1,7 @@
 myst-parser
 pypandoc>=1.6.3
-readthedocs-sphinx-search; python_version>='3.8'
-sphinx>=3.5.4
+readthedocs-sphinx-search; python_version>='3.6'
+sphinx>=3.5.4,<6
 sphinx-autobuild
 sphinx_book_theme
 watchdog<1.0.0; python_version<'3.6'
-setuptools_scm
-
diff --git a/scripts/cloud_optimized_geoTiff.py b/scripts/cloud_optimized_geoTiff.py
index 1ce9e88f..28f166cb 100644
--- a/scripts/cloud_optimized_geoTiff.py
+++ b/scripts/cloud_optimized_geoTiff.py
@@ -21,7 +21,8 @@
 from eodal.core.band import Band
 
 # link to cloud-optimized geoTiffs at Swisstopp
-dem_file = 'https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2618-1092/swissalti3d_2019_2618-1092_2_2056_5728.tif'
+dem_file = 'https://data.geo.admin.ch/ch.swisstopo.swissalti3d/' + \
+    'swissalti3d_2019_2618-1092/swissalti3d_2019_2618-1092_2_2056_5728.tif'
 
 # load resource into a Band instance and name it "Elevation"
 dem = Band.from_rasterio(fpath_raster=dem_file, band_name_dst='Elevation')
@@ -30,12 +31,12 @@
 # EOdal Band
 # ---------.
 # Name:    Elevation
-# GeoInfo:    {'epsg': 2056, 'ulx': 2618000.0, 'uly': 1093000.0, 'pixres_x': 2.0, 'pixres_y': -2.0}
+# GeoInfo:    {'epsg': 2056, 'ulx': 2618000.0, 'uly': 1093000.0, 'pixres_x': 2.0, 'pixres_y': -2.0}   # noqa: E501
 
 # fast visualization
 fig = dem.plot(
     colormap='terrain',
-    colorbar_label=f'Elevation above Mean Sea Level [m]'
+    colorbar_label='Elevation above Mean Sea Level [m]'
 )
 
 fig.savefig('../img/eodal_SwissALTI3D_sample.png', dpi=150, bbox_inches='tight')
diff --git a/scripts/download_ndvi.py b/scripts/download_ndvi.py
new file mode 100644
index 00000000..1c2152f7
--- /dev/null
+++ b/scripts/download_ndvi.py
@@ -0,0 +1,99 @@
+"""
+Script to get NDVI from Sentinel-2 for a specific area of interest
+(e.g., a field parcel) for a pre-defined time period.
+
+@author: Lukas Valentin Graf
+"""
+
+import geopandas as gpd
+import planetary_computer
+import urllib
+
+from datetime import datetime
+from eodal.config import get_settings
+from eodal.core.raster import RasterCollection
+from eodal.core.scene import SceneCollection
+from eodal.core.sensors.sentinel2 import Sentinel2
+from eodal.mapper.feature import Feature
+from eodal.mapper.filter import Filter
+from eodal.mapper.mapper import Mapper, MapperConfigs
+
+from pathlib import Path
+from typing import List
+
+
+Settings = get_settings()
+# set to False to use a local data archove
+Settings.USE_STAC = True
+
+
+def get_ndvi(
+    mapper: Mapper,
+    output_dir: Path
+) -> None:
+    """
+    Fetch Sentinel-2 scenes, calculate the NDVI and
+    save the NDVI as geoTiff files.
+
+    :param mapper:
+        eodal Mapper instance
+    :param output_dir:
+        directory where to save the GeoTiff files to
+    """
+    mapper.query_scenes()
+
+    # load the scenes available from STAC
+    scene_kwargs = {
+        'scene_constructor': Sentinel2.from_safe,
+        'scene_constructor_kwargs': {
+            'band_selection': ["B04", "B08"]}}
+    mapper.load_scenes(scene_kwargs=scene_kwargs)
+
+    # calculate the NDVI
+    for scene_uri, scene in mapper.data:
+        scene.calc_si('NDVI', inplace=True)
+        # save NDVI as GeoTiff
+        fpath_ndvi = output_dir.joinpath(
+            scene_uri.stem + '_ndvi.tif'
+        )
+        scene['ndvi'].to_rasterio(fpath_ndvi)
+
+
+if __name__ == '__main__':
+
+    # -------------------------- Paths -------------------------------------
+    # define the output directory where to save the NDVI GeoTiff files
+    output_dir = Path('data')
+
+    # user-inputs
+    # -------------------------- Collection -------------------------------
+    collection: str = 'sentinel2-msi'
+
+    # ------------------------- Time Range ---------------------------------
+    time_start: datetime = datetime(2022, 6, 1)  		# year, month, day (incl.)
+    time_end: datetime = datetime(2022, 6, 30)   		# year, month, day (incl.)
+
+    # ----------------------- Cloudy Pixel Percentage ----------------------
+    cloudy_pixel_percentage: int = 25  # percent (scene-wide)
+
+    # ---------------------- Spatial Feature  ------------------------------
+    geom = Path('data/sample_polygons/ZH_Polygon_73129_ESCH_EPSG32632.shp')
+
+    # ------------------------- Metadata Filters ---------------------------
+    metadata_filters: List[Filter] = [
+        Filter('cloudy_pixel_percentage', '<=', cloudy_pixel_percentage),
+        Filter('processing_level', '==', 'Level-2A')]
+
+    # query the scenes available (no I/O of scenes, this only fetches metadata)
+    feature = Feature.from_geoseries(gpd.read_file(geom).geometry)
+    mapper_configs = MapperConfigs(
+        collection=collection,
+        time_start=time_start,
+        time_end=time_end,
+        feature=feature,
+        metadata_filters=metadata_filters)
+
+    # now, a new Mapper instance is created
+    mapper = Mapper(mapper_configs)
+
+    get_ndvi(mapper, output_dir)
diff --git a/scripts/get_least_cloudy_s2_scene.py b/scripts/get_least_cloudy_s2_scene.py
new file mode 100644
index 00000000..84abe7b1
--- /dev/null
+++ b/scripts/get_least_cloudy_s2_scene.py
@@ -0,0 +1,111 @@
+"""
+Script to get the least cloudy Sentinel-2 scene by month
+and area of interest. The script is based on the EOdal Mapper.
+
+The scene is saved to GeoTiff alongside its granule metadata xml.
+
+@author: Lukas Valentinb Graf
+"""
+
+import geopandas as gpd
+import planetary_computer
+import urllib
+
+from datetime import datetime
+from eodal.config import get_settings
+from eodal.core.raster import RasterCollection
+from eodal.core.scene import SceneCollection
+from eodal.core.sensors.sentinel2 import Sentinel2
+from eodal.mapper.feature import Feature
+from eodal.mapper.filter import Filter
+from eodal.mapper.mapper import Mapper, MapperConfigs
+
+from pathlib import Path
+from typing import List
+
+
+Settings = get_settings()
+# set to False to use a local data archove
+Settings.USE_STAC = True
+
+
+if __name__ == '__main__':
+
+    # -------------------------- Paths -------------------------------------
+    # define the output directory
+    out_dir = Path('./data')
+
+    # user-inputs
+    # -------------------------- Collection -------------------------------
+    collection: str = 'sentinel2-msi'
+
+    # ------------------------- Time Range ---------------------------------
+    time_start: datetime = datetime(2022, 6, 1)  		# year, month, day (incl.)
+    time_end: datetime = datetime(2022, 6, 30)   		# year, month, day (incl.)
+
+    # ---------------------- Spatial Feature  ------------------------------
+    geom = \
+        'https://raw.githubusercontent.com/neffjulian/remote_sensing/main/images/coordinates/zhr_coordinates.geojson'  # noqa: E501
+
+    # ------------------------- Metadata Filters ---------------------------
+    metadata_filters: List[Filter] = [
+        Filter('cloudy_pixel_percentage', '<=', 25),
+        Filter('processing_level', '==', 'Level-2A')]
+
+    # query the scenes available (no I/O of scenes, this only fetches metadata)
+    feature = Feature.from_geoseries(gpd.read_file(geom).geometry)
+    mapper_configs = MapperConfigs(
+        collection=collection,
+        time_start=time_start,
+        time_end=time_end,
+        feature=feature,
+        metadata_filters=metadata_filters)
+    # to enhance reproducibility and provide proper documentation, the MapperConfigs
+    # can be saved as yaml (and also then be loaded again from yaml)
+    mapper_configs.to_yaml(out_dir.joinpath('sample_mapper_call.yaml'))
+
+    # now, a new Mapper instance is created
+    mapper = Mapper(mapper_configs)
+    mapper.query_scenes()
+    # the metadata is loaded into a GeoPandas GeoDataFrame
+    mapper.metadata
+
+    # get the least cloudy scene
+    mapper.metadata = mapper.metadata[
+        mapper.metadata.cloudy_pixel_percentage ==
+        mapper.metadata.cloudy_pixel_percentage.min()].copy()
+
+    # load the least cloudy scene available from STAC
+    scene_kwargs = {
+        'scene_constructor': Sentinel2.from_safe,
+        'scene_constructor_kwargs': {
+            'band_selection': ["B02", "B03", "B04", "B05", "B8A"]}}
+
+    mapper.load_scenes(scene_kwargs=scene_kwargs)
+    # the data loaded into `mapper.data` as a EOdal SceneCollection
+    # it should now contain only a single scene
+    scene = mapper.data[mapper.data.timestamps[0]]
+
+    # save the scene to disk
+    # we distinguish the 10 and 20m bands by the suffixes _10m and _20m
+    scene_10m = RasterCollection()
+    for band in ['blue', 'green', 'red']:
+        scene_10m.add_band(scene[band])
+    scene_10m.to_rasterio(out_dir.joinpath('scene_10m.tif'))
+
+    scene_20m = RasterCollection()
+    for band in ['red_edge_1', 'nir_2']:
+        scene_20m.add_band(scene[band])
+    scene_20m.to_rasterio(out_dir.joinpath('scene_20m.tif'))
+
+    # save metadata xml to disk
+    href_xml = mapper.metadata.assets.iloc[0]['granule-metadata']['href']
+    response = urllib.request.urlopen(
+        planetary_computer.sign_url(href_xml)).read()
+    fpath_xml = out_dir.joinpath(href_xml.split('/')[-1])
+    with open(fpath_xml, 'wb') as dst:
+        dst.write(response)
+
+    # plot scene
+    fig = scene_10m.plot_multiple_bands(band_selection=['blue', 'green', 'red'])
+    fig.savefig(out_dir.joinpath('scene_10m.png'), dpi=300)
diff --git a/scripts/planet_download.py b/scripts/planet_download.py
index 6574553c..ca64be60 100644
--- a/scripts/planet_download.py
+++ b/scripts/planet_download.py
@@ -10,7 +10,7 @@
 .. code-block:: shell
 
     export PLANET_API_KEY = "<your-planet-api-key>"
-    
+
 Copyright (C) 2022 Samuel Wildhaber with some modifications by Lukas Valentin Graf
 
 This program is free software: you can redistribute it and/or modify
@@ -33,9 +33,10 @@
 from pathlib import Path
 
 ###################################
-# usage example 1 - query archive by date and AOI, place order and download it once it's ready
-start_date = date(2022,7,25)
-end_date = date(2022,7,29)
+# usage example 1 - query archive by date and AOI, place order and download
+# it once it's ready
+start_date = date(2022, 7, 25)
+end_date = date(2022, 7, 29)
 bounding_box = Path('../data/sample_polygons/ZH_Polygon_73129_ESCH_EPSG32632.shp')
 order_name = f'{date.today()}_ZH_Polygon_73129'
 cloud_cover = 50.
@@ -56,7 +57,7 @@
 client.check_order_status(order_url, loop=True)
 
 # download order -> make sure the order is activated (see previous step)
-download_dir = Path('/home/graflu/public/Evaluation/Projects/KP0031_lgraf_PhenomEn/__work__')
+download_dir = Path('<enter_a_path')
 client.download_order(order_url, download_dir)
 
 ###################################
diff --git a/scripts/sentinel1_mapper.py b/scripts/sentinel1_mapper.py
index 93818300..966147e4 100644
--- a/scripts/sentinel1_mapper.py
+++ b/scripts/sentinel1_mapper.py
@@ -6,7 +6,8 @@
 (https://planetarycomputer.microsoft.com/compute).
 
 When using locally, make sure you have a valid MSPC subscription key
-(see also: https://planetarycomputer.microsoft.com/docs/concepts/sas/#when-an-account-is-needed)
+(see also:
+https://planetarycomputer.microsoft.com/docs/concepts/sas/#when-an-account-is-needed)
 
 The key must be made available to EOdal using the PC_SDK_SUBSCRIPTION_KEY
 
@@ -40,13 +41,14 @@
 if __name__ == '__main__':
 
     collection = 'sentinel1-grd'
-    time_start = datetime(2020,7,1)
-    time_end = datetime(2020,7,15)
+    # define time period
+    time_start = datetime(2020, 7, 1)
+    time_end = datetime(2020, 7, 15)
 
-    # 
+    # define input geometry
     bbox = [9.0924, 47.5992, 9.2190, 47.7295]
     geom = box(*bbox)
-    metadata_filters = [Filter('product_type','==', 'RTC')]
+    metadata_filters = [Filter('product_type', '==', 'RTC')]
 
     feature = Feature(
         name='Test Area',
@@ -67,8 +69,8 @@
     mapper.metadata
 
     scene_kwargs = {
-	    'scene_constructor': Sentinel1.from_safe,
-	    'scene_constructor_kwargs': {}
-	}
+        'scene_constructor': Sentinel1.from_safe,
+        'scene_constructor_kwargs': {}
+    }
     mapper.load_scenes(scene_kwargs=scene_kwargs)
-    f = mapper.data.plot(band_selection=['VH'], figsize=(20,10))
+    f = mapper.data.plot(band_selection=['VH'], figsize=(20, 10))
diff --git a/scripts/sentinel2_mapper.py b/scripts/sentinel2_mapper.py
index d1bf6c97..fcdf0ac4 100644
--- a/scripts/sentinel2_mapper.py
+++ b/scripts/sentinel2_mapper.py
@@ -5,12 +5,11 @@
 The script shows how to use the EOdal Mapper class that takes over
 data handling such as
 
-	* querying of spatio-temporal metadata catalogs to identify
-	  available Sentinel-2 scenes
-	* merging data from different Sentinel-2 tiles if required
-	* re-projection of imagery from one UTM zone into another
-	  if required
-	* removal of black-filled (i.e., no-data) scenes
+* querying of spatio-temporal metadata catalogs to identify
+available Sentinel-2 scenes
+* merging data from different Sentinel-2 tiles if required
+* re-projection of imagery from one UTM zone into another if required
+* removal of black-filled (i.e., no-data) scenes
 
 This script works by retrieving Sentinel-2 scenes from
 Microsoft Planetary Computer (https://planetarycomputer.microsoft.com).
@@ -49,89 +48,96 @@
 
 
 Settings = get_settings()
-# set to False to use a local data archove
+# set to False to use a local data archive
 Settings.USE_STAC = True
 
+
 def preprocess_sentinel2_scenes(
-		ds: Sentinel2,
-		target_resolution: int,
-	) -> Sentinel2:
-	"""
-	Resample Sentinel-2 scenes and mask clouds, shadows, and snow
-	based on the Scene Classification Layer (SCL).
-
-	NOTE:
-		Depending on your needs, the pre-processing function can be
-		fully customized using the full power of EOdal and its
-		interfacing libraries!
-
-	:param target_resolution:
-		spatial target resolution to resample all bands to.
-	:returns:
-		resampled, cloud-masked Sentinel-2 scene.
-	"""
-	# resample scene
-	ds.resample(inplace=True, target_resolution=target_resolution)
-	# mask clouds, shadows, and snow
-	ds.mask_clouds_and_shadows(inplace=True)
-	return ds
+    ds: Sentinel2,
+    target_resolution: int,
+) -> Sentinel2:
+    """
+    Resample Sentinel-2 scenes and mask clouds, shadows, and snow
+    based on the Scene Classification Layer (SCL).
+
+    NOTE:
+    Depending on your needs, the pre-processing function can be
+    fully customized using the full power of EOdal and its
+    interfacing libraries!
+
+    :param target_resolution:
+        spatial target resolution to resample all bands to.
+    :returns:
+        resampled, cloud-masked Sentinel-2 scene.
+    """
+    # resample scene
+    ds.resample(inplace=True, target_resolution=target_resolution)
+    # mask clouds, shadows, and snow
+    ds.mask_clouds_and_shadows(inplace=True)
+    return ds
+
 
 if __name__ == '__main__':
-	#%% user-inputs
-	# -------------------------- Collection -------------------------------
-	collection: str = 'sentinel2-msi'
-	
-	# ------------------------- Time Range ---------------------------------
-	time_start: datetime = datetime(2022,3,1)  		# year, month, day (incl.)
-	time_end: datetime = datetime(2022,6,30)   		# year, month, day (incl.)
-	
-	# ---------------------- Spatial Feature  ------------------------------
-	geom: Path = Path('../data/sample_polygons/lake_lucerne.gpkg')
-	
-	# ------------------------- Metadata Filters ---------------------------
-	metadata_filters: List[Filter] = [
-		Filter('cloudy_pixel_percentage','<', 80),
-		Filter('processing_level', '==', 'Level-2A')
-	]
-	
-	#%% query the scenes available (no I/O of scenes, this only fetches metadata)
-	feature = Feature.from_geoseries(gpd.read_file(geom).geometry)
-	mapper_configs = MapperConfigs(
-	    collection=collection,
-	    time_start=time_start,
-	    time_end=time_end,
-	    feature=feature,
-	    metadata_filters=metadata_filters
-	)
-	# to enhance reproducibility and provide proper documentation, the MapperConfigs
-	# can be saved as yaml (and also then be loaded again from yaml)
-	mapper_configs.to_yaml('../data/sample_mapper_call.yaml')
-	
-	# now, a new Mapper instance is created
-	mapper = Mapper(mapper_configs)
-	mapper.query_scenes()
-	# the metadata is loaded into a GeoPandas GeoDataFrame
-	mapper.metadata
-	
-	#%% load the scenes available from STAC
-	scene_kwargs = {
-	    'scene_constructor': Sentinel2.from_safe,
-	    'scene_constructor_kwargs': {'band_selection': ['B02', 'B03', 'B04']},
-	    'scene_modifier': preprocess_sentinel2_scenes,
-	    'scene_modifier_kwargs': {'target_resolution': 10}
-	}
-	mapper.load_scenes(scene_kwargs=scene_kwargs)
-	# the data loaded into `mapper.data` as a EOdal SceneCollection
-	mapper.data
-
-	# plot scenes in collection
-	f_scenes = mapper.data.plot(band_selection=['red', 'green', 'blue'])
-
-	# EOdal SceneCollections can be made persistent by storing them as serialized pickled
-	# objects on disk (and can be loaded from there)
-	fpath = Path('../data/sample_mapper_data.pkl')
-	with open(fpath, 'wb+') as dst:
-		dst.write(mapper.data.to_pickle())
-
-	# read data from pickled file object into SceneCollectio
-	scoll = SceneCollection.from_pickle(stream=fpath)
+
+    # user-inputs
+    # -------------------------- Collection -------------------------------
+    collection: str = 'sentinel2-msi'
+
+    # ------------------------- Time Range ---------------------------------
+    time_start: datetime = datetime(2022, 6, 1)  		# year, month, day (incl.)
+    time_end: datetime = datetime(2022, 6, 30)   		# year, month, day (incl.)
+
+    # ---------------------- Spatial Feature  ------------------------------
+    geom: Path = Path('data/sample_polygons/lake_lucerne.gpkg')
+
+    # ------------------------- Metadata Filters ---------------------------
+    metadata_filters: List[Filter] = [
+        Filter('cloudy_pixel_percentage', '<', 25),
+        Filter('processing_level', '==', 'Level-2A')
+    ]
+
+    # query the scenes available (no I/O of scenes, this only fetches metadata)
+    feature = Feature.from_geoseries(gpd.read_file(geom).geometry)
+    mapper_configs = MapperConfigs(
+        collection=collection,
+        time_start=time_start,
+        time_end=time_end,
+        feature=feature,
+        metadata_filters=metadata_filters)
+
+    # to enhance reproducibility and provide proper documentation, the MapperConfigs
+    # can be saved as yaml (and also then be loaded again from yaml)
+    mapper_configs.to_yaml('data/sample_mapper_call.yaml')
+
+    # now, a new Mapper instance is created
+    mapper = Mapper(mapper_configs)
+    mapper.query_scenes()
+    # the metadata is loaded into a GeoPandas GeoDataFrame
+    mapper.metadata
+
+    # get the least cloudy scene
+    mapper.metadata = mapper.metadata[
+          mapper.metadata.cloudy_pixel_percentage ==
+          mapper.metadata.cloudy_pixel_percentage.min()].copy()
+
+    # load the least cloudy scene available from STAC
+    scene_kwargs = {
+        'scene_constructor': Sentinel2.from_safe,
+        'scene_constructor_kwargs': {'band_selection':
+                                     ["B02", "B03", "B04", "B05", "B8A"]}}
+
+    mapper.load_scenes(scene_kwargs=scene_kwargs)
+    # the data loaded into `mapper.data` as a EOdal SceneCollection
+    mapper.data
+
+    # plot scenes in collection
+    f_scenes = mapper.data.plot(band_selection=['blue', 'green', 'red'])
+
+    # EOdal SceneCollections can be made persistent by storing them as serialized
+    # pickled objects on disk (and can be loaded from there)
+    fpath = Path('../data/sample_mapper_data.pkl')
+    with open(fpath, 'wb+') as dst:
+        dst.write(mapper.data.to_pickle())
+
+    # read data from pickled file object into SceneCollection
+    scoll = SceneCollection.from_pickle(stream=fpath)
diff --git a/scripts/update_sentinel_archive.py b/scripts/update_sentinel_archive.py
index f3e48daa..12976c48 100644
--- a/scripts/update_sentinel_archive.py
+++ b/scripts/update_sentinel_archive.py
@@ -3,8 +3,8 @@
 in your local archive for a given geographic region, time period, product type and
 sensor mode.
 
-The function not only checks if CREODIAS has new datasets available, it also automatically
-downloads them into a user-defined location.
+The function not only checks if CREODIAS has new datasets available, it also
+automatically downloads them into a user-defined location.
 
 IMPORTANT:
     In order to receive results the region (i.e., geographic extent) for which
@@ -12,9 +12,9 @@
 
 IMPORTANT:
     CREODIAS does not allow more than 2000 records (each Sentinel scene is a record)
-    to be queried at once. If you might exceed this threshold (e.g., your region is large and/or
-    your time period is long) split your query into smaller chunks by using, e.g., shorter time
-    periods for querying.
+    to be queried at once. If you might exceed this threshold (e.g., your region is
+    large and/or your time period is long) split your query into smaller chunks by
+    using, e.g., shorter time periods for querying.
 
 Copyright (C) 2022/23 Gregor Perich & Lukas Valentin Graf
 
@@ -48,11 +48,15 @@
 from eodal.downloader.utils.creodias import download_datasets
 from eodal.downloader.utils import unzip_datasets
 from eodal.metadata.database.querying import get_region
-from eodal.metadata.sentinel1.database.ingestion import meta_df_to_database as s1_meta_to_db
-from eodal.metadata.sentinel1.database.querying import find_raw_data_by_bbox as s1_db_query
+from eodal.metadata.sentinel1.database.ingestion import meta_df_to_database as \
+    s1_meta_to_db
+from eodal.metadata.sentinel1.database.querying import find_raw_data_by_bbox as \
+    s1_db_query
 from eodal.metadata.sentinel1.parsing import parse_s1_metadata
-from eodal.metadata.sentinel2.database.ingestion import meta_df_to_database as s2_meta_to_db
-from eodal.metadata.sentinel2.database.querying import find_raw_data_by_bbox as s2_db_query
+from eodal.metadata.sentinel2.database.ingestion import meta_df_to_database as \
+    s2_meta_to_db
+from eodal.metadata.sentinel2.database.querying import find_raw_data_by_bbox as \
+    s2_db_query
 from eodal.metadata.sentinel2.parsing import parse_s2_scene_metadata
 from eodal.utils.constants import ProcessingLevels
 from eodal.utils.exceptions import DataNotFoundError
@@ -65,6 +69,19 @@
     'sentinel1': ['measurement'],
     'sentinel2': ['GRANULE', 'AUX_DATA']
 }
+required_subdirectories = {
+    'sentinel1': ['measurement'],
+    'sentinel2': ['GRANULE']
+}
+
+# write a dict that returns for each month the number of days
+months = np.arange(1, 13)
+days_per_month = {
+    1: 31, 2: 28, 3: 31, 4: 30,
+    5: 31, 6: 30, 7: 31, 8: 31,
+    9: 30, 10: 31, 11: 30, 12: 31
+}
+
 
 def pull_from_creodias(
     date_start: date,
@@ -111,7 +128,7 @@ def pull_from_creodias(
         to True can save time because datasets already downloaded are ignored. NOTE:
         The function does **not** check if a dataset was downloaded completely!
     :param kwargs:
-        optional key-word arguments to pass to 
+        optional key-word arguments to pass to
         `~eodal.downloader.sentinel1.creodias.query_creodias` such sensor_mode and
         product_type
     :return:
@@ -215,10 +232,11 @@ def pull_from_creodias(
 
     return datasets_filtered
 
+
 def sentinel_creodias_update(
     sentinel_raw_data_archive: Path,
     region: str,
-    path_options: Optional[Dict[str, str]] = None,
+    path_options: Optional[Dict[str, str]] = {},
     overwrite_existing_zips: Optional[bool] = False,
     **kwargs
 ) -> None:
@@ -287,16 +305,16 @@ def sentinel_creodias_update(
     # this example shows the workflow for Sentinel-2 (Sentinel-1 would also be possible
     # but requires slightly different inputs)
     sensor = 'sentinel2'
-    
+
     # define processing level (usually L2A but also L1C works)
     processing_level = ProcessingLevels.L2A
 
-    # specifiy region, we use Switzerland (the extent of Switzerland is defined in the database)
-    region = 'CH'
+    # specifiy region, we use Switzerland (the extent of Switzerland is defined in the
+    database) region = 'CH'
 
     # the archive should be always mounted in the same way for each user
     user_name = '<your_username>'
-    s2_raw_data_archive = Path(f'/home/{user_name}/public/Evaluation/Satellite_data/Sentinel-2/Rawdata')
+    s2_raw_data_archive = Path(f'<your-path>/Satellite_data/Sentinel-2/Rawdata')
 
     # file-system specific handling: this allows to store the paths in the database
     # in such way that the dataset paths can be found from Linux and Windows machines
@@ -336,76 +354,88 @@ def sentinel_creodias_update(
             path_out = path.joinpath(f"temp_dl_{year}")
             path_out.mkdir(exist_ok=True)
 
-            # download data from CREODIAS
-            downloaded_ds = pull_from_creodias(
-                date_start=date(year,1,1),
-                date_end=date(year,12,31),
-                path_out=path_out,
-                region=region,
-                overwrite_existing_zips=overwrite_existing_zips,
-                **kwargs
-            )
-
-            if downloaded_ds.empty:
-                logger.info(f"No new datasets found for year {year} on CREODIAS")
-                continue
+            # download data from CREODIAS for each month of the year
+            for month in months:
 
-            # move the datasets into the actual SAT archive (on level up)
-            parent_dir = path_out.parent
-            for _, record in downloaded_ds.iterrows():
-                # check if the record exists first
-                if not path_out.joinpath(record.dataset_name).exists():
-                    logger.warn(f'{record.dataset_name} does not exist')
+                # check if the current month and year are in the future
+                if date(year, month, 1) > date.today():
+                    logger.info(f'{month}/{year} is in the future - skipping')
                     continue
 
-                try:
-                    shutil.move(record.dataset_name, "..")
-                    logger.info(f"Moved {record.dataset_name} to {parent_dir}")
-                except Exception as e:
-                    logger.error(f'Could not move {record.dataset_name}: {e}')
+                downloaded_ds = pull_from_creodias(
+                    date_start=date(year, month, days_per_month[month]),
+                    date_end=date(year, month, days_per_month[month]),
+                    path_out=path_out,
+                    region=region,
+                    overwrite_existing_zips=overwrite_existing_zips,
+                    **kwargs
+                )
+
+                if downloaded_ds.empty:
+                    logger.info(
+                        f"No new datasets found for {month}/{year} on CREODIAS")
                     continue
 
-                # check if the SAFE folder is complete
-                for required_subdir in required_subdirectories[sensor]:
-                    if not parent_dir.joinpath(record.dataset_name).joinpath(required_subdir).exists():
-                        logger.error(f'{record.dataset_name} has no sub-directory {required_subdir}')
-                        shutil.rmtree(parent_dir.joinpath(record.dataset_name))
+                # move the datasets into the actual SAT archive (on level up)
+                parent_dir = path_out.parent
+                for _, record in downloaded_ds.iterrows():
+                    # check if the record exists first
+                    if not path_out.joinpath(record.dataset_name).exists():
+                        logger.warn(f'{record.dataset_name} does not exist')
                         continue
 
-                # once the dataset is moved successfully parse its metadata and
-                # ingest it into the database
-                in_dir = path.joinpath(record.dataset_name)
-                try:
-                    if sensor == 'sentinel1':
-                        scene_metadata = parse_s1_metadata(in_dir=in_dir)
-                    elif sensor == 'sentinel2':
-                        scene_metadata, _ = parse_s2_scene_metadata(in_dir=in_dir)
-                except Exception as e:
-                    logger.error(f'Parsing of metadata of {record.dataset_name} failed: {e}')
-                    shutil.rmtree(in_dir)
-                    continue
+                    try:
+                        shutil.move(record.dataset_name, "..")
+                        logger.info(f"Moved {record.dataset_name} to {parent_dir}")
+                    except Exception as e:
+                        logger.error(f'Could not move {record.dataset_name}: {e}')
+                        continue
 
-                # some path handling if required
-                if path_options != {}:
+                    # check if the SAFE folder is complete
+                    for required_subdir in required_subdirectories[sensor]:
+                        if not parent_dir.joinpath(record.dataset_name).joinpath(
+                                required_subdir).exists():
+                            logger.error(
+                                f'{record.dataset_name} has no '
+                                f'sub-directory {required_subdir}')
+                            shutil.rmtree(parent_dir.joinpath(record.dataset_name))
+                            continue
+
+                    # once the dataset is moved successfully parse its metadata and
+                    # ingest it into the database
+                    in_dir = path.joinpath(record.dataset_name)
                     try:
-                        scene_metadata["storage_device_ip"] = path_options.get(
-                            "storage_device_ip", ""
-                        )
-                        scene_metadata["storage_device_ip_alias"] = path_options.get(
-                            "storage_device_ip_alias", ""
-                        )
-                        mount_point = path_options.get("mount_point", "")
-                        mount_point_replacement = path_options.get(
-                            "mount_point_replacement", ""
-                        )
-                        scene_metadata["storage_share"] = scene_metadata[
-                            "storage_share"
-                        ].replace(mount_point, mount_point_replacement)
+                        if sensor == 'sentinel1':
+                            scene_metadata = parse_s1_metadata(in_dir=in_dir)
+                        elif sensor == 'sentinel2':
+                            scene_metadata, _ = parse_s2_scene_metadata(in_dir=in_dir)
                     except Exception as e:
-                        logger.error(f'Handling path options for {record.dataset_name} failed: {e}')
+                        logger.error(
+                            f'Parsing of metadata of {record.dataset_name} '
+                            f'failed: {e}')
                         shutil.rmtree(in_dir)
                         continue
 
+                    # some path handling if required
+                    if path_options != {}:
+                        try:
+                            scene_metadata["storage_device_ip"] = \
+                                path_options.get("storage_device_ip", "")
+                            scene_metadata["storage_device_ip_alias"] = \
+                                path_options.get("storage_device_ip_alias", "")
+                            mount_point = path_options.get("mount_point", "")
+                            mount_point_replacement = \
+                                path_options.get("mount_point_replacement", "")
+                            scene_metadata["storage_share"] = scene_metadata[
+                                "storage_share"
+                            ].replace(mount_point, mount_point_replacement)
+                        except Exception as e:
+                            logger.error(
+                                f'Handling path options for {record.dataset_name} '
+                                f'failed: {e}')
+                            shutil.rmtree(in_dir)
+                            continue
+
                     # database insert
                     try:
                         scene_metadata = pd.DataFrame([scene_metadata])
@@ -418,10 +448,11 @@ def sentinel_creodias_update(
                         )
                     except Exception as e:
                         logger.error(
-                            f'Could not ingest scene metadata for {record.dataset_name} into DB: {e}'
+                            'Could not ingest scene metadata for '
+                            f'{record.dataset_name} into DB: {e}'
                         )
                         shutil.rmtree(in_dir)
                         continue
 
-            # delete the temp_dl directory
-            shutil.rmtree(path_out)
+                # delete the temp_dl directory
+                shutil.rmtree(path_out)
diff --git a/setup.py b/setup.py
index 078c8a5a..d1c3495d 100755
--- a/setup.py
+++ b/setup.py
@@ -135,6 +135,7 @@ def run(self):
 
         sys.exit()
 
+
 setup(
     name='eodal',
     # setup_requires=['setuptools_scm'],
@@ -142,9 +143,9 @@ def run(self):
     version='0.2.0',
     description='The Earth Observation Data Analysis Library EOdal',
     long_description=long_description,
-    # long_description='*A truely open-source package for unified analysis of Earth Observation (EO) data\nCloud-native by design providing access to Petabytes of EO data',
     long_description_content_type='text/markdown',
-    author='Group of Crop Science, ETH Zurich & EOA-Team Agroscope Reckenholz, Zurich, Switzerland',
+    author='Group of Crop Science, ETH Zurich & EOA-Team Agroscope Reckenholz, ' + \
+    'Zurich, Switzerland',
     author_email='',
     install_requires=install_requires,
     url='https://github.com/EOA-team/eodal',
@@ -153,7 +154,7 @@ def run(self):
     include_package_data=True,
     license='GNU General Public License v3',
     package_data={'': []},
-    classifiers = [
+    classifiers = [   # noqa: E251
      "Natural Language :: English",
      "Programming Language :: Python :: 3",
      "Operating System :: OS Independent",


Test patch:
diff --git a/.github/workflows/test-release-candidate.yaml b/.github/workflows/test-release-candidate.yaml
index 333453fe..aa56b76b 100644
--- a/.github/workflows/test-release-candidate.yaml
+++ b/.github/workflows/test-release-candidate.yaml
@@ -48,19 +48,21 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, windows-latest]
-        python-version: ["3.10"]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
         include:
           - os: macos-latest
-            python-version: "3.10"
+            python-version: "3.11"
+          - os: macos-latest
+            python-version: "3.11"
     env:
       OS: ${{ matrix.os }}
       PYTHON: ${{ matrix.python-version }}
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -69,7 +71,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        python -m pip install --upgrade pip
+        python -m pip install --upgrade pip wheel
         python -m pip install flake8
         python -m pip install --editable .[test]
 
@@ -88,7 +90,7 @@ jobs:
         python -m pytest --cov=package_name --cov-report term --cov-report xml --cov-config .coveragerc --junitxml=testresults.xml
 
     - name: Upload coverage to Codecov
-      uses: codecov/codecov-action@v1
+      uses: codecov/codecov-action@v3
       with:
         flags: unittests
         env_vars: OS,PYTHON
@@ -100,7 +102,7 @@ jobs:
         python setup.py sdist bdist_wheel
 
     - name: Store wheel artifacts
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v3
       with:
         name: wheel-${{ matrix.os }}-${{ matrix.python-version }}
         path: dist/*
@@ -121,7 +123,7 @@ jobs:
 
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Download wheel artifacts
       uses: actions/download-artifact@v2
@@ -130,7 +132,7 @@ jobs:
         path: dist/
 
     - name: Store aggregated wheel artifacts
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v3
       with:
         name: wheels
         path: dist/*
@@ -140,4 +142,4 @@ jobs:
       with:
         user: __token__
         password: ${{ secrets.TEST_PYPI_API_TOKEN }}
-        repository_url: https://test.pypi.org/legacy/
+        repository_url: https://test.pypi.org/legacy/
\ No newline at end of file
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 4c9d641b..02b1ccaa 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -23,7 +23,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        python-version: ["3.10"]
+        python-version: ["3.9", "3.10"]
         include:
           - os: windows-latest
             python-version: "3.10"
@@ -32,35 +32,22 @@ jobs:
       PYTHON: ${{ matrix.python-version }}
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
+        cache: pip
 
     - name: System information
       run: python .github/workflows/system_info.py
 
-    - name: Get pip cache dir
-      id: pip-cache
-      run: echo "::set-output name=dir::$(pip cache dir)"
-
-    - name: pip cache
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.pip-cache.outputs.dir }}
-        key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('**/requirements*.txt') }}
-        restore-keys: ${{ runner.os }}-pip-
-
     - name: Install dependencies
       run: |
-        python -m pip install --upgrade pip
+        python -m pip install --upgrade pip wheel
         python -m pip install flake8
         python -m pip install --editable .[test]
-        python -m pip install pytest
-        python -m pip install pytest-cov
-        python -m pip install requests
 
     - name: Sanity check with flake8
       run: |
@@ -74,10 +61,12 @@ jobs:
 
     - name: Test with pytest
       run: |
+        python -m pip install pytest
+        python -m pip install pytest-cov
         python -m pytest --cov=eodal --cov-report term --cov-report xml --cov-config .coveragerc --junitxml=testresults.xml
 
     - name: Upload coverage to Codecov
-      uses: codecov/codecov-action@v1
+      uses: codecov/codecov-action@v3
       with:
         flags: unittests
         env_vars: OS,PYTHON
diff --git a/tests/conftest.py b/tests/conftest.py
index 1c13bbe3..85241aa6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,11 +9,13 @@
 from distutils import dir_util
 from pathlib import Path
 
+from eodal.config import get_settings
 from eodal.core.band import Band
 from eodal.core.raster import RasterCollection
 from eodal.core.scene import SceneCollection
 from eodal.downloader.utils import unzip_datasets
 
+
 @pytest.fixture
 def tmppath(tmpdir):
     '''
@@ -22,6 +24,7 @@ def tmppath(tmpdir):
     '''
     return Path(tmpdir)
 
+
 @pytest.fixture
 def datadir(tmppath, request):
     '''
@@ -41,6 +44,7 @@ def datadir(tmppath, request):
 
     return tmppath
 
+
 @pytest.fixture()
 def get_project_root_path() -> Path:
     """
@@ -48,13 +52,16 @@ def get_project_root_path() -> Path:
     """
     return Path(os.path.dirname(os.path.abspath(__file__))).parent
 
+
 @pytest.fixture
 def get_test_band(get_bandstack, get_polygons):
     """Fixture returning Band object from rasterio"""
     def _get_test_band():
+        settings = get_settings()
+        settings.USE_STAC = False
         fpath_raster = get_bandstack()
         vector_features = get_polygons()
-    
+
         band = Band.from_rasterio(
             fpath_raster=fpath_raster,
             band_idx=1,
@@ -66,6 +73,7 @@ def _get_test_band():
         return band
     return _get_test_band
 
+
 @pytest.fixture()
 def get_s2_safe_l2a(get_project_root_path):
     """
@@ -79,12 +87,13 @@ def _get_s2_safe_l2a():
         testdata_fname = testdata_dir.joinpath(
             'S2A_MSIL2A_20190524T101031_N0212_R022_T32UPU_20190524T130304.SAFE'
         )
-    
+
         # download URL
-        url = 'https://data.mendeley.com/public-files/datasets/ckcxh6jskz/files/e97b9543-b8d8-436e-b967-7e64fe7be62c/file_downloaded'
-    
+        url = \
+            'https://data.mendeley.com/public-files/datasets/ckcxh6jskz/files/e97b9543-b8d8-436e-b967-7e64fe7be62c/file_downloaded'  # noqa: E501
+
         if not testdata_fname.exists():
-        
+
             # download dataset
             r = requests.get(url, stream=True)
             r.raise_for_status()
@@ -92,14 +101,15 @@ def _get_s2_safe_l2a():
             with open(testdata_fname_zip, 'wb') as fd:
                 for chunk in r.iter_content(chunk_size=5096):
                     fd.write(chunk)
-        
+
             # unzip dataset
             unzip_datasets(download_dir=testdata_dir, platform='S2')
-            
+    
         return testdata_fname
 
     return _get_s2_safe_l2a
 
+
 @pytest.fixture()
 def get_s2_safe_l1c(get_project_root_path):
     """
@@ -113,27 +123,28 @@ def _get_s2_safe_l1c():
         testdata_fname = testdata_dir.joinpath(
             'S2B_MSIL1C_20190725T100039_N0208_R122_T33UWP_20190725T123957.SAFE'
         )
-    
+
         # download URL
-        url = 'https://data.mendeley.com/public-files/datasets/ckcxh6jskz/files/52abe583-c322-4ef1-8825-883fbfefe495/file_downloaded'
-    
+        url = 'https://data.mendeley.com/public-files/datasets/ckcxh6jskz/files/52abe583-c322-4ef1-8825-883fbfefe495/file_downloaded'  # noqa: E501
+
         if not testdata_fname.exists():
-        
+
             # download dataset
             r = requests.get(url, stream=True)
             r.raise_for_status()
-            testdata_fname_zip = str(testdata_fname).replace('.SAFE','.zip')
+            testdata_fname_zip = str(testdata_fname).replace('.SAFE', '.zip')
             with open(testdata_fname_zip, 'wb') as fd:
                 for chunk in r.iter_content(chunk_size=5096):
                     fd.write(chunk)
-        
+
             # unzip dataset
             unzip_datasets(download_dir=testdata_dir, platform='S2')
-            
+
         return testdata_fname
 
     return _get_s2_safe_l1c
 
+
 @pytest.fixture()
 def get_bandstack(get_project_root_path):
     """
@@ -149,14 +160,14 @@ def _get_bandstack():
         return testdata_fname
     return _get_bandstack
 
+
 @pytest.fixture()
 def get_points(get_project_root_path):
     """
     Returns path to test points to be used for pixel extraction
     """
-    
     def _get_points():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_points = testdata_dir.joinpath(
             Path('sample_points').joinpath('ZH_Points_2019_EPSG32632_random.shp')
@@ -164,14 +175,14 @@ def _get_points():
         return testdata_points
     return _get_points
 
+
 @pytest.fixture()
 def get_points2(get_project_root_path):
     """
     Returns path to test points to be used for pixel extraction
     """
-    
     def _get_points():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_points = testdata_dir.joinpath(
             Path('sample_points').joinpath('sampling_test_points.shp')
@@ -179,14 +190,15 @@ def _get_points():
         return testdata_points
     return _get_points
 
+
 @pytest.fixture()
 def get_points3(get_project_root_path):
     """
     Returns path to test points to be used for pixel extraction
     """
-    
+
     def _get_points():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_points = testdata_dir.joinpath(
             Path('sample_points').joinpath('BY_Points_2019_EPSG32633.shp')
@@ -194,14 +206,15 @@ def _get_points():
         return testdata_points
     return _get_points
 
+
 @pytest.fixture()
 def get_polygons(get_project_root_path):
     """
     Returns path to agricultural field polygons to use for masking
     """
-    
+
     def _get_polygons():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_polys = testdata_dir.joinpath(
             Path('sample_polygons').joinpath('ZH_Polygons_2020_ESCH_EPSG32632.shp')
@@ -209,14 +222,15 @@ def _get_polygons():
         return testdata_polys
     return _get_polygons
 
+
 @pytest.fixture()
 def get_polygons_2(get_project_root_path):
     """
     Returns path to agricultural field polygons to use for masking
     """
-    
+
     def _get_polygons():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_polys = testdata_dir.joinpath(
             Path('sample_polygons').joinpath('BY_AOI_2019_CLOUDS_EPSG32632.shp')
@@ -224,14 +238,14 @@ def _get_polygons():
         return testdata_polys
     return _get_polygons
 
+
 @pytest.fixture()
 def get_polygons_3(get_project_root_path):
     """
     Returns path to agricultural field polygons to use for masking
     """
-    
     def _get_polygons():
-        
+
         testdata_dir = get_project_root_path.joinpath('data')
         testdata_polys = testdata_dir.joinpath(
             Path('sample_polygons').joinpath('lake_lucerne.gpkg')
@@ -239,10 +253,13 @@ def _get_polygons():
         return testdata_polys
     return _get_polygons
 
+
 @pytest.fixture()
 def get_scene_collection(get_bandstack):
     """fixture returing a SceneCollection with three scenes"""
     def _get_scene_collection():
+        settings = get_settings()
+        settings.USE_STAC = False
         fpath_raster = get_bandstack()
         # open three scenes
         scene_list = []
@@ -250,6 +267,8 @@ def _get_scene_collection():
             ds = RasterCollection.from_multi_band_raster(fpath_raster=fpath_raster)
             ds.scene_properties.acquisition_time = 1000 * (i+1)
             scene_list.append(ds)
-        scoll = SceneCollection.from_raster_collections(scene_list, indexed_by_timestamps=False)
+        scoll = SceneCollection.from_raster_collections(
+            scene_list,
+            indexed_by_timestamps=False)
         return scoll
     return _get_scene_collection
diff --git a/tests/core/test_scene_collection.py b/tests/core/test_scene_collection.py
index 4757c867..ff3f23fe 100644
--- a/tests/core/test_scene_collection.py
+++ b/tests/core/test_scene_collection.py
@@ -15,17 +15,22 @@
 from shapely.geometry import Point, Polygon
 from typing import List
 
+from eodal.config import get_settings
 from eodal.core.band import Band
 from eodal.core.raster import RasterCollection
 from eodal.core.scene import SceneCollection
 from eodal.core.sensors import Sentinel2
 
+Settings = get_settings()
+Settings.USE_STAC = False
+
+
 @pytest.fixture()
 def generate_random_points():
     def _generate_random(number: int, polygon: Polygon) -> List[Point]:
         """
         Generates random points within a polygon
-    
+
         :param number:
             number of random points to create
         :param polygon:
@@ -42,7 +47,8 @@ def _generate_random(number: int, polygon: Polygon) -> List[Point]:
         return points
     return _generate_random
 
-def test_raster_is_scene(get_bandstack):
+
+def test_raster_is_scene(apply_settings, get_bandstack):
     """test the is_scene attribute of RasterCollections"""
 
     fpath_raster = get_bandstack()
@@ -55,6 +61,7 @@ def test_raster_is_scene(get_bandstack):
     ds.scene_properties.platform = 'test'
     assert ds.is_scene, 'scene metadata have been set, so it is a scene'
 
+
 def test_scene_collection(get_s2_safe_l2a, get_polygons_2, get_bandstack):
     """test scene collection constructor calls and built-ins"""
 
@@ -90,7 +97,8 @@ def test_scene_collection(get_s2_safe_l2a, get_polygons_2, get_bandstack):
     rcoll_id = scoll[scoll.identifiers[0]]
     assert isinstance(rcoll_id, RasterCollection), 'expected a raster collection'
     assert not rcoll_id.empty, 'RasterCollection must not be empty'
-    assert rcoll.scene_properties.acquisition_time == rcoll_id.scene_properties.acquisition_time, \
+    assert rcoll.scene_properties.acquisition_time == \
+        rcoll_id.scene_properties.acquisition_time, \
         'selection by timestamp and identifier returned different results'
 
     # open a SceneCollection by passing a RasterCollection -> should raise an error
@@ -107,30 +115,37 @@ def test_scene_collection(get_s2_safe_l2a, get_polygons_2, get_bandstack):
     assert len(scoll) == 2, 'wrong number of scenes'
     assert len(scoll.timestamps) == len(scoll.identifiers) == len(scoll), \
         'wrong number of items'
-    assert scoll.timestamps[-1] == str(s2_ds_2.scene_properties.acquisition_time), 'wrong timestamp'
-    assert scoll.timestamps[0] == str(s2_ds_1.scene_properties.acquisition_time), 'wrong timestamp'
+    assert scoll.timestamps[-1] == str(s2_ds_2.scene_properties.acquisition_time), \
+        'wrong timestamp'
+    assert scoll.timestamps[0] == str(s2_ds_1.scene_properties.acquisition_time), \
+        'wrong timestamp'
     # add the same scene -> should raise an error
     with pytest.raises(KeyError):
         scoll.add_scene(s2_ds_2)
 
     # try working with slices
     # slice by date range
-    scoll_daterange = scoll[date(2022,1,1):date(2999,12,31)]
+    scoll_daterange = scoll[date(2022, 1, 1):date(2999, 12, 31)]
     assert isinstance(scoll_daterange, SceneCollection), 'expected a SceneCollection'
     assert len(scoll_daterange) == 1, 'expected only a single scene in collection'
     # slice by date range, open end of slice
-    scoll_daterange_openend = scoll[date(2022,1,1):]
-    assert isinstance(scoll_daterange_openend, SceneCollection), 'expected a SceneCollection'
-    assert len(scoll_daterange_openend) == 1, 'expected only a single scene in collection'
+    scoll_daterange_openend = scoll[date(2022, 1, 1):]
+    assert isinstance(scoll_daterange_openend, SceneCollection), \
+        'expected a SceneCollection'
+    assert len(scoll_daterange_openend) == 1, \
+        'expected only a single scene in collection'
     # slice by date range, open start of slice
-    scoll_daterange_openstart = scoll[:date(2022,1,1)]
-    assert isinstance(scoll_daterange_openstart, SceneCollection), 'expected a SceneCollection'
-    assert len(scoll_daterange_openstart) == 1, 'expected only a single scene in collection'
+    scoll_daterange_openstart = scoll[:date(2022, 1, 1)]
+    assert isinstance(scoll_daterange_openstart, SceneCollection), \
+        'expected a SceneCollection'
+    assert len(scoll_daterange_openstart) == 1, \
+        'expected only a single scene in collection'
     # slice outside of daterange covered by SceneCollection
-    assert scoll[date(1900,1,1):date(1901,12,31)].empty, 'SceneCollection returned must be empty'
+    assert scoll[date(1900, 1, 1):date(1901, 12, 31)].empty, \
+        'SceneCollection returned must be empty'
 
     # test deleting a scene by its timestamp
-    del(scoll[scoll.timestamps[0]])
+    del scoll[scoll.timestamps[0]]
     assert len(scoll) == 1, 'scene was not deleted'
     assert len(scoll.timestamps) == len(scoll.identifiers) == len(scoll), \
         'wrong number of items'
@@ -164,16 +179,20 @@ def test_scene_collection(get_s2_safe_l2a, get_polygons_2, get_bandstack):
 
     # sort the scene collection using its sort method
     s2_ds_3 = s2_ds_2.copy()
-    test_time = datetime.datetime(1900,1,1)
+    test_time = datetime.datetime(1900, 1, 1)
     s2_ds_3.scene_properties.acquisition_time = test_time
-    scoll = SceneCollection.from_raster_collections([s2_ds_1, s2_ds_2, s2_ds_3], sort_scenes=False)
+    scoll = SceneCollection.from_raster_collections(
+        [s2_ds_1, s2_ds_2, s2_ds_3], sort_scenes=False)
     scoll_sorted = scoll.sort()
     assert scoll_sorted.is_sorted, 'expected a sorted SceneCollection'
-    assert scoll_sorted.timestamps[0] == str(test_time), 'expected a different timestamp'
+    assert scoll_sorted.timestamps[0] == str(test_time), \
+        'expected a different timestamp'
     # sort descending
     scoll_sorted_desc = scoll.sort(sort_direction='desc')
     assert scoll_sorted_desc.is_sorted, 'expected a sorted SceneCollection'
-    assert scoll_sorted_desc.timestamps[-1] == str(test_time), 'expected a different timestamp'
+    assert scoll_sorted_desc.timestamps[-1] == str(test_time), \
+        'expected a different timestamp'
+
 
 def test_scene_collection_to_xarray(get_scene_collection):
     """convert SceneCollection to xarray"""
@@ -184,9 +203,12 @@ def test_scene_collection_to_xarray(get_scene_collection):
     assert len(xarr) == len(scoll), 'wrong length of DataArray'
     assert (xarr.time.values == scoll.timestamps).all(), 'wrong timestamps in DataArray'
     for idx in range(len(scoll)):
-        assert (xarr.values[idx,:,:,:] == scoll[scoll.timestamps[idx]].get_values()).all(), 'wrong '
+        assert (xarr.values[idx, :, :, :] ==
+                scoll[scoll.timestamps[idx]].get_values()).all(), 'wrong '
+
 
-def test_scene_collection_time_series(get_scene_collection, generate_random_points, get_polygons):
+def test_scene_collection_time_series(get_scene_collection, generate_random_points,
+                                      get_polygons):
     """time series extraction from scene collection"""
     scoll = get_scene_collection()
     # sample pixels randomly distributed within the scene collection's spatial extent
@@ -209,9 +231,11 @@ def test_scene_collection_time_series(get_scene_collection, generate_random_poin
     polygons_ts = scoll.get_feature_timeseries(vector_features=polys, method=methods)
     assert isinstance(polygons_ts, gpd.GeoDataFrame), 'expected a GeoDataFrame'
     assert polygons_ts.iloc[1]['median'] == \
-        scoll[1000]['B02'].reduce(by=polygons_ts.iloc[1].geometry, method='median')[0]['median'], \
+        scoll[1000]['B02'].reduce(
+            by=polygons_ts.iloc[1].geometry, method='median')[0]['median'], \
             'values are not the same'
 
+
 def test_dump_and_load(get_scene_collection, datadir):
     """dumping and loading SceneCollections to and from disk as pickled objects"""
     scoll = get_scene_collection()
@@ -230,6 +254,7 @@ def test_dump_and_load(get_scene_collection, datadir):
     assert scoll_reloaded_from_file.collection == scoll.collection, \
         'data in collection should be the same'
 
+
 def test_plot_scene_collection(get_scene_collection):
     """plot scenes in collection"""
     scoll = get_scene_collection()
@@ -240,6 +265,7 @@ def test_plot_scene_collection(get_scene_collection):
     f = scoll.plot(band_selection=['B8A'], eodal_plot_kwargs={'colormap': 'viridis'})
     assert isinstance(f, plt.Figure), 'expected a matplotlib figure'
 
+
 def test_clip_scene_collecton(get_scene_collection, get_polygons):
     """clip scene collection to field parcel boundaries"""
     scoll = get_scene_collection()
diff --git a/tests/core/test_sentinel2.py b/tests/core/test_sentinel2.py
index ee875c8c..5505eb86 100644
--- a/tests/core/test_sentinel2.py
+++ b/tests/core/test_sentinel2.py
@@ -18,7 +18,9 @@
 settings = get_settings()
 settings.USE_STAC = False
 
-def test_read_pixels_from_safe(get_s2_safe_l1c, get_s2_safe_l2a, get_points2, get_points3):
+
+def test_read_pixels_from_safe(get_s2_safe_l1c, get_s2_safe_l2a, get_points2,
+                               get_points3):
     """
     Tests reading pixels from a .SAFE archive using class and instance methods
     """
@@ -112,6 +114,7 @@ def test_read_pixels_from_safe(get_s2_safe_l1c, get_s2_safe_l2a, get_points2, ge
     assert gdf_classmethod.empty, 'pixel values returned although all of the are outside of the scene extent'
     assert 'SCL' in gdf_classmethod.columns, 'SCL not attempted to extract'
 
+
 def test_read_from_safe_l1c(get_s2_safe_l1c):
     """handling of Sentinel-2 data in L1C processing level from .SAFE archives"""
     in_dir = get_s2_safe_l1c()
@@ -141,6 +144,7 @@ def test_read_from_safe_l1c(get_s2_safe_l1c):
     assert len(bands) == len(band_selection), 'number of bands is wrong'
     assert 'SCL' not in bands, 'SCL band cannot be available for L1C data'
 
+
 def test_read_from_safe_with_mask_l2a(datadir, get_s2_safe_l2a, get_polygons, get_polygons_2):
     """handling Sentinel-2 data from .SAFE archives (masking)"""
     in_dir = get_s2_safe_l2a()
@@ -214,6 +218,7 @@ def test_read_from_safe_with_mask_l2a(datadir, get_s2_safe_l2a, get_polygons, ge
     assert xds.attrs['descriptions'] == tuple(handler.band_aliases), \
         'band description not set properly'
 
+
 def test_ignore_scl(datadir, get_s2_safe_l2a, get_polygons_2):
     """ignore the SCL on reading"""
     in_dir = get_s2_safe_l2a()
@@ -242,6 +247,7 @@ def test_ignore_scl(datadir, get_s2_safe_l2a, get_polygons_2):
     with pytest.raises(KeyError):
         handler['SCL'].meta
 
+
 def test_band_selections(datadir, get_s2_safe_l2a, get_polygons, get_polygons_2,
                          get_bandstack):
     """testing invalid band selections"""
@@ -255,9 +261,10 @@ def test_band_selections(datadir, get_s2_safe_l2a, get_polygons, get_polygons_2,
         handler.from_safe(
             in_dir=in_dir,
             vector_features=in_file_aoi,
-            band_selection=['B02','B13']
+            band_selection=['B02', 'B13']
         )
 
+
 def test_read_from_safe_l2a(datadir, get_s2_safe_l2a):
     """handling Sentinel-2 data from .SAFE archives (no masking)"""
     in_dir = get_s2_safe_l2a()
@@ -313,10 +320,10 @@ def test_read_from_safe_l2a(datadir, get_s2_safe_l2a):
 
     # get non-exisiting bands
     with pytest.raises(BandNotFoundError):
-        non_existing_band = reader.get_band('B01')
+        non_existing_band = reader.get_band('B01')  # noqa: F841
 
     # check the RGB
-    fig_rgb = reader.plot_multiple_bands(band_selection=['red','green','blue'])
+    fig_rgb = reader.plot_multiple_bands(band_selection=['red', 'green', 'blue'])
     assert type(fig_rgb) == Figure, 'plotting of RGB bands failed'
 
     # check the scene classification layer
@@ -333,10 +340,12 @@ def test_read_from_safe_l2a(datadir, get_s2_safe_l2a):
     )
 
     # SCL should not have changed
-    assert reader.get_values(['scl']).shape == (1, 5490,5490), 'SCL was resampled although excluded'
+    assert reader.get_values(['scl']).shape == (1, 5490, 5490), \
+        'SCL was resampled although excluded'
 
     # but B8A should have 10m resolution now
-    assert reader.get_values(['B8A']).shape == (1,10980,10980), 'B8A was not resampled although selected'
+    assert reader.get_values(['B8A']).shape == (1, 10980, 10980), \
+        'B8A was not resampled although selected'
     assert reader['B8A'].geo_info.pixres_x == 10, 'geo info was not updated'
 
     # add custom band
@@ -355,7 +364,8 @@ def test_read_from_safe_l2a(datadir, get_s2_safe_l2a):
     assert 0 <= cloudy_pixels <= 100, 'cloud pixel percentage must be between 0 and 100%'
 
     # check blackfill (there is some but not the entire scene is blackfilled)
-    assert not reader.is_blackfilled, 'blackfill detection did not work out - to many false positives'
+    assert not reader.is_blackfilled, \
+        'blackfill detection did not work out - to many false positives'
 
     # blackfill_mask = reader.get_blackfill('blue')
     # assert blackfill_mask.dtype == bool, 'A boolean mask is required for the blackfill'
diff --git a/tests/core/test_si_calculation.py b/tests/core/test_si_calculation.py
index fc275c94..3dece2ae 100644
--- a/tests/core/test_si_calculation.py
+++ b/tests/core/test_si_calculation.py
@@ -3,12 +3,13 @@
 from eodal.config import get_settings
 from eodal.core.sensors import Sentinel2
 
-settings = get_settings()
-settings.USE_STAC = False
 
 @pytest.mark.parametrize('apply_scaling', [False, True])
 def test_sentinel2_vi(get_s2_safe_l2a, get_polygons_2, apply_scaling):
 
+    settings = get_settings()
+    settings.USE_STAC = False
+
     s2_dir = get_s2_safe_l2a()
     vector_features = get_polygons_2()
 
@@ -27,4 +28,3 @@ def test_sentinel2_vi(get_s2_safe_l2a, get_polygons_2, apply_scaling):
     assert 0 <= evi.max() <= 1, 'wrong scale for EVI'
     assert -1 <= evi.min() <= 0, 'expected a negative number here'
     assert 0 <= evi.mean() <= 1, 'expected a positive number here'
-        
\ No newline at end of file
diff --git a/tests/mapper/test_mapper.py b/tests/mapper/test_mapper.py
index 64a72789..86635b93 100644
--- a/tests/mapper/test_mapper.py
+++ b/tests/mapper/test_mapper.py
@@ -72,6 +72,7 @@ def test_mapper_configs(tmppath):
         assert _filter.entity == mapper_configs_rl.metadata_filters[idx].entity, \
             'wrong filtering entity'
 
+@pytest.mark.skip(reason="At the moment, we cannot test with a DB since we have no test DB")
 @pytest.mark.parametrize(
     'collection,time_start,time_end,geom,metadata_filters',
     [(
@@ -187,7 +188,7 @@ def test_mapper_get_scenes_stac(collection, time_start, time_end, geom, metadata
 def get_mapper():
     def _get_mapper():
 
-        Settings.USE_STAC = False
+        Settings.USE_STAC = True
 
         collection = 'sentinel2-msi'
         time_start = datetime(2022,7,1)