-1

I am working on a script that let's me connect to the Sentinel satellite database to download the requested map files.

from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt

def get_available(input_geojson, user, password, date_start, date_end, satellite, sensormode, product_type):

    # LogIn
    api = SentinelAPI(user, password , 'https://scihub.copernicus.eu/dhus')

    # Input parameter of the search
    footprint = geojson_to_wkt(read_geojson(input_geojson))   # irrelevant to the question
    products = api.query(footprint,
                         date = (date_start, date_end),
                         platformname = satellite,
                         sensoroperationalmode = sensormode,
                         producttype = product_type,
                         )

My problem is depending on what kind of "satellite" input I am going to use will change what other arguments are necessary, required or even allowed. Some won't need "sensormode" and other maybe need "cloudcoverage". How would i go about writing a clean code with variable/optional arguments in a function within a function? Do I have to list every possible argument?

1
  • 3
    What's with the "in a function within a function"? What is it that determines what arguments are needed? Commented Nov 3, 2020 at 23:24

3 Answers 3

1

That api seems too tedious to use. Better group the arguments with classes.

from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt


class Satellite:
    def __init__(self, name, producttype, sensormode=None, cloudcoverage=None):
        self.name = name
        self.producttype = producttype
        self.sensormode = sensormode
        self.cloudcoverage = cloudcoverage


class SentinelConnection:
    def __init__(self, input_geojson, user, password):
        self.input_geojson = input_geojson
        self.user = user
        self.password = password
        self.api = None
        self.footprint = None

    def __enter__(self):
        self.api = SentinelAPI(self.user, self.password,
                               'https://scihub.copernicus.eu/dhus')
        self.footprint = geojson_to_wkt(read_geojson(self.input_geojson))

    def __exit__(self, exc_type, exc_val, exc_tb):
        # logout here
        pass


def get_available(conn, satellite, date_start, date_end):
    s = satellite
    products = conn.api.query(conn.footprint,
                              date=(date_start, date_end),
                              platformname=satellite,
                              sensoroperationalmode=s.sensormode,
                              producttype=s.product_type,
                              )


def main():
    with SentinelConnection("abc.json", "name", "password") as conn:
        satellite = Satellite('Sputnik X', 'military satellite')
        get_available(conn, satellite, date_start, date_end)

I have zero idea about what is a footprint. If different queries can use different footprint and the queries often reuse the same footprints, make a Location class for the foot print.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your fast answer! I will look into your code. The footprint is just a geojson file of a polygon with the coordinates I want to get the satellite images from.
1

I find Crawl Cycle's answer to be very Pythonic and beautiful, so I recommend going with that. Regardless, I had fun working on this so here's my interpretation of what you were looking for :)

import inspect


def foo_api(*, foo=None):
    print(f'foo_api: foo={foo}')

def bar_api(*, bar=None):
    print(f'bar_api: bar={bar}')

_API_BY_PARAMETERS = {
    frozenset(inspect.signature(api).parameters): api
    for api in (foo_api, bar_api)
}


def api(**kwargs):
    """Selects the correct API to call based on the given kwargs."""
    actual_params = frozenset(kwargs)
    if actual_params in _API_BY_PARAMETERS:
        actual_api = _API_BY_PARAMETERS[actual_params]
        return actual_api(**kwargs)
    else:
        param_errors = (
            (api.__name__,
             ', '.join(sorted(expected_params - actual_params)),
             ', '.join(sorted(actual_params - expected_params)))
            for expected_params, api in _API_BY_PARAMETERS.items())
        raise TypeError(
            'Arguments must match exactly with one of the APIs, but found '
            'the following differences: ' +
            '; '.join(
                f'{api_name} -> missing=[{missing}], unexpected=[{unexpected}]'
                for api_name, missing, unexpected in param_errors))

Try it Online

There are a few constraints that keep this implementation as concise as it is:

  • All API signatures must be unique.
  • All API signatures accept keyword arguments only.

Comments

0

I believe this would be solved well by **kwargs. This allows you to pass any keywork argument to a function without specifying then in the function signature ie:

def foobar(foo, **kwargs):
   bar = kwargs["bar"]
   return foo + bar


barfoo = foobar(foo='something', bar='other_thing')

1 Comment

Thank you for your answer! Using **kwargs seems to be the simplest option. I created a dictionary outside the function and will just give this as an **kwarg to the function to be used in the API. seems to be working so far

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.