Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrunato committed Feb 8, 2024
2 parents e98e094 + 2f0513a commit d0b89c6
Show file tree
Hide file tree
Showing 21 changed files with 632 additions and 207 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: '3.7'
python-version: '3.8'
- name: Run pre-commit action
uses: pre-commit/action@v2.0.0
build:
Expand All @@ -34,16 +34,16 @@ jobs:
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
python-version: '3.8'
architecture: 'x64'

- name: Setup pip cache
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: pip-3.7-${{ hashFiles('package.json') }}
key: pip-3.8-${{ hashFiles('package.json') }}
restore-keys: |
pip-3.7-
pip-3.8-
pip-
- name: Get yarn cache directory path
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
- name: Checkout source
uses: actions/checkout@v2

- name: Set up Python 3.7
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: '3.7'
python-version: '3.8'

- name: Check that the current version isn't already on PyPi
run: |
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Run Tests

on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
schedule:
- cron: '0 7 * * 1'
workflow_dispatch:

jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, '3.11']
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
architecture: 'x64'

- name: Setup pip cache
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: pip-${{ matrix.python-version }}-${{ hashFiles('package.json') }}
restore-keys: |
pip-${{ matrix.python-version }}-
pip-
- name: Install the extension
run: python -m pip install .[dev]
- name: Test with pytest
run: pytest -v
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release history

## 3.5.0 (2024-02-08)

- New `provider` filtering [(#127)](https://github.com/CS-SI/eodag-labextension/pull/127)
- New python tests [(#127)](https://github.com/CS-SI/eodag-labextension/pull/127)[(#130)](https://github.com/CS-SI/eodag-labextension/pull/130)
- Product type `title` instead of longer `abstract` displayed in dropdown list tooltip [(#131)](https://github.com/CS-SI/eodag-labextension/pull/131)
- Supported python versions starting from `3.8` to `3.11` [(#132)](https://github.com/CS-SI/eodag-labextension/pull/132)

## 3.4.0 (2023-10-12)

- Updates internal geometry format and removes exposed REST API [(#120)](https://github.com/CS-SI/eodag-labextension/pull/120)
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ Click on this icon in the left of JupyterLab interface to open EODAG-Labextensio

With displayed search form, you can enter search extent and following search criteria:

- **Provider**: the provider on which to perform the search. If no provider is selected, search will loop on providers
by [priority](https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#priority-setting), and
return the first non empty results.
- **Product type**: the searched product type. List filtering is performed using product types description keywords.
For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding description.
For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding title.
![product types](https://raw.githubusercontent.com/CS-SI/eodag-labextension/develop/notebooks/images/eodag_labext_product_types.png)

- **Date range**: minimal and maximal dates of the search temporal window.
Expand Down
155 changes: 134 additions & 21 deletions eodag_labextension/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,100 @@

"""Tornado web requests handlers"""

import json
import re

import orjson
import tornado
from eodag.rest.utils import eodag_api, get_product_types, search_products
from eodag.rest.utils import eodag_api, search_products
from eodag.utils import parse_qs
from eodag.utils.exceptions import AuthenticationError, NoMatchingProductType, UnsupportedProductType, ValidationError
from eodag.utils.exceptions import (
AuthenticationError,
NoMatchingProductType,
UnsupportedProductType,
UnsupportedProvider,
ValidationError,
)
from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join


class ProductTypeHandler(APIHandler):
"""Product type listing handler
"""Product type listing handlerd"""

@tornado.web.authenticated
def get(self):
"""Get endpoint"""
query_dict = parse_qs(self.request.query)

provider = None
if "provider" in query_dict and isinstance(query_dict["provider"], list) and len(query_dict["provider"]) > 0:
provider = query_dict.pop("provider")[0]
provider = None if not provider or provider == "null" else provider

try:
product_types = eodag_api.list_product_types(provider=provider)
except UnsupportedProvider as e:
self.set_status(400)
self.finish({"error": str(e)})
return

self.write(orjson.dumps(product_types))

.. note::

Product types endpoint filtered by provider not implemented"""
class ProvidersHandler(APIHandler):
"""Providers listing handler"""

@tornado.web.authenticated
def get(self):
"""Get endpoint"""

self.write(json.dumps(get_product_types()))
available_providers_kwargs = {}
query_dict = parse_qs(self.request.query)
if (
"product_type" in query_dict
and isinstance(query_dict["product_type"], list)
and len(query_dict["product_type"]) > 0
):
available_providers_kwargs["product_type"] = query_dict["product_type"][0]
available_providers = eodag_api.available_providers(**available_providers_kwargs)

all_providers_list = [
dict(
provider=provider,
priority=conf.priority,
description=getattr(conf, "description", None),
url=getattr(conf, "url", None),
)
for provider, conf in eodag_api.providers_config.items()
if provider in available_providers
]
all_providers_list.sort(key=lambda x: (x["priority"] * -1, x["provider"]))

returned_providers = []
if "keywords" in query_dict and isinstance(query_dict["keywords"], list) and len(query_dict["keywords"]) > 0:
# 1. List providers starting with given keyword
first_keyword = query_dict["keywords"][0].lower()
returned_providers = [p for p in all_providers_list if p["provider"].lower().startswith(first_keyword)]
providers_ids = [p["provider"] for p in returned_providers]

# 2. List providers containing given keyword
returned_providers += [
p
for p in all_providers_list
if first_keyword in p["provider"].lower() and p["provider"] not in providers_ids
]
providers_ids = [p["provider"] for p in returned_providers]

# 3. List providers containing given keyword in decription
returned_providers += [
p
for p in all_providers_list
if first_keyword in p["description"].lower() and p["provider"] not in providers_ids
]
else:
returned_providers = all_providers_list

self.write(orjson.dumps(returned_providers))


class GuessProductTypeHandler(APIHandler):
Expand All @@ -37,23 +108,59 @@ def get(self):
"""Get endpoint"""

query_dict = parse_qs(self.request.query)
guess_kwargs = {}

# ["aa bb", "cc-dd_ee"] to "*aa* *bb* *cc* **dd* *ee*"
for k, v in query_dict.items():
guess_kwargs[k] = re.sub(r"(\S+)", r"*\1*", " ".join(v).replace("-", " ").replace("_", " "))
provider = None
if "provider" in query_dict and isinstance(query_dict["provider"], list) and len(query_dict["provider"]) > 0:
provider = query_dict.pop("provider")[0]
provider = None if not provider or provider == "null" else provider

try:
# guessed product types ids
guessed_ids_list = eodag_api.guess_product_type(**guess_kwargs)
# product types with full associated metadata
guessed_list = [
dict({"ID": k}, **v) for k, v in eodag_api.product_types_config.source.items() if k in guessed_ids_list
]

self.write(json.dumps(guessed_list))
returned_product_types = []
# fetch all product types
all_product_types = eodag_api.list_product_types(provider=provider)

if (
"keywords" in query_dict
and isinstance(query_dict["keywords"], list)
and len(query_dict["keywords"]) > 0
):
# 1. List product types starting with given keywords
first_keyword = query_dict["keywords"][0].lower()
returned_product_types = [pt for pt in all_product_types if pt["ID"].lower().startswith(first_keyword)]
returned_product_types_ids = [pt["ID"] for pt in returned_product_types]

# 2. List product types containing keyword
returned_product_types += [
pt
for pt in all_product_types
if first_keyword in pt["ID"].lower() and pt["ID"] not in returned_product_types_ids
]
returned_product_types_ids += [pt["ID"] for pt in returned_product_types]

# 3. Append guessed product types
guess_kwargs = {}
# ["aa bb", "cc-dd_ee"] to "*aa* *bb* *cc* **dd* *ee*"
for k, v in query_dict.items():
guess_kwargs[k] = re.sub(r"(\S+)", r"*\1*", " ".join(v).replace("-", " ").replace("_", " "))

# guessed product types ids
guessed_ids_list = eodag_api.guess_product_type(**guess_kwargs)
# product types with full associated metadata
returned_product_types += [
pt
for pt in all_product_types
if pt["ID"] in guessed_ids_list and pt["ID"] not in returned_product_types_ids
]
else:
returned_product_types = all_product_types

self.write(orjson.dumps(returned_product_types))
except NoMatchingProductType:
self.write(json.dumps([]))
self.write(orjson.dumps([]))
except UnsupportedProvider as e:
self.set_status(400)
self.finish({"error": str(e)})
return


class SearchHandler(APIHandler):
Expand All @@ -63,13 +170,17 @@ class SearchHandler(APIHandler):
def post(self, product_type):
"""Post endpoint"""

arguments = json.loads(self.request.body)
arguments = orjson.loads(self.request.body)

# move geom to intersects parameter
geom = arguments.pop("geom", None)
if geom:
arguments["intersects"] = geom

provider = arguments.pop("provider", None)
if provider and provider != "null":
arguments["provider"] = provider

try:
response = search_products(product_type, arguments, stac_formatted=False)
except ValidationError as e:
Expand Down Expand Up @@ -121,12 +232,14 @@ def setup_handlers(web_app, url_path):
# matching patterns
host_pattern = ".*$"
product_types_pattern = url_path_join(base_url, url_path, "product-types")
providers_pattern = url_path_join(base_url, url_path, "providers")
guess_product_types_pattern = url_path_join(base_url, url_path, "guess-product-type")
search_pattern = url_path_join(base_url, url_path, r"(?P<product_type>[\w-]+)")

# handlers added for each pattern
handlers = [
(product_types_pattern, ProductTypeHandler),
(providers_pattern, ProvidersHandler),
(guess_product_types_pattern, GuessProductTypeHandler),
(MethodAndPathMatch("POST", search_pattern), SearchHandler),
]
Expand Down
Binary file modified notebooks/images/eodag_labext_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified notebooks/images/eodag_labext_product_types.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions notebooks/user_manual.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@
"\n",
"With displayed search form, you can enter search extent and following search criteria:\n",
"\n",
"* **Product type**: the searched product type. List filtering is performed using product types description keywords. For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding description.\n",
"* **Provider**: the provider on which to perform the search. If no provider is selected, search will loop on providers\n",
" by [priority](https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#priority-setting), and\n",
" return the first non empty results.\n",
"* **Product type**: the searched product type. List filtering is performed using product types description keywords. For each entry of the drop-down list, a tooltip is displayed at hovering time with corresponding title.\n",
"<img style=\"display: block;\" src=\"https://raw.githubusercontent.com/CS-SI/eodag-labextension/develop/notebooks/images/eodag_labext_product_types.png\" alt=\"product types\">\n",
"\n",
"* **Date range**: minimal and maximal dates of the search temporal window.\n",
Expand Down Expand Up @@ -265,7 +268,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.10.12"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eodag-labextension",
"version": "3.4.0",
"version": "3.5.0",
"description": "Searching remote sensed imagery from various image providers",
"keywords": [
"jupyter",
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,22 @@
"tornado>=6.0.3,<7.0.0",
"notebook>=6.0.3,<7.0.0",
"eodag[notebook]>=2.8.0",
"orjson",
],
extras_require={"dev": ["black", "pre-commit"]},
extras_require={"dev": ["black", "pre-commit", "pytest"]},
zip_safe=False,
include_package_data=True,
python_requires=">=3.7",
python_requires=">=3.8",
platforms="Linux, Mac OS X, Windows",
keywords=["Jupyter", "JupyterLab", "JupyterLab3"],
classifiers=[
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Framework :: Jupyter",
],
)
Expand Down
Loading

0 comments on commit d0b89c6

Please sign in to comment.