Repository: ESA-VirES/VirES-Python-Client (language: python)

Issue:
Allow Jinja2 v3 in installation (#74)
* Allow Jinja2 v3 in installation

* Update release_notes.rst


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..cb3751c
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,7 @@
+[flake8]
+extend-ignore = E203, E501, E722, B950, T001
+select = C,E,F,W,T,B,B9,I
+per-file-ignores =
+    tests/*: T
+    noxfile.py: T
+    __init__.py:F401
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 0a5f4a0..ee6f2d9 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -1,25 +1,41 @@
 name: CI
 
+
 on:
-  push:
-    branches: [ staging ]
+  workflow_dispatch:
   pull_request:
+  push:
     branches: [ staging ]
-  workflow_dispatch:
 
 jobs:
-  run-tests:
-    runs-on: ${{ matrix.operating-system }}
+  pre-commit:
+    name: Format
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: actions/setup-python@v2
+    - uses: pre-commit/action@v2.0.3
+      with:
+        extra_args: --hook-stage manual --all-files
+
+  checks:
+    name: Check Python ${{ matrix.python-version }} on ${{ matrix.runs-on }}
+    runs-on: ${{ matrix.runs-on }}
     strategy:
+      fail-fast: false
       matrix:
-        operating-system:
-          - ubuntu-latest
-          - windows-latest
-          - macOS-latest
+        python-version: ["3.6", "3.10"]
+        runs-on: [ubuntu-latest, macos-latest, windows-latest]
     steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-      - name: Setup Nox
-        uses: excitedleigh/setup-nox@v2.0.0
-      - name: Run Nox
-        run: nox
+    - uses: actions/checkout@v2
+    - uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install package
+      run: python -m pip install .[test]
+    - name: Configure token access
+      run: |
+        viresclient set_token "https://vires.services/ows" ${{ secrets.VIRES_TOKEN_SWARM }}
+        viresclient set_default_server https://vires.services/ows
+    - name: Test package
+      run: python -m pytest -ra
diff --git a/.gitignore b/.gitignore
index 4341043..9d7bc0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,4 @@ MANIFEST
 docs/_build/
 
 # nox
-.nox
\ No newline at end of file
+.nox
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..1ebbc02
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,94 @@
+repos:
+- repo: https://github.com/psf/black
+  rev: 22.3.0
+  hooks:
+  - id: black-jupyter
+
+- repo: https://github.com/kynan/nbstripout
+  rev: "0.5.0"
+  hooks:
+    - id: nbstripout
+
+- repo: https://github.com/pre-commit/pre-commit-hooks
+  rev: v4.1.0
+  hooks:
+  - id: check-added-large-files
+  - id: check-case-conflict
+  - id: check-merge-conflict
+  - id: check-symlinks
+  - id: check-yaml
+  - id: debug-statements
+  - id: end-of-file-fixer
+  - id: mixed-line-ending
+  - id: requirements-txt-fixer
+  - id: trailing-whitespace
+
+- repo: https://github.com/pre-commit/pygrep-hooks
+  rev: v1.9.0
+  hooks:
+  - id: python-check-blanket-noqa
+  # - id: python-check-blanket-type-ignore
+  - id: python-no-eval
+  - id: python-use-type-annotations
+  - id: rst-backticks
+  - id: rst-directive-colons
+  - id: rst-inline-touching-normal
+
+- repo: https://github.com/PyCQA/isort
+  rev: 5.10.1
+  hooks:
+  - id: isort
+    # args: ["-a", "from __future__ import annotations"]  # Python 3.7+
+
+- repo: https://github.com/asottile/pyupgrade
+  rev: v2.31.1
+  hooks:
+  - id: pyupgrade
+    args: ["--py37-plus"]
+
+# - repo: https://github.com/hadialqattan/pycln
+#   rev: v1.1.0
+#   hooks:
+#   - id: pycln
+#     args: [--config=pyproject.toml]
+
+- repo: https://github.com/asottile/yesqa
+  rev: v1.3.0
+  hooks:
+  - id: yesqa
+    exclude: docs/conf.py
+    additional_dependencies: &flake8_dependencies
+      - flake8-bugbear
+      - flake8-print
+
+- repo: https://github.com/pycqa/flake8
+  rev: 4.0.1
+  hooks:
+  - id: flake8
+    exclude: docs/conf.py
+    additional_dependencies: *flake8_dependencies
+
+# - repo: https://github.com/pre-commit/mirrors-mypy
+#   rev: v0.930
+#   hooks:
+#   - id: mypy
+#     files: src
+#     args: [--show-error-codes]
+
+# - repo: https://github.com/codespell-project/codespell
+#   rev: v2.1.0
+#   hooks:
+#   - id: codespell
+
+- repo: https://github.com/shellcheck-py/shellcheck-py
+  rev: v0.8.0.4
+  hooks:
+  - id: shellcheck
+
+- repo: local
+  hooks:
+  - id: disallow-caps
+    name: Disallow improper capitalization
+    language: pygrep
+    entry: PyBind|Numpy|Cmake|CCache|Github|PyTest
+    exclude: .pre-commit-config.yaml
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..6ea8bfa
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,21 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/conf.py
+
+# Include PDF and ePub
+formats: all
+
+python:
+  version: 3.8
+  install:
+  - method: pip
+    path: .
+    extra_requirements:
+    - docs
diff --git a/README.rst b/README.rst
index 4f6082b..17789f1 100644
--- a/README.rst
+++ b/README.rst
@@ -101,6 +101,6 @@ You can reference *viresclient* directly using the DOI of our zenodo_ record. Vi
 
 .. _zenodo: https://doi.org/10.5281/zenodo.2554162
 
-    | "We use the Python package, viresclient [1], to access [...] from ESA's VirES for Swarm service [2]"  
-    | [1] https://doi.org/10.5281/zenodo.2554162  
+    | "We use the Python package, viresclient [1], to access [...] from ESA's VirES for Swarm service [2]"
+    | [1] https://doi.org/10.5281/zenodo.2554162
     | [2] https://vires.services
diff --git a/docs/access_token.rst b/docs/access_token.rst
index f531aad..915ade6 100644
--- a/docs/access_token.rst
+++ b/docs/access_token.rst
@@ -6,14 +6,14 @@ The tokens are managed via the web user interface.
 
 Assuming you have an existing VirES account and you can access
 the VirES web client (https://vires.services or https://aeolus.services),
-you get to token manager via `Manage access tokens` item in your account menu
+you get to token manager via *Manage access tokens* item in your account menu
 (displaying your username)
 
 .. image:: images/vires_token_manager_menu.png
    :align: center
 
 The token manager user interface allows creation of new tokens by pressing the
-`Create New Access Token` button
+*Create New Access Token* button
 
 .. image:: images/vires_token_manager_create_blank.png
    :align: center
@@ -24,7 +24,7 @@ brief label indicating its purpose
 .. image:: images/vires_token_manager_create_filled.png
    :align: center
 
-Once the `Create New Access Token` button has been pressed, a new token is
+Once the *Create New Access Token* button has been pressed, a new token is
 generated and displayed to the user
 
 .. image:: images/vires_token_manager_new_token.png
@@ -42,4 +42,3 @@ The token manager list the active access tokens and allows their revocation
   *The tokens are secret.* Therefore, do not keep thier copies or share them
   with others.  When a token is needed generate new one. When a token is no
   longer needed revoke it.
-
diff --git a/docs/available_parameters.rst b/docs/available_parameters.rst
index 5588322..cc9c9e0 100644
--- a/docs/available_parameters.rst
+++ b/docs/available_parameters.rst
@@ -73,7 +73,7 @@ SW_OPER_MITx_LP_2F     MIT_LP           Minima of the Midlatitude Ionospheric Tr
 SW_OPER_MITx_LP_2F:ID  MIT_LP:ID        -> Boundaries of the MIT - derived from the LP
 SW_OPER_MITxTEC_2F     MIT_TEC          Minima of the MIT - derived from Total Electron Content (TEC)
 SW_OPER_MITxTEC_2F:ID  MIT_TEC:ID       -> Boundaries of the MIT - derived from TEC
-SW_OPER_PPIxFAC_2F     PPI_FAC          Midnight Plasmapause Index (PPI) 
+SW_OPER_PPIxFAC_2F     PPI_FAC          Midnight Plasmapause Index (PPI)
 SW_OPER_PPIxFAC_2F:ID  PPI_FAC:ID       -> Boundaries of the Small-Scale Field Aligned Currents (SSFAC)
 ====================== ================ ===================================================================================================
 
@@ -125,7 +125,7 @@ The ``measurements``, ``models``, and ``auxiliaries`` chosen will match the cade
 ----
 
 ``measurements``
----------------- 
+----------------
 
 Choose combinations of measurements from one of the following sets, corresponding to the collection chosen above. The collection full name or collection type can be given to :py:meth:`viresclient.SwarmRequest.available_measurements` to retrieve the list of available measurements for a given collection (e.g. ``request.available_measurements("SW_OPER_MAGA_LR_1B")``)
 
@@ -184,7 +184,7 @@ AUX_OBSH contains a special variable, ``ObsIndex``, which is set to 0, 1, 2 ...
 
 VOBS products:
 
-==================================== =========================================== 
+==================================== ===========================================
 Collection full name                 Available measurement names
 ==================================== ===========================================
 SW_OPER_VOBS_1M_2\_                  ``SiteCode,B_CF,B_OB,sigma_CF,sigma_OB``
@@ -205,7 +205,7 @@ Models are evaluated along the satellite track at the positions of the time seri
   For a good estimate of the ionospheric field measured by a Swarm satellite (with the core, crust and magnetosphere effects removed) use a composed model defined as:
   ``models=["'CHAOS-full' = 'CHAOS-Core' + 'CHAOS-Static' + 'CHAOS-MMA-Primary' + 'CHAOS-MMA-Secondary'"]``
   `(click for more info) <https://github.com/klaundal/notebooks/blob/master/get_external_field.ipynb>`_
-  
+
   This composed model can also be accessed by an alias: ``models=["CHAOS"]`` which represents the full CHAOS model
 
   See `Magnetic Earth <https://magneticearth.org/pages/models.html>`_ for an introduction to geomagnetic models.
@@ -292,8 +292,7 @@ NB: When using model names containing a hyphen (``-``) then extra single (``'``)
 .. note::
 
   Check other packages such as `hapiclient`_ and others from `PyHC`_ for data from other sources.
-  
+
 .. _`hapiclient`: https://github.com/hapi-server/client-python
 
 .. _`PyHC`: http://heliopython.org/projects/
-
diff --git a/docs/available_parameters_aeolus.rst b/docs/available_parameters_aeolus.rst
index 267e312..49e0524 100644
--- a/docs/available_parameters_aeolus.rst
+++ b/docs/available_parameters_aeolus.rst
@@ -2,4 +2,4 @@ Available parameters for Aeolus
 ===============================
 
 .. note::
-  Details to follow. See https://notebooks.aeolus.services for examples.
\ No newline at end of file
+  Details to follow. See https://notebooks.aeolus.services for examples.
diff --git a/docs/cli.rst b/docs/cli.rst
index 6fd7a31..eb990f9 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -143,7 +143,7 @@ Setting extra constant parameters::
       Timestamp
 
 Removing constant parameters::
- 
+
   $ viresclient clear_upload_parameters https://staging.vires.services/ows
   eff3bd47-0098-45d3-ba9e-158bea0fae12: parameters removed
 
diff --git a/docs/conf.py b/docs/conf.py
index 9408215..6c84dd3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # Configuration file for the Sphinx documentation builder.
 #
@@ -15,25 +14,12 @@
 import os
 import sys
 import urllib.request
-sys.path.insert(0, os.path.abspath('..'))
-
 
 # -- Project information -----------------------------------------------------
 
-project = 'viresclient'
-copyright = '2018, Ashley Smith'
-author = 'Ashley Smith'
-
-# The short X.Y version
-with open('../viresclient/__init__.py') as f:
-    for line in f:
-        if line.find("__version__") >= 0:
-            version = line.split("=")[1].strip()
-            version = version.strip('"')
-            version = version.strip("'")
-            continue
-# The full version, including alpha/beta/rc tags
-release = version
+project = "viresclient"
+copyright = "2018, Ashley Smith"
+author = "Ashley Smith"
 
 
 # -- General configuration ---------------------------------------------------
@@ -46,24 +32,25 @@
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.imgmath',
-    'sphinx.ext.autosectionlabel',
-    'sphinx.ext.viewcode',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+    "sphinx.ext.imgmath",
+    "sphinx.ext.autosectionlabel",
+    "sphinx_copybutton",
+    "sphinx.ext.viewcode",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
 #
 # source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -75,10 +62,10 @@
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path .
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 
 # -- Options for HTML output -------------------------------------------------
@@ -86,9 +73,7 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-# html_theme = 'alabaster'
-html_theme = 'sphinx_rtd_theme'
-html_theme_path = ["_themes", ]
+html_theme = "sphinx_book_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -112,18 +97,19 @@
 # html_sidebars = {}
 
 # Fetch and use external logo
-logo_filename = 'vre_logo_light.svg'
+logo_filename = "vre_logo_light.svg"
 if not os.path.exists(logo_filename):
     urllib.request.urlretrieve(
-        'https://raw.githubusercontent.com/ESA-VirES/Swarm-VRE/staging/docs/_static/vre_logo_light.svg',
-        logo_filename)
+        "https://raw.githubusercontent.com/ESA-VirES/Swarm-VRE/staging/docs/_static/vre_logo_light.svg",
+        logo_filename,
+    )
 html_logo = logo_filename
 
 
 # -- Options for HTMLHelp output ---------------------------------------------
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'viresclientdoc'
+htmlhelp_basename = "viresclientdoc"
 
 
 # -- Options for LaTeX output ------------------------------------------------
@@ -132,15 +118,12 @@
     # The paper size ('letterpaper' or 'a4paper').
     #
     # 'papersize': 'letterpaper',
-
     # The font size ('10pt', '11pt' or '12pt').
     #
     # 'pointsize': '10pt',
-
     # Additional stuff for the LaTeX preamble.
     #
     # 'preamble': '',
-
     # Latex figure (float) alignment
     #
     # 'figure_align': 'htbp',
@@ -150,8 +133,13 @@
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, 'viresclient.tex', 'viresclient Documentation',
-     'Ashley Smith', 'manual'),
+    (
+        master_doc,
+        "viresclient.tex",
+        "viresclient Documentation",
+        "Ashley Smith",
+        "manual",
+    ),
 ]
 
 
@@ -159,10 +147,7 @@
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'viresclient', 'viresclient Documentation',
-     [author], 1)
-]
+man_pages = [(master_doc, "viresclient", "viresclient Documentation", [author], 1)]
 
 
 # -- Options for Texinfo output ----------------------------------------------
@@ -171,7 +156,13 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    (master_doc, 'viresclient', 'viresclient Documentation',
-     author, 'viresclient', 'One line description of project.',
-     'Miscellaneous'),
+    (
+        master_doc,
+        "viresclient",
+        "viresclient Documentation",
+        author,
+        "viresclient",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt
deleted file mode 100644
index 6b28c15..0000000
--- a/docs/docs_requirements.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-pygments>=2.4.1
-sphinx_rtd_theme
-Jinja2
-pandas
-cdflib
-tables
-tqdm
-xarray
-netcdf4
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 5927c25..538b4c0 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -10,7 +10,7 @@ Get started `at the introduction <readme.html>`_
   🔗 How-To Guides [Swarm Notebooks] <https://swarm.magneticearth.org>
   🔗 VirES for Aeolus <https://aeolus.services>
   🔗 How-To Guides [Aeolus Notebooks] <https://notebooks.aeolus.services>
-  
+
 
 .. toctree::
   :maxdepth: 2
@@ -43,4 +43,4 @@ Get started `at the introduction <readme.html>`_
 
   🔗 GitHub: viresclient <https://github.com/ESA-VirES/VirES-Python-Client/>
   🔗 GitHub: Swarm-DISC <https://github.com/Swarm-DISC/>
-  🔗 Magnetic Earth <https://magneticearth.org>
\ No newline at end of file
+  🔗 Magnetic Earth <https://magneticearth.org>
diff --git a/docs/notebook_intro.rst b/docs/notebook_intro.rst
index 98c4fda..caec9e0 100644
--- a/docs/notebook_intro.rst
+++ b/docs/notebook_intro.rst
@@ -29,4 +29,4 @@ The VRE is a whole analysis suite, including many other libraries from the Pytho
       To clear any changes you made and fetch the latest version, from within ``Swarm_notebooks`` or ``Aeolus-notebooks`` run from a terminal::
 
             git fetch
-            git reset --hard origin/master
\ No newline at end of file
+            git reset --hard origin/master
diff --git a/noxfile.py b/noxfile.py
index b8368cb..b491533 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -1,7 +1,35 @@
 import nox
 
+
+@nox.session
+def lint(session):
+    """
+    Run the linter.
+    """
+    session.install("pre-commit")
+    session.run("pre-commit", "run", "--all-files", *session.posargs)
+
+
 @nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"])
-def test(session):
+def tests(session):
     session.install(".")
     session.install("pytest")
-    session.run("pytest")
\ No newline at end of file
+    session.run("pytest")
+
+
+@nox.session
+def docs(session: nox.Session) -> None:
+    """
+    Build the docs. Pass "serve" to serve.
+    """
+
+    session.install(".[docs]")
+    session.chdir("docs")
+    session.run("sphinx-build", "-M", "html", ".", "_build")
+
+    if session.posargs:
+        if "serve" in session.posargs:
+            print("Launching docs at http://localhost:8000/ - use Ctrl-C to quit")
+            session.run("python", "-m", "http.server", "8000", "-d", "_build/html")
+        else:
+            session.warn("Unsupported argument to docs")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3aee07b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,98 @@
+[build-system]
+requires = ["flit_core >=3.4"]
+build-backend = "flit_core.buildapi"
+
+
+[project.urls]
+homepage = "https://github.com/ESA-VirES/VirES-Python-Client"
+
+[project]
+name = "viresclient"
+authors = [
+    { name = "Ashley Smith", email = "ashley.smith@ed.ac.uk" },
+]
+maintainers = [
+    { name = "Ashley Smith", email = "ashley.smith@ed.ac.uk" },
+]
+license = { file = "LICENSE" }
+description = "A Python client for interacting with a VirES server"
+readme = "README.rst"
+requires-python = ">=3.6"
+classifiers = [
+    "License :: OSI Approved :: MIT License",
+    "Topic :: Scientific/Engineering",
+    "Intended Audience :: Science/Research",
+    "Intended Audience :: Developers",
+    "Operating System :: OS Independent",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Development Status :: 5 - Production/Stable",
+]
+dynamic = ["version"]
+dependencies = [
+    "cdflib >= 0.3.9",
+    "Jinja2 >= 2.10",
+    "netCDF4 >= 1.5.3",
+    "pandas >= 0.18",
+    "requests >= 2.0.0",
+    "tables >= 3.4.4",
+    "tqdm >= 4.23.0",
+    "xarray >= 0.11.0",
+]
+
+[project.optional-dependencies]
+test = [
+    "pytest >=6",
+]
+dev = [
+    "pytest >=6",
+    "nox >=2022",
+]
+docs = [
+    "Sphinx == 4.5.0",
+    "sphinx-book-theme == 0.2.0",
+    "sphinx_copybutton == 0.5.0",
+]
+
+[project.scripts]
+viresclient = "viresclient.commands.viresclient:start"
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
+xfail_strict = true
+filterwarnings = ["error"]
+testpaths = [
+    "tests",
+]
+
+[tool.pycln]
+all = true
+
+[tool.mypy]
+files = "src"
+python_version = "3.8"
+warn_unused_configs = true
+ignore_missing_imports = true
+check_untyped_defs = true
+# disallow_incomplete_defs = true
+# disallow_any_generics = true
+# disallow_subclassing_any = true
+# disallow_untyped_calls = true
+# disallow_untyped_defs = true
+# disallow_untyped_decorators = true
+# no_implicit_optional = true
+# warn_redundant_casts = true
+# warn_unused_ignores = true
+# warn_return_any = true
+# no_implicit_reexport = true
+# strict_equality = true
+
+[tool.isort]
+profile = "black"
diff --git a/setup.py b/setup.py
deleted file mode 100644
index b992679..0000000
--- a/setup.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#-------------------------------------------------------------------------------
-#
-# viresclient package setup
-#
-# Project: VirES-Python-Client
-# Authors: Ashley Smith <ashley.smith@ed.ac.uk>
-#          Martin Paces <martin.paces@eox.at>
-#
-#-------------------------------------------------------------------------------
-# Copyright (C) 2018 EOX IT Services GmbH
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies of this Software or works derived from this Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#-------------------------------------------------------------------------------
-
-import os
-from setuptools import setup, find_packages
-
-with open('viresclient/__init__.py') as f:
-    for line in f:
-        if line.find("__version__") >= 0:
-            version = line.split("=")[1].strip()
-            version = version.strip('"')
-            version = version.strip("'")
-            continue
-
-
-# Utility function to read the README file.
-def read(fname):
-    return open(os.path.join(os.path.dirname(__file__), fname)).read()
-
-
-setup(
-    name='viresclient',
-    version=version,
-    license='EOX licence (MIT style)',
-    description='A Python client for interacting with a VirES server',
-    long_description=read('README.rst'),
-    author='Ashley Smith',
-    author_email='ashley.smith@ed.ac.uk',
-    url='https://github.com/ESA-VirES/VirES-Python-Client',
-    packages=find_packages(),
-    scripts=[],
-    package_data={
-        'viresclient': [
-            '_wps/templates/*',
-            '_data/*'
-        ],
-    },
-    python_requires='>=3.6',
-    install_requires=[
-        'Jinja2>=2.10, <4.0.0',
-        'pandas>=0.18, <2.0.0',
-        'cdflib>=0.3.9, <=0.3.20',
-        'tables>=3.4.4, <4.0.0',
-        'tqdm>=4.23.0, <5.0.0',
-        'xarray>=0.11.0, <1.0.0',
-        'requests>=2.0.0, <3.0.0',
-        'netCDF4>=1.5.3, <1.6.0',
-    ],
-    entry_points={
-        'console_scripts': [
-            'viresclient=viresclient.commands.viresclient:start',
-        ],
-    },
-)
diff --git a/viresclient/__init__.py b/src/viresclient/__init__.py
similarity index 80%
rename from viresclient/__init__.py
rename to src/viresclient/__init__.py
index 0146722..07b6388 100644
--- a/viresclient/__init__.py
+++ b/src/viresclient/__init__.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # Handles the WPS requests to the VirES server
 #
 # Authors: Ashley Smith <ashley.smith@ed.ac.uk>
 #          Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,16 +25,14 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
-from ._config import ClientConfig, set_token
-from ._client_swarm import SwarmRequest
-from ._client_aeolus import AeolusRequest
-from ._data_handling import ReturnedData
-from ._data_handling import ReturnedDataFile
-from ._api.upload import DataUpload
-from ._api.token import TokenManager
 from . import _data
+from ._api.token import TokenManager
+from ._api.upload import DataUpload
+from ._client_aeolus import AeolusRequest
+from ._client_swarm import SwarmRequest
+from ._config import ClientConfig, set_token
+from ._data_handling import ReturnedData, ReturnedDataFile
 
-
-__version__ = "0.10.2-alpha"
+__version__ = "0.11.0-alpha"
diff --git a/test/__init__.py b/src/viresclient/_api/__init__.py
similarity index 100%
rename from test/__init__.py
rename to src/viresclient/_api/__init__.py
diff --git a/viresclient/_api/token.py b/src/viresclient/_api/token.py
similarity index 76%
rename from viresclient/_api/token.py
rename to src/viresclient/_api/token.py
index ec8c7ef..a24d775 100644
--- a/viresclient/_api/token.py
+++ b/src/viresclient/_api/token.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # vires API - token management API proxy
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,16 +25,18 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 import re
+
 import requests
+
 from .._wps.http_util import encode_token_auth
 
 
-class TokenManager():
-    """ VirES for Swarm token management API proxy.
+class TokenManager:
+    """VirES for Swarm token management API proxy.
 
     Example usage::
 
@@ -54,11 +56,12 @@ class TokenManager():
        'token': '...'}
 
     """
+
     RE_URL_BASE = re.compile(r"(/(ows)?)?$")
     PATH_TOKEN_API = "/accounts/api/tokens/"
 
     class Error(Exception):
-        """ Token manager error exception. """
+        """Token manager error exception."""
 
     def __init__(self, url, token):
         self.url = self.get_api_url(url)
@@ -66,25 +69,29 @@ def __init__(self, url, token):
         self.headers = encode_token_auth(token=token)
 
     def post(self, purpose=None, expires=None):
-        """ HTTP POST
+        """HTTP POST
         Create a new token.
         Accepted parameters:
           purpose   optional text label describing purpose of the token
           expires   optional token expiration as ISO timestamp or ISO duration
         """
-        response = requests.post(self.url, headers=self.headers, json={
-            "purpose": purpose,
-            "expires": expires,
-        })
+        response = requests.post(
+            self.url,
+            headers=self.headers,
+            json={
+                "purpose": purpose,
+                "expires": expires,
+            },
+        )
 
         if response.status_code != 200:
-            raise self.Error("%s %s: %s" % (
-                response.status_code, response.reason, response.text
-            ))
+            raise self.Error(
+                f"{response.status_code} {response.reason}: {response.text}"
+            )
 
         return response.json()
 
     @classmethod
     def get_api_url(cls, url):
-        """ Translate WPS URL path to the upload REST/API URL path. """
+        """Translate WPS URL path to the upload REST/API URL path."""
         return cls.RE_URL_BASE.sub(cls.PATH_TOKEN_API, url, count=1)
diff --git a/viresclient/_api/upload.py b/src/viresclient/_api/upload.py
similarity index 70%
rename from viresclient/_api/upload.py
rename to src/viresclient/_api/upload.py
index 1dc0fbc..113c8ff 100644
--- a/viresclient/_api/upload.py
+++ b/src/viresclient/_api/upload.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # vires API - data upload API proxy
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,17 +25,19 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 import json
 from os.path import basename
+
 import requests
+
 from .._wps.http_util import encode_token_auth
 
 
-class DataUpload():
-    """ VirES for Swarm data upload API proxy.
+class DataUpload:
+    """VirES for Swarm data upload API proxy.
 
     Example usage::
 
@@ -81,35 +83,41 @@ class DataUpload():
     `file format specification <https://github.com/ESA-VirES/VirES-Server/blob/master/vires/custom_data_format_description.md>`_
 
     """
+
     PATH_OWS = "/ows"
     PATH_UPLOAD = "/custom_data/"
 
     class Error(Exception):
-        """ Data upload error exception. """
+        """Data upload error exception."""
 
     def __init__(self, url, token, **kwargs):
-        self.url = self.get_api_url(url) # translates path from /ows to /custom_data/
+        self.url = self.get_api_url(url)  # translates path from /ows to /custom_data/
         self.token = token
         self.headers = encode_token_auth(token=token)
 
     @property
     def ids(self):
-        """ Get list of identifiers. """
-        return [item['identifier'] for item in self.get()]
+        """Get list of identifiers."""
+        return [item["identifier"] for item in self.get()]
 
     def clear(self):
-        """ Remove all uploaded items.  """
+        """Remove all uploaded items."""
         for id_ in self.ids:
             self.delete(id_)
 
     def post(self, file, filename=None):
-        """ HTTP POST multipart/form-data
+        """HTTP POST multipart/form-data
         Upload file to the server and get info about the uploaded file.
         """
+
         def _post(file, filename):
-            return requests.post(self.url, headers=self.headers, files={
-                "file": (filename, file),
-            })
+            return requests.post(
+                self.url,
+                headers=self.headers,
+                files={
+                    "file": (filename, file),
+                },
+            )
 
         if isinstance(file, str):
             if not filename:
@@ -120,19 +128,18 @@ def _post(file, filename):
             response = _post(file, filename)
 
         if response.status_code != 200:
-            raise self.Error("%s %s: %s" % (
-                response.status_code, response.reason, response.text
-            ))
+            raise self.Error(
+                f"{response.status_code} {response.reason}: {response.text}"
+            )
 
         return json.loads(response.text)
 
-
     def get_constant_parameters(self, identifier):
-        """ Get dictionary of the currently set constant parameters. """
+        """Get dictionary of the currently set constant parameters."""
         return self._extract_constant_values(self.get(identifier))
 
     def set_constant_parameters(self, identifier, parameters, replace=False):
-        """ Set constant parameters form from give key value dictionary.
+        """Set constant parameters form from give key value dictionary.
         Set replace to True if you prefer to replace the already set parameters
         rather then update them.
         """
@@ -142,73 +149,78 @@ def set_constant_parameters(self, identifier, parameters, replace=False):
             parameters_all = self.get_constant_parameters(identifier)
             parameters_all.update(parameters)
 
-        return self._extract_constant_values(self.patch(identifier, {
-            "constant_fields": {
-                name: {"value": value} for name, value in parameters_all.items()
-            }
-        }))
+        return self._extract_constant_values(
+            self.patch(
+                identifier,
+                {
+                    "constant_fields": {
+                        name: {"value": value} for name, value in parameters_all.items()
+                    }
+                },
+            )
+        )
 
     def patch(self, identifier, data):
-        """ REST/API PATCH
+        """REST/API PATCH
         Update metadata of the uploaded dataset.
         """
         url = self.url + (identifier or "")
-        headers = {'Content-Type': 'application/json'}
+        headers = {"Content-Type": "application/json"}
         headers.update(self.headers)
         response = requests.patch(url, data=json.dumps(data), headers=headers)
         if response.status_code != 200:
-            raise self.Error("%s %s: %s" % (
-                response.status_code, response.reason, response.text
-            ))
+            raise self.Error(
+                f"{response.status_code} {response.reason}: {response.text}"
+            )
         return json.loads(response.text)
 
     def get(self, identifier=None):
-        """ REST/API GET
+        """REST/API GET
         If an identifier provided, get info about the uploaded item.
         If no identifier provided, list all uploaded items.
         """
         url = self.url + (identifier or "")
         response = requests.get(url, headers=self.headers)
         if response.status_code != 200:
-            raise self.Error("%s %s: %s" % (
-                response.status_code, response.reason, response.text
-            ))
+            raise self.Error(
+                f"{response.status_code} {response.reason}: {response.text}"
+            )
         return json.loads(response.text)
 
     def delete(self, identifier):
-        """ REST/API DELETE request.
+        """REST/API DELETE request.
         Delete item of the given identifier.
         """
         url = self.url + identifier
         response = requests.delete(url, headers=self.headers)
         if response.status_code != 204:
-            raise self.Error("%s %s: %s" % (
-                response.status_code, response.reason, response.text
-            ))
+            raise self.Error(
+                f"{response.status_code} {response.reason}: {response.text}"
+            )
 
     @classmethod
     def get_api_url(cls, url):
-        """ Translate WPS URL path to the upload REST/API URL path. """
+        """Translate WPS URL path to the upload REST/API URL path."""
         return cls._replace_path(url, cls.PATH_OWS, cls.PATH_UPLOAD) or url
 
     @classmethod
     def get_ows_url(cls, url):
-        """ Translate REST/API URL path to the upload WPS URL path. """
+        """Translate REST/API URL path to the upload WPS URL path."""
         return (
-            cls._replace_path(url, cls.PATH_UPLOAD, cls.PATH_OWS) or
-            cls._replace_path(url, cls.PATH_UPLOAD[:-1], cls.PATH_OWS)
+            cls._replace_path(url, cls.PATH_UPLOAD, cls.PATH_OWS)
+            or cls._replace_path(url, cls.PATH_UPLOAD[:-1], cls.PATH_OWS)
             or url
         )
 
     @staticmethod
     def _replace_path(url, path_old, path_new):
         if path_old and url.endswith(path_old):
-            return url[:-len(path_old)] + path_new
+            return url[: -len(path_old)] + path_new
         return None
 
     @staticmethod
     def _extract_constant_values(data):
         return {
-            name: info['value'] for name, info
-            in data.get('constant_fields', {}).items()
+            name: info["value"]
+            for name, info in data.get("constant_fields", {}).items()
         }
diff --git a/viresclient/_client.py b/src/viresclient/_client.py
similarity index 78%
rename from viresclient/_client.py
rename to src/viresclient/_client.py
index dd95db0..c0000e5 100644
--- a/viresclient/_client.py
+++ b/src/viresclient/_client.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # Handles the WPS requests to the VirES server
 #
 # Authors: Ashley Smith <ashley.smith@ed.ac.uk>
 #          Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,38 +25,39 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
+import json
 import os
 from datetime import timedelta
-from logging import getLogger, DEBUG, INFO, WARNING, ERROR, CRITICAL
-import json
+from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING, getLogger
+
 # Identify whether code is running in Jupyter notebook or not
 try:
     from IPython import get_ipython
-    IN_JUPYTER = 'zmqshell' in str(type(get_ipython()))
+
+    IN_JUPYTER = "zmqshell" in str(type(get_ipython()))
 except Exception:
     IN_JUPYTER = False
 if IN_JUPYTER:
     from tqdm.notebook import tqdm
 else:
     from tqdm import tqdm
+
 from io import StringIO
+
 from pandas import read_csv, to_datetime
 
+from ._config import ClientConfig, set_token
+from ._data_handling import ReturnedData
 
-from ._wps.wps_vires import ViresWPS10Service
-from ._wps.time_util import parse_duration, parse_datetime
-from ._wps.http_util import (
-    encode_basic_auth, encode_token_auth, encode_no_auth,
-)
-from ._wps.log_util import set_stream_handler
 # from jinja2 import Environment, FileSystemLoader
 from ._wps.environment import JINJA2_ENVIRONMENT
-from ._wps.wps import WPSError, AuthenticationError
-
-from ._data_handling import ReturnedData
-from ._config import ClientConfig, set_token
+from ._wps.http_util import encode_basic_auth, encode_no_auth, encode_token_auth
+from ._wps.log_util import set_stream_handler
+from ._wps.time_util import parse_datetime, parse_duration
+from ._wps.wps import AuthenticationError, WPSError
+from ._wps.wps_vires import ViresWPS10Service
 
 # Logging levels
 LEVELS = {
@@ -71,7 +72,7 @@
 RESPONSE_TYPES = {
     "csv": "text/csv",
     "cdf": "application/x-cdf",
-    "nc": "application/netcdf"
+    "nc": "application/netcdf",
 }
 
 # Maximum number of records allowable in one WPS request
@@ -81,11 +82,11 @@
 CONFIG_FILE_PATH = os.path.join(os.path.expanduser("~"), ".viresclient,ini")
 
 # Maximum time-chunk size ~25 years
-MAX_CHUNK_DURATION = timedelta(days=25*365.25)
+MAX_CHUNK_DURATION = timedelta(days=25 * 365.25)
 
 TEMPLATE_FILES = {
-    'list_jobs': "vires_list_jobs.xml",
-    'getTimeData': "vires_getTimeData.xml"
+    "list_jobs": "vires_list_jobs.xml",
+    "getTimeData": "vires_getTimeData.xml",
 }
 
 AUTH_ERROR_TEXT = """
@@ -99,17 +100,16 @@
 }
 
 
-
 def get_log_level(level):
-    """ Translate log-level string to an actual log level number accepted by
-    the python logging. """
+    """Translate log-level string to an actual log level number accepted by
+    the python logging."""
     if isinstance(level, str):
         level = LEVELS[level]
     return level
 
 
-class WPSInputs(object):
-    """ Base WPS inputs class holding the set of inputs to be passed
+class WPSInputs:
+    """Base WPS inputs class holding the set of inputs to be passed
     to the request template.
 
     Properties of this class are the set of valid inputs to a WPS request.
@@ -118,17 +118,17 @@ class WPSInputs(object):
     dictionary to be passed as kwargs to as_xml() which fills the xml
     template.
     """
-    NAMES = [] # to be overridden in the sub-classes
+
+    NAMES = []  # to be overridden in the sub-classes
 
     def __str__(self):
-        return "Request details:\n{}".format('\n'.join([
-            '{}: {}'.format(key, value)
-            for (key, value) in self.as_dict.items()
-            ]))
+        return "Request details:\n{}".format(
+            "\n".join([f"{key}: {value}" for (key, value) in self.as_dict.items()])
+        )
 
     @property
     def as_dict(self):
-        return {key: self.__dict__['_{}'.format(key)] for key in self.NAMES}
+        return {key: self.__dict__[f"_{key}"] for key in self.NAMES}
 
     def as_xml(self, templatefile):
         """Renders a WPS request template (xml) that can later be executed
@@ -138,19 +138,20 @@ def as_xml(self, templatefile):
 
         """
         template = JINJA2_ENVIRONMENT.get_template(templatefile)
-        request = template.render(**self.as_dict).encode('UTF-8')
+        request = template.render(**self.as_dict).encode("UTF-8")
         return request
 
 
-class ProgressBar(object):
-    """Custom tqdm status bar
-    """
+class ProgressBar:
+    """Custom tqdm status bar"""
 
     def __init__(self, bar_format, total=100, leave=True):
         self.percentCompleted = 0
         self.lastpercent = 0
         self.tqdm_pbar = tqdm(
-            total=total, bar_format=bar_format, leave=leave,
+            total=total,
+            bar_format=bar_format,
+            leave=leave,
         )
 
     def __enter__(self):
@@ -163,11 +164,10 @@ def cleanup(self):
         self.tqdm_pbar.close()
 
     def refresh_tqdm(self):
-        """Updates the output of the progress bar.
-        """
+        """Updates the output of the progress bar."""
         if self.percentCompleted is None:
             return
-        self.tqdm_pbar.update(self.percentCompleted-self.lastpercent)
+        self.tqdm_pbar.update(self.percentCompleted - self.lastpercent)
         if self.percentCompleted == 100:
             self.cleanup()
 
@@ -180,17 +180,17 @@ class ProgressBarChunks(ProgressBar):
     """A progress bar to track longer (chunked) requests"""
 
     def __init__(self, nchunks):
-        l_bar = '{desc}'
-        bar = '{bar}'
-        r_bar = '|  [ Elapsed: {elapsed}, Remaining: {remaining}]  {postfix}'
-        bar_format = '{}{}{}'.format(l_bar, bar, r_bar)
-        super().__init__(bar_format, total=nchunks+1)
+        l_bar = "{desc}"
+        bar = "{bar}"
+        r_bar = "|  [ Elapsed: {elapsed}, Remaining: {remaining}]  {postfix}"
+        bar_format = f"{l_bar}{bar}{r_bar}"
+        super().__init__(bar_format, total=nchunks + 1)
 
     def update(self, i, n, size, final=False):
         """Update the chunk iteration (i) and total file size"""
         # Update {desc} set above
         self.tqdm_pbar.set_description(f"Processing chunks [{i+1}/{n}]")
-        size = round(size/1e6, 3)
+        size = round(size / 1e6, 3)
         filesize_display = f"({size} MB)" if final else f"(> {size} MB)"
         # Update {postfix} set above
         self.tqdm_pbar.set_postfix_str(filesize_display)
@@ -205,16 +205,15 @@ class ProgressBarProcessing(ProgressBar):
     """
 
     def __init__(self, extra_text=None, leave=True):
-        extra_text = str() if extra_text is None else extra_text
-        l_bar = 'Processing:  {percentage:3.0f}%|'
-        bar = '{bar}'
-        r_bar = '|  [ Elapsed: {elapsed}, Remaining: {remaining} {postfix}]'
-        bar_format = '{}{}{} {}'.format(l_bar, bar, r_bar, extra_text)
+        extra_text = "" if extra_text is None else extra_text
+        l_bar = "Processing:  {percentage:3.0f}%|"
+        bar = "{bar}"
+        r_bar = "|  [ Elapsed: {elapsed}, Remaining: {remaining} {postfix}]"
+        bar_format = f"{l_bar}{bar}{r_bar} {extra_text}"
         super().__init__(bar_format, leave=leave)
 
     def update(self, wpsstatus):
-        """Updates the internal state based on the state of a WPSStatus object.
-        """
+        """Updates the internal state based on the state of a WPSStatus object."""
         self.lastpercent = self.percentCompleted
         self.percentCompleted = wpsstatus.percentCompleted
         if self.lastpercent != self.percentCompleted:
@@ -228,32 +227,38 @@ class ProgressBarDownloading(ProgressBar):
 
     def __init__(self, size, leave=True):
         # Convert size to MB
-        sizeMB = round(size/1e6, 3)
+        sizeMB = round(size / 1e6, 3)
         # if sizeMB > 1:
         #     sizeMB = round(sizeMB,)
-        l_bar = 'Downloading: {percentage:3.0f}%|'
-        bar = '{bar}'
-        r_bar = '|  [ Elapsed: {{elapsed}}, '\
-                'Remaining: {{remaining}} {{postfix}}] '\
-                '({sizeMB}MB)'.format(sizeMB=sizeMB)
-        bar_format = '{}{}{}'.format(l_bar, bar, r_bar)
+        l_bar = "Downloading: {percentage:3.0f}%|"
+        bar = "{bar}"
+        r_bar = (
+            "|  [ Elapsed: {{elapsed}}, "
+            "Remaining: {{remaining}} {{postfix}}] "
+            "({sizeMB}MB)".format(sizeMB=sizeMB)
+        )
+        bar_format = f"{l_bar}{bar}{r_bar}"
         super().__init__(bar_format, leave=leave)
 
     def update(self, percentCompleted):
-        """Updates the internal state of the percentage completion.
-        """
+        """Updates the internal state of the percentage completion."""
         self.lastpercent = self.percentCompleted
         self.percentCompleted = percentCompleted
         if self.lastpercent != self.percentCompleted:
             self.refresh_tqdm()
 
 
-class ClientRequest(object):
-    """Base class handling the requests to and downloads from the server.
-    """
+class ClientRequest:
+    """Base class handling the requests to and downloads from the server."""
 
-    def __init__(self, url=None, token=None,
-                 config=None, logging_level="NO_LOGGING", server_type=None):
+    def __init__(
+        self,
+        url=None,
+        token=None,
+        config=None,
+        logging_level="NO_LOGGING",
+        server_type=None,
+    ):
 
         self._server_type = server_type
 
@@ -281,9 +286,7 @@ def __init__(self, url=None, token=None,
         self._logger = getLogger()
         set_stream_handler(self._logger, logging_level)
 
-        self._wps_service = self._create_service_proxy_(
-            config, url, None, None, token
-        )
+        self._wps_service = self._create_service_proxy_(config, url, None, None, token)
         # Test if the token is working; re-enter if not
         if IN_JUPYTER:
             invalid_token = True
@@ -325,9 +328,9 @@ def _create_service_proxy_(self, config, url, username, password, token):
             encode_headers = encode_basic_auth
         else:
             credentials = config.get_site_config(url)
-            if 'token' in credentials:
+            if "token" in credentials:
                 encode_headers = encode_token_auth
-            elif 'username' in credentials and 'password' in credentials:
+            elif "username" in credentials and "password" in credentials:
                 encode_headers = encode_basic_auth
             else:
                 encode_headers = encode_no_auth
@@ -353,8 +356,7 @@ def __str__(self):
 
     # @staticmethod
     def _response_handler(
-                self, retdatafile, show_progress=True,
-                leave_progress_bar=True
+        self, retdatafile, show_progress=True, leave_progress_bar=True
     ):
         """Creates the response handler function for the WPS request
 
@@ -362,7 +364,7 @@ def _response_handler(
         with a download progress bar
         """
 
-        def copyfileobj(fsrc, fdst, callback=None, total=None, length=16*1024):
+        def copyfileobj(fsrc, fdst, callback=None, total=None, length=16 * 1024):
             """Copying with progress reporting
             https://stackoverflow.com/a/29967714
             """
@@ -378,7 +380,8 @@ def copyfileobj(fsrc, fdst, callback=None, total=None, length=16*1024):
 
         def copy_progress(pbar):
             def _copy_progress(copied, total):
-                return pbar.update(100*copied/total)
+                return pbar.update(100 * copied / total)
+
             return _copy_progress
 
         def write_response(file_obj):
@@ -386,17 +389,16 @@ def write_response(file_obj):
             https://stackoverflow.com/a/7244263
             file_obj is what is returned from urllib.urlopen()
             """
-            size = int(file_obj.info()['Content-Length'])
+            size = int(file_obj.info()["Content-Length"])
             self._downloaded_chunk_sizes.append(size)
             with ProgressBarDownloading(size, leave=leave_progress_bar) as pbar:
                 with open(retdatafile._file.name, "wb") as out_file:
                     copyfileobj(
-                        file_obj, out_file, callback=copy_progress(pbar),
-                        total=size
-                        )
+                        file_obj, out_file, callback=copy_progress(pbar), total=size
+                    )
 
         def write_response_without_reporting(file_obj):
-            size = int(file_obj.info()['Content-Length'])
+            size = int(file_obj.info()["Content-Length"])
             self._downloaded_chunk_sizes.append(size)
             with open(retdatafile._file.name, "wb") as out_file:
                 copyfileobj(file_obj, out_file)
@@ -428,9 +430,12 @@ def _chunkify_request(start_time, end_time, sampling_step, nrecords_limit):
                 e.g. [(start1, end1), (start2, end2)]
         """
         # maximum chunk duration as a timedelta object
-        chunk_duration = min(timedelta(seconds=(
-            nrecords_limit * parse_duration(sampling_step).total_seconds()
-        )), MAX_CHUNK_DURATION)
+        chunk_duration = min(
+            timedelta(
+                seconds=(nrecords_limit * parse_duration(sampling_step).total_seconds())
+            ),
+            MAX_CHUNK_DURATION,
+        )
 
         # calculate the chunk intervals ...
         request_intervals = []
@@ -445,8 +450,15 @@ def _chunkify_request(start_time, end_time, sampling_step, nrecords_limit):
 
         return request_intervals
 
-    def _get(self, request=None, asynchronous=None, response_handler=None,
-             message=None, show_progress=True, leave_progress_bar=True):
+    def _get(
+        self,
+        request=None,
+        asynchronous=None,
+        response_handler=None,
+        message=None,
+        show_progress=True,
+        leave_progress_bar=True,
+    ):
         """Make a request and handle response according to response_handler
 
         Args:
@@ -467,32 +479,35 @@ def _get(self, request=None, asynchronous=None, response_handler=None,
                         return self._wps_service.retrieve_async(
                             request,
                             handler=response_handler,
-                            status_handler=progressbar.update
+                            status_handler=progressbar.update,
                         )
                 else:
                     return self._wps_service.retrieve_async(
-                        request,
-                        handler=response_handler
+                        request, handler=response_handler
                     )
             else:
-                return self._wps_service.retrieve(
-                    request,
-                    handler=response_handler
-                )
+                return self._wps_service.retrieve(request, handler=response_handler)
         except WPSError:
             raise RuntimeError(
                 "Server error. Or perhaps the request is invalid? "
                 "Check the output of: print(request) and "
                 "print(request._request.decode())"
-                )
+            )
         except AuthenticationError:
             raise AuthenticationError(AUTH_ERROR_TEXT)
 
-    def get_between(self, start_time=None, end_time=None,
-                    filetype="cdf", asynchronous=True, show_progress=True,
-                    show_progress_chunks=True,
-                    leave_intermediate_progress_bars=True,
-                    nrecords_limit=None, tmpdir=None):
+    def get_between(
+        self,
+        start_time=None,
+        end_time=None,
+        filetype="cdf",
+        asynchronous=True,
+        show_progress=True,
+        show_progress_chunks=True,
+        leave_intermediate_progress_bars=True,
+        nrecords_limit=None,
+        tmpdir=None,
+    ):
         """Make the server request and download the data.
 
         Args:
@@ -532,17 +547,15 @@ def get_between(self, start_time=None, end_time=None,
         retdatagroup = ReturnedData(filetype=filetype)
 
         if retdatagroup.filetype not in self._supported_filetypes:
-            raise TypeError(
-                "filetype: {} not supported by server".format(filetype)
-                )
+            raise TypeError(f"filetype: {filetype} not supported by server")
         self._request_inputs.response_type = RESPONSE_TYPES[retdatagroup.filetype]
 
         if asynchronous:
             # asynchronous WPS request
-            templatefile = self._templatefiles['async']
+            templatefile = self._templatefiles["async"]
         else:
             # synchronous WPS request
-            templatefile = self._templatefiles['sync']
+            templatefile = self._templatefiles["sync"]
 
         # Set the "sampling step" to use to split the request if it's too long
         # (Due to the the server limit of NRECORDS_LIMIT)
@@ -558,24 +571,26 @@ def get_between(self, start_time=None, end_time=None,
         if sampling_step_estimate is None:
             # Identify a default sampling step if possible
             try:
-                collection_key = self._available[
-                    "collections_to_keys"][self._collection_list[0]]
-                sampling_step_estimate = self._available[
-                    "collection_sampling_steps"][collection_key]
+                collection_key = self._available["collections_to_keys"][
+                    self._collection_list[0]
+                ]
+                sampling_step_estimate = self._available["collection_sampling_steps"][
+                    collection_key
+                ]
             except Exception:
                 sampling_step_estimate = "PT1S"
         nrecords_limit = NRECORDS_LIMIT if nrecords_limit is None else nrecords_limit
         # Split the request into several intervals
         intervals = self._chunkify_request(
             start_time, end_time, sampling_step_estimate, nrecords_limit
-            )
+        )
         nchunks = len(intervals)
         # Recreate the ReturnedData with the right number of chunks
         retdatagroup = ReturnedData(filetype=filetype, N=nchunks, tmpdir=tmpdir)
 
         def _get_chunk(i, start_time_i, end_time_i, leave_progress_bar=False):
             """Process an individual chunk and update retdatagroup"""
-            message = "[{}/{}] ".format(i+1, nchunks)
+            message = f"[{i + 1}/{nchunks}] "
             # Finalise the WPSInputs object and (re-)generate the xml
             self._request_inputs.begin_time = start_time_i
             self._request_inputs.end_time = end_time_i
@@ -585,8 +600,9 @@ def _get_chunk(i, start_time_i, end_time_i, leave_progress_bar=False):
             # Make the request, as either asynchronous or synchronous
             # The response handler streams the data to the ReturnedData object
             response_handler = self._response_handler(
-                retdatafile, show_progress=show_progress,
-                leave_progress_bar=leave_progress_bar
+                retdatafile,
+                show_progress=show_progress,
+                leave_progress_bar=leave_progress_bar,
             )
             self._get(
                 request=self._request,
@@ -594,7 +610,7 @@ def _get_chunk(i, start_time_i, end_time_i, leave_progress_bar=False):
                 response_handler=response_handler,
                 message=message,
                 show_progress=show_progress,
-                leave_progress_bar=leave_progress_bar
+                leave_progress_bar=leave_progress_bar,
             )
 
         if nchunks > 1:
@@ -607,8 +623,10 @@ def _get_chunk(i, start_time_i, end_time_i, leave_progress_bar=False):
                     for i, (start_time_i, end_time_i) in enumerate(intervals):
                         pbar.update(i, nchunks, totalsize)
                         _get_chunk(
-                            i, start_time_i, end_time_i,
-                            leave_progress_bar=leave_intermediate_progress_bars
+                            i,
+                            start_time_i,
+                            end_time_i,
+                            leave_progress_bar=leave_intermediate_progress_bars,
                         )
                         totalsize = sum(self._downloaded_chunk_sizes)
                     pbar.update(i, nchunks, totalsize, final=True)
@@ -621,16 +639,16 @@ def _get_chunk(i, start_time_i, end_time_i, leave_progress_bar=False):
         return retdatagroup
 
     def list_jobs(self):
-        """ Return job information from the server.
+        """Return job information from the server.
 
         Returns:
             dict
         """
-        templatefile = TEMPLATE_FILES['list_jobs']
+        templatefile = TEMPLATE_FILES["list_jobs"]
         template = JINJA2_ENVIRONMENT.get_template(templatefile)
-        request = template.render().encode('UTF-8')
+        request = template.render().encode("UTF-8")
         response = self._get(request, asynchronous=False, show_progress=False)
-        return json.loads(response.decode('UTF-8'))
+        return json.loads(response.decode("UTF-8"))
 
     def available_times(self, collection, start_time=None, end_time=None):
         """Returns temporal availability for a given collection
@@ -644,20 +662,12 @@ def available_times(self, collection, start_time=None, end_time=None):
             DataFrame
 
         """
-        template = JINJA2_ENVIRONMENT.get_template(
-            TEMPLATE_FILES["getTimeData"]
-        )
+        template = JINJA2_ENVIRONMENT.get_template(TEMPLATE_FILES["getTimeData"])
         request = template.render(
-            collection_id=collection,
-            begin_time=start_time,
-            end_time=end_time
-        ).encode('UTF-8')
-        response = self._get(
-            request, asynchronous=False, show_progress=False
-        )
-        df = read_csv(
-            StringIO(str(response, 'utf-8'))
-        )
+            collection_id=collection, begin_time=start_time, end_time=end_time
+        ).encode("UTF-8")
+        response = self._get(request, asynchronous=False, show_progress=False)
+        df = read_csv(StringIO(str(response, "utf-8")))
         # Convert to datetime objects
         df["starttime"] = to_datetime(df["starttime"])
         df["endtime"] = to_datetime(df["endtime"])
diff --git a/viresclient/_client_aeolus.py b/src/viresclient/_client_aeolus.py
similarity index 78%
rename from viresclient/_client_aeolus.py
rename to src/viresclient/_client_aeolus.py
index b0bcd35..e411d46 100644
--- a/viresclient/_client_aeolus.py
+++ b/src/viresclient/_client_aeolus.py
@@ -1,68 +1,72 @@
-import datetime, json
+import datetime
+import json
 
-from ._client import WPSInputs, ClientRequest
-from ._data_handling import ReturnedDataFile
-from ._data import CONFIG_AEOLUS
 import pandas as pd
+
+from ._client import ClientRequest, WPSInputs
+from ._data import CONFIG_AEOLUS
+from ._data_handling import ReturnedDataFile
+
 # from pandas import DataFrame, json_normalize
 
 TEMPLATE_FILES = {
-    'sync': "vires_aeolus_fetch_filtered_data.xml",
-    'async': "vires_aeolus_fetch_filtered_data_async.xml"
+    "sync": "vires_aeolus_fetch_filtered_data.xml",
+    "async": "vires_aeolus_fetch_filtered_data_async.xml",
 }
 
 
 class AeolusWPSInputs(WPSInputs):
 
     NAMES = [
-        'processId',
-        'collection_ids',
-        'begin_time',
-        'end_time',
-        'response_type',
-        'fields',
-        'filters',
-        'aux_type',
-        'observation_fields',
-        'measurement_fields',
-        'mie_profile_fields',
-        'rayleigh_profile_fields',
-        'mie_wind_fields',
-        'rayleigh_wind_fields',
-        'mie_grouping_fields',
-        'rayleigh_grouping_fields',
-        'ica_fields',
-        'sca_fields',
-        'mca_fields',
-        'group_fields',
-        'bbox',
-        'dsd_info'
+        "processId",
+        "collection_ids",
+        "begin_time",
+        "end_time",
+        "response_type",
+        "fields",
+        "filters",
+        "aux_type",
+        "observation_fields",
+        "measurement_fields",
+        "mie_profile_fields",
+        "rayleigh_profile_fields",
+        "mie_wind_fields",
+        "rayleigh_wind_fields",
+        "mie_grouping_fields",
+        "rayleigh_grouping_fields",
+        "ica_fields",
+        "sca_fields",
+        "mca_fields",
+        "group_fields",
+        "bbox",
+        "dsd_info",
     ]
 
-    def __init__(self,
-                 processId=None,
-                 collection_ids=None,
-                 begin_time=None,
-                 end_time=None,
-                 response_type=None,
-                 fields=None,
-                 filters=None,
-                 aux_type=None,
-                 observation_fields=None,
-                 measurement_fields=None,
-                 sca_fields=None,
-                 ica_fields=None,
-                 mca_fields=None,
-                 group_fields=None,
-                 mie_profile_fields=None,
-                 rayleigh_profile_fields=None,
-                 mie_wind_fields=None,
-                 rayleigh_wind_fields=None,
-                 mie_grouping_fields=None,
-                 rayleigh_grouping_fields=None,
-                 bbox=None,
-                 dsd_info=False,
-                 ):
+    def __init__(
+        self,
+        processId=None,
+        collection_ids=None,
+        begin_time=None,
+        end_time=None,
+        response_type=None,
+        fields=None,
+        filters=None,
+        aux_type=None,
+        observation_fields=None,
+        measurement_fields=None,
+        sca_fields=None,
+        ica_fields=None,
+        mca_fields=None,
+        group_fields=None,
+        mie_profile_fields=None,
+        rayleigh_profile_fields=None,
+        mie_wind_fields=None,
+        rayleigh_wind_fields=None,
+        mie_grouping_fields=None,
+        rayleigh_grouping_fields=None,
+        bbox=None,
+        dsd_info=False,
+    ):
         # Obligatory
         self.processId = None if processId is None else processId
         self.collection_ids = None if collection_ids is None else collection_ids
@@ -107,7 +111,7 @@ def as_dict(self):
         self._sca_fields = self.sca_fields
         self._bbox = self.bbox
         self._dsd_info = self.dsd_info
-        return {key: self.__dict__['_{}'.format(key)] for key in self.NAMES}
+        return {key: self.__dict__[f"_{key}"] for key in self.NAMES}
 
     @property
     def processId(self):
@@ -217,15 +221,12 @@ class AeolusRequest(ClientRequest):
         logging_level (str):
 
     """
-    def __init__(self, url=None, token=None,
-                 config=None, logging_level="NO_LOGGING"):
-        super().__init__(
-            url, token, config, logging_level,
-            server_type="Aeolus"
-            )
+
+    def __init__(self, url=None, token=None, config=None, logging_level="NO_LOGGING"):
+        super().__init__(url, token, config, logging_level, server_type="Aeolus")
         # self._available = self._set_available_data()
         self._request_inputs = AeolusWPSInputs()
-        self._request_inputs.processId = 'aeolus:level1B'
+        self._request_inputs.processId = "aeolus:level1B"
         self._templatefiles = TEMPLATE_FILES
         self._filterlist = {}
         self._supported_filetypes = ("nc",)
@@ -251,11 +252,13 @@ def set_collection(self, collection):
             raise ValueError("Product not found")
 
     def available_collections(
-        self, collection=None, field_type=None, like=None, details=True):
+        self, collection=None, field_type=None, like=None, details=True
+    ):
         return CONFIG_AEOLUS
-    
+
     def print_available_collections(
-        self, collection=None, field_type=None, regex=None, details=True, path=False):
+        self, collection=None, field_type=None, regex=None, details=True, path=False
+    ):
         pd.set_option("display.max_rows", None)
         pd.set_option("display.max_colwidth", None)
         collection_dfs = []
@@ -265,20 +268,20 @@ def print_available_collections(
             for _, ft in collection_obj.items():
                 fdf = pd.DataFrame(ft).transpose()
                 fdf.index.name = "identifier"
-                if regex != None:
+                if regex is not None:
                     fdf = fdf[fdf.index.str.contains(regex, regex=True)]
-                if path == False:
-                    del fdf['path']
+                if path is False:
+                    del fdf["path"]
                 field_dfs.append(fdf)
             ft_df = pd.concat(
                 field_dfs, names=["field type"], keys=collection_obj.keys()
             )
-            if field_type != None:
-                try: 
+            if field_type is not None:
+                try:
                     ft_df = ft_df.loc[field_type]
                     collection_dfs.append(ft_df)
                     collection_names.append(c_name)
-                except KeyError: 
+                except KeyError:
                     pass
             else:
                 collection_dfs.append(ft_df)
@@ -287,15 +290,15 @@ def print_available_collections(
             print("Passed field_type not found")
             return
         df = pd.concat(collection_dfs, names=["collection"], keys=collection_names)
-        df.fillna("-",inplace=True)
-        if collection != None:
-            try: 
+        df.fillna("-", inplace=True)
+        if collection is not None:
+            try:
                 df = df.loc[collection]
-            except KeyError: 
+            except KeyError:
                 print("Passed collection not found")
                 return
         if not details:
-            df = df.filter('')
+            df = df.filter("")
         return df
 
     def set_bbox(self, bbox=None):
@@ -309,13 +312,22 @@ def set_bbox(self, bbox=None):
         if bbox:
             self._request_inputs.bbox = bbox
 
-    def set_fields(self,
-                   observation_fields=None, measurement_fields=None,
-                   ica_fields=None, sca_fields=None, mca_fields=None,
-                   mie_profile_fields=None, rayleigh_profile_fields=None,
-                   rayleigh_wind_fields=None, mie_wind_fields=None,
-                   rayleigh_grouping_fields=None, mie_grouping_fields=None,
-                   group_fields=None, fields=None):
+    def set_fields(
+        self,
+        observation_fields=None,
+        measurement_fields=None,
+        ica_fields=None,
+        sca_fields=None,
+        mca_fields=None,
+        mie_profile_fields=None,
+        rayleigh_profile_fields=None,
+        rayleigh_wind_fields=None,
+        mie_wind_fields=None,
+        rayleigh_grouping_fields=None,
+        mie_grouping_fields=None,
+        group_fields=None,
+        fields=None,
+    ):
         if observation_fields:
             self._request_inputs.observation_fields = ",".join(observation_fields)
         if measurement_fields:
@@ -329,11 +341,15 @@ def set_fields(self,
         if mie_profile_fields:
             self._request_inputs.mie_profile_fields = ",".join(mie_profile_fields)
         if rayleigh_profile_fields:
-            self._request_inputs.rayleigh_profile_fields = ",".join(rayleigh_profile_fields)
+            self._request_inputs.rayleigh_profile_fields = ",".join(
+                rayleigh_profile_fields
+            )
         if rayleigh_wind_fields:
             self._request_inputs.rayleigh_wind_fields = ",".join(rayleigh_wind_fields)
         if rayleigh_grouping_fields:
-            self._request_inputs.rayleigh_grouping_fields = ",".join(rayleigh_grouping_fields)
+            self._request_inputs.rayleigh_grouping_fields = ",".join(
+                rayleigh_grouping_fields
+            )
         if mie_grouping_fields:
             self._request_inputs.mie_grouping_fields = ",".join(mie_grouping_fields)
         if mie_wind_fields:
@@ -342,7 +358,7 @@ def set_fields(self,
             self._request_inputs.group_fields = ",".join(group_fields)
         if fields:
             self._request_inputs.fields = ",".join(fields)
-    
+
     def set_variables(self, aux_type=None, fields=None, dsd_info=False):
         self._request_inputs.aux_type = aux_type
         self._request_inputs.fields = fields
@@ -365,10 +381,7 @@ def set_range_filter(self, parameter=None, minimum=None, maximum=None):
         if not isinstance(parameter, str):
             raise TypeError("parameter must be a str")
         # Update filter dictionary
-        self._filterlist[parameter] = {
-            "min": minimum,
-            "max": maximum
-        }
+        self._filterlist[parameter] = {"min": minimum, "max": maximum}
         # Update the inputs object with dictionary converted
         # to JSON used by XML template
         self._request_inputs.filters = json.dumps(self._filterlist)
@@ -380,7 +393,7 @@ def clear_range_filter(self):
         self._request_inputs.filters = None
         return self
 
-    def get_from_file(self, path=None, filetype='nc'):
+    def get_from_file(self, path=None, filetype="nc"):
         """Get VirES ReturnedData object from file path
 
         Allows loading of locally saved netCDF file (e.g. using to_file method)
@@ -391,9 +404,10 @@ def get_from_file(self, path=None, filetype='nc'):
             filetype (str)
 
         """
-        if filetype != 'nc':
+        if filetype != "nc":
             raise NotImplementedError(
-                "Currently only loading of netCDF files is supported")
+                "Currently only loading of netCDF files is supported"
+            )
         df = ReturnedDataFile(filetype=filetype)
         df._file.name = path
-        return df
\ No newline at end of file
+        return df
diff --git a/viresclient/_client_swarm.py b/src/viresclient/_client_swarm.py
similarity index 65%
rename from viresclient/_client_swarm.py
rename to src/viresclient/_client_swarm.py
index 830c773..0511f7e 100644
--- a/viresclient/_client_swarm.py
+++ b/src/viresclient/_client_swarm.py
@@ -2,166 +2,201 @@
 
 import datetime
 import json
-from collections import OrderedDict
 import os
 import sys
+from collections import OrderedDict
 from io import StringIO
-from pandas import read_csv
-from tqdm import tqdm
 from textwrap import dedent
 from warnings import warn
 
+from pandas import read_csv
+from tqdm import tqdm
+
+from ._client import TEMPLATE_FILES, ClientRequest, WPSInputs
+from ._data import CONFIG_SWARM
+from ._data_handling import ReturnedDataFile
 from ._wps.environment import JINJA2_ENVIRONMENT
 from ._wps.time_util import parse_datetime
-from ._client import WPSInputs, ClientRequest, TEMPLATE_FILES
-from ._data_handling import ReturnedDataFile
-from ._data import CONFIG_SWARM
 
 TEMPLATE_FILES = {
     **TEMPLATE_FILES,
-    'sync': "vires_fetch_filtered_data.xml",
-    'async': "vires_fetch_filtered_data_async.xml",
-    'model_info': "vires_get_model_info.xml",
-    'times_from_orbits': "vires_times_from_orbits.xml",
-    'get_observatories': 'vires_get_observatories.xml',
-    'get_conjunctions': 'vires_get_conjunctions.xml',
+    "sync": "vires_fetch_filtered_data.xml",
+    "async": "vires_fetch_filtered_data_async.xml",
+    "model_info": "vires_get_model_info.xml",
+    "times_from_orbits": "vires_times_from_orbits.xml",
+    "get_observatories": "vires_get_observatories.xml",
+    "get_conjunctions": "vires_get_conjunctions.xml",
 }
 
 REFERENCES = {
-    'General Swarm': (" Swarm Data Handbook, https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook ",
-                      " The Swarm Satellite Constellation Application and Research Facility (SCARF) and Swarm data products, https://doi.org/10.5047/eps.2013.07.001 ",
-                      " Swarm Science Data Processing and Products (2013), https://link.springer.com/journal/40623/65/11/page/1 ",
-                      " Special issue “Swarm science results after 2 years in space (2016), https://www.springeropen.com/collections/swsr ",
-                      " Earth's Magnetic Field: Understanding Geomagnetic Sources from the Earth's Interior and its Environment (2017), https://link.springer.com/journal/11214/206/1/page/1 ")
-    }
+    "General Swarm": (
+        " Swarm Data Handbook, https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook ",
+        " The Swarm Satellite Constellation Application and Research Facility (SCARF) and Swarm data products, https://doi.org/10.5047/eps.2013.07.001 ",
+        " Swarm Science Data Processing and Products (2013), https://link.springer.com/journal/40623/65/11/page/1 ",
+        " Special issue “Swarm science results after 2 years in space (2016), https://www.springeropen.com/collections/swsr ",
+        " Earth's Magnetic Field: Understanding Geomagnetic Sources from the Earth's Interior and its Environment (2017), https://link.springer.com/journal/11214/206/1/page/1 ",
+    )
+}
 
 MODEL_REFERENCES = {
-    'IGRF':
-        (" International Geomagnetic Reference Field: the thirteenth generation, (https://doi.org/10.1186/s40623-020-01288-x) ",
-         " https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html "),
-    'IGRF12':
-        (" International Geomagnetic Reference Field: the 12th generation, https://doi.org/10.1186/s40623-015-0228-9 ",
-         " https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html "
-         " deprecated model identifier, use IGRF instead"),
-    'CHAOS-Core':
-        ("CHAOS-7 Core field (SH degrees 1-20)",
-         " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ "),
-    'CHAOS-Static':
-        ("CHAOS-7 crust field (SH degrees 21-185)",
-         " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ "),
-    'CHAOS-MMA-Primary':
-        ("CHAOS-7 Primary (external) magnetospheric field",
-         " hhttp://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ "),
-    'CHAOS-MMA-Secondary':
-        ("CHAOS-7 Secondary (internal) magnetospheric field",
-         " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ "),
-    'CHAOS-6-Core':
-        ("CHAOS Core field",
-         " deprecated model identifier, use CHAOS-Core instead"),
-    'CHAOS-6-Static':
-        ("CHAOS crust field",
-         " deprecated model identifier, use CHAOS-Static instead"),
-    'CHAOS-6-MMA-Primary':
-        ("CHAOS Primary (external) magnetospheric field",
-         " deprecated model identifier, use CHAOS-MMA-Primary instead"),
-    'CHAOS-6-MMA-Secondary':
-        ("CHAOS-Secondary (internal) magnetospheric field",
-         " deprecated model identifier, use CHAOS-MMA-Secondary instead"),
-    'MF7':
-        ("MF7 crustal field model, derived from CHAMP satellite observations",
-         " http://geomag.org/models/MF7.html"),
-    'LCS-1':
-        ("The LCS-1 high-resolution lithospheric field model, derived from CHAMP and Swarm satellite observations",
-         " http://www.spacecenter.dk/files/magnetic-models/LCS-1/"),
-    'MCO_SHA_2C':
-        ("[Comprehensive Inversion]: Core field of CIY4",
-         " A comprehensive model of Earth’s magnetic field determined from 4 years of Swarm satellite observations, https://doi.org/10.1186/s40623-018-0896-3 ",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MCO/SW_OPER_MCO_VAL_2C_20131201T000000_20180101T000000_0401.ZIP "),
-    'MCO_SHA_2D':
-        ("[Dedicated Chain]: Core field",
-         "An algorithm for deriving core magnetic field models from the Swarm data set, https://doi.org/10.5047/eps.2013.07.005 ",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MCO/SW_OPER_MCO_VAL_2D_20131126T000000_20180101T000000_0401.ZIP "),
-    'MLI_SHA_2C':
-        ("[Comprehensive Inversion]: Lithospheric field of CIY4",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MLI/SW_OPER_MLI_VAL_2C_00000000T000000_99999999T999999_0401.ZIP"),
-    'MLI_SHA_2D':
-        ("[Dedicated Chain]: Lithospheric field",
-         " Swarm SCARF Dedicated Lithospheric Field Inversion chain, https://doi.org/10.5047/eps.2013.07.008 ",
-         " Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MLI/SW_OPER_MLI_VAL_2D_00000000T000000_99999999T999999_0401.ZIP "),
-    'MLI_SHA_2E':
-        ("[Extended dedicated chain]: Lithospheric field",
-         " Joint inversion of Swarm, CHAMP, and WDMAM data ",
-         " https://swarm-diss.eo.esa.int/?do=download&file=swarm%2FLevel2longterm%2FMLI%2FSW_OPER_MLI_VAL_2E_00000000T000000_99999999T999999_0502.ZIP "),
-    'MMA_SHA_2C-Primary':
-        ("[Comprehensive Inversion]: Primary (external) magnetospheric field of CIY4",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MMA/SW_OPER_MMA_VAL_2C_20131201T000000_20180101T000000_0401.ZIP"),
-    'MMA_SHA_2C-Secondary':
-        ("[Comprehensive Inversion]: Secondary (internal/induced) magnetospheric field of CIY4",),
-    'MMA_SHA_2F-Primary':
-        ("[Fast-Track Product]: Primary (external) magnetospheric field",
-         " Rapid modelling of the large-scale magnetospheric field from Swarm satellite data, https://doi.org/10.5047/eps.2013.09.003 "),
-    'MMA_SHA_2F-Secondary':
-        ("[Fast-Track Product]: Secondary (internal/induced) magnetospheric field",),
-    'MIO_SHA_2C-Primary':
-        ("[Comprehensive Inversion]: Primary (external) ionospheric field of CIY4",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MIO/SW_OPER_MIO_VAL_2C_00000000T000000_99999999T999999_0401.ZIP "),
-    'MIO_SHA_2C-Secondary':
-        ("[Comprehensive Inversion]: Secondary (external/induced) ionospheric field of CIY4",),
-    'MIO_SHA_2D-Primary':
-        ("[Dedicated Chain]: Primary (external) ionospheric field, DIFI",
-         " Swarm SCARF dedicated ionospheric field inversion chain, https://doi.org/10.5047/eps.2013.08.006 ",
-         " First results from the Swarm Dedicated Ionospheric Field Inversion chain, https://doi.org/10.1186/s40623-016-0481-6 ",
-         " http://geomag.colorado.edu/difi-3 ",
-         "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MIO/SW_OPER_MIO_VAL_2D_20131201T000000_20171231T235959_0402.ZIP "),
-    'MIO_SHA_2D-Secondary':
-        ("[Dedicated Chain]: Secondary (external/induced) ionospheric field, DIFI",),
-    'AMPS':
-        ("AMPS - associated magnetic field, https://github.com/klaundal/pyAMPS",),
-    'MCO_SHA_2X':
-        ("Alias for 'CHAOS-Core'",),
-    'CHAOS':
-        ("Alias for 'CHAOS-Core' + 'CHAOS-Static' + 'CHAOS-MMA-Primary' + 'CHAOS-MMA-Secondary'",),
-    'CHAOS-MMA':
-        ("Alias for 'CHAOS-MMA-Primary' + 'CHAOS-MMA-Secondary'",),
-    'MMA_SHA_2C':
-        ("Alias for 'MMA_SHA_2C-Primary' + 'MMA_SHA_2C-Secondary'",),
-    'MMA_SHA_2F':
-        ("Alias for 'MMA_SHA_2F-Primary' + 'MMA_SHA_2F-Secondary'",),
-    'MIO_SHA_2C':
-        ("Alias for 'MIO_SHA_2C-Primary' + 'MIO_SHA_2C-Secondary'",),
-    'MIO_SHA_2D':
-        ("Alias for 'MIO_SHA_2D-Primary' + 'MIO_SHA_2D-Secondary'",),
-    'SwarmCI':
-        ("Alias for 'MCO_SHA_2C' + 'MLI_SHA_2C' + 'MIO_SHA_2C-Primary' + 'MIO_SHA_2C-Secondary' + 'MMA_SHA_2C-Primary' + 'MMA_SHA_2C-Secondary'",),
+    "IGRF": (
+        " International Geomagnetic Reference Field: the thirteenth generation, (https://doi.org/10.1186/s40623-020-01288-x) ",
+        " https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html ",
+    ),
+    "IGRF12": (
+        " International Geomagnetic Reference Field: the 12th generation, https://doi.org/10.1186/s40623-015-0228-9 ",
+        " https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html "
+        " deprecated model identifier, use IGRF instead",
+    ),
+    "CHAOS-Core": (
+        "CHAOS-7 Core field (SH degrees 1-20)",
+        " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ ",
+    ),
+    "CHAOS-Static": (
+        "CHAOS-7 crust field (SH degrees 21-185)",
+        " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ ",
+    ),
+    "CHAOS-MMA-Primary": (
+        "CHAOS-7 Primary (external) magnetospheric field",
+        " hhttp://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ ",
+    ),
+    "CHAOS-MMA-Secondary": (
+        "CHAOS-7 Secondary (internal) magnetospheric field",
+        " http://www.spacecenter.dk/files/magnetic-models/CHAOS-7/ ",
+    ),
+    "CHAOS-6-Core": (
+        "CHAOS Core field",
+        " deprecated model identifier, use CHAOS-Core instead",
+    ),
+    "CHAOS-6-Static": (
+        "CHAOS crust field",
+        " deprecated model identifier, use CHAOS-Static instead",
+    ),
+    "CHAOS-6-MMA-Primary": (
+        "CHAOS Primary (external) magnetospheric field",
+        " deprecated model identifier, use CHAOS-MMA-Primary instead",
+    ),
+    "CHAOS-6-MMA-Secondary": (
+        "CHAOS-Secondary (internal) magnetospheric field",
+        " deprecated model identifier, use CHAOS-MMA-Secondary instead",
+    ),
+    "MF7": (
+        "MF7 crustal field model, derived from CHAMP satellite observations",
+        " http://geomag.org/models/MF7.html",
+    ),
+    "LCS-1": (
+        "The LCS-1 high-resolution lithospheric field model, derived from CHAMP and Swarm satellite observations",
+        " http://www.spacecenter.dk/files/magnetic-models/LCS-1/",
+    ),
+    "MCO_SHA_2C": (
+        "[Comprehensive Inversion]: Core field of CIY4",
+        " A comprehensive model of Earth’s magnetic field determined from 4 years of Swarm satellite observations, https://doi.org/10.1186/s40623-018-0896-3 ",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MCO/SW_OPER_MCO_VAL_2C_20131201T000000_20180101T000000_0401.ZIP ",
+    ),
+    "MCO_SHA_2D": (
+        "[Dedicated Chain]: Core field",
+        "An algorithm for deriving core magnetic field models from the Swarm data set, https://doi.org/10.5047/eps.2013.07.005 ",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MCO/SW_OPER_MCO_VAL_2D_20131126T000000_20180101T000000_0401.ZIP ",
+    ),
+    "MLI_SHA_2C": (
+        "[Comprehensive Inversion]: Lithospheric field of CIY4",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MLI/SW_OPER_MLI_VAL_2C_00000000T000000_99999999T999999_0401.ZIP",
+    ),
+    "MLI_SHA_2D": (
+        "[Dedicated Chain]: Lithospheric field",
+        " Swarm SCARF Dedicated Lithospheric Field Inversion chain, https://doi.org/10.5047/eps.2013.07.008 ",
+        " Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MLI/SW_OPER_MLI_VAL_2D_00000000T000000_99999999T999999_0401.ZIP ",
+    ),
+    "MLI_SHA_2E": (
+        "[Extended dedicated chain]: Lithospheric field",
+        " Joint inversion of Swarm, CHAMP, and WDMAM data ",
+        " https://swarm-diss.eo.esa.int/?do=download&file=swarm%2FLevel2longterm%2FMLI%2FSW_OPER_MLI_VAL_2E_00000000T000000_99999999T999999_0502.ZIP ",
+    ),
+    "MMA_SHA_2C-Primary": (
+        "[Comprehensive Inversion]: Primary (external) magnetospheric field of CIY4",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MMA/SW_OPER_MMA_VAL_2C_20131201T000000_20180101T000000_0401.ZIP",
+    ),
+    "MMA_SHA_2C-Secondary": (
+        "[Comprehensive Inversion]: Secondary (internal/induced) magnetospheric field of CIY4",
+    ),
+    "MMA_SHA_2F-Primary": (
+        "[Fast-Track Product]: Primary (external) magnetospheric field",
+        " Rapid modelling of the large-scale magnetospheric field from Swarm satellite data, https://doi.org/10.5047/eps.2013.09.003 ",
+    ),
+    "MMA_SHA_2F-Secondary": (
+        "[Fast-Track Product]: Secondary (internal/induced) magnetospheric field",
+    ),
+    "MIO_SHA_2C-Primary": (
+        "[Comprehensive Inversion]: Primary (external) ionospheric field of CIY4",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MIO/SW_OPER_MIO_VAL_2C_00000000T000000_99999999T999999_0401.ZIP ",
+    ),
+    "MIO_SHA_2C-Secondary": (
+        "[Comprehensive Inversion]: Secondary (external/induced) ionospheric field of CIY4",
+    ),
+    "MIO_SHA_2D-Primary": (
+        "[Dedicated Chain]: Primary (external) ionospheric field, DIFI",
+        " Swarm SCARF dedicated ionospheric field inversion chain, https://doi.org/10.5047/eps.2013.08.006 ",
+        " First results from the Swarm Dedicated Ionospheric Field Inversion chain, https://doi.org/10.1186/s40623-016-0481-6 ",
+        " http://geomag.colorado.edu/difi-3 ",
+        "Validation: ftp://swarm-diss.eo.esa.int/Level2longterm/MIO/SW_OPER_MIO_VAL_2D_20131201T000000_20171231T235959_0402.ZIP ",
+    ),
+    "MIO_SHA_2D-Secondary": (
+        "[Dedicated Chain]: Secondary (external/induced) ionospheric field, DIFI",
+    ),
+    "AMPS": ("AMPS - associated magnetic field, https://github.com/klaundal/pyAMPS",),
+    "MCO_SHA_2X": ("Alias for 'CHAOS-Core'",),
+    "CHAOS": (
+        "Alias for 'CHAOS-Core' + 'CHAOS-Static' + 'CHAOS-MMA-Primary' + 'CHAOS-MMA-Secondary'",
+    ),
+    "CHAOS-MMA": ("Alias for 'CHAOS-MMA-Primary' + 'CHAOS-MMA-Secondary'",),
+    "MMA_SHA_2C": ("Alias for 'MMA_SHA_2C-Primary' + 'MMA_SHA_2C-Secondary'",),
+    "MMA_SHA_2F": ("Alias for 'MMA_SHA_2F-Primary' + 'MMA_SHA_2F-Secondary'",),
+    "MIO_SHA_2C": ("Alias for 'MIO_SHA_2C-Primary' + 'MIO_SHA_2C-Secondary'",),
+    "MIO_SHA_2D": ("Alias for 'MIO_SHA_2D-Primary' + 'MIO_SHA_2D-Secondary'",),
+    "SwarmCI": (
+        "Alias for 'MCO_SHA_2C' + 'MLI_SHA_2C' + 'MIO_SHA_2C-Primary' + 'MIO_SHA_2C-Secondary' + 'MMA_SHA_2C-Primary' + 'MMA_SHA_2C-Secondary'",
+    ),
 }
 
 DEPRECATED_MODELS = {
-    'IGRF12': "Use IGRF instead.",
-    'CHAOS-6-Core': "Use CHAOS-Core instead.",
-    'CHAOS-6-Static': "Use CHAOS-Static instead.",
-    'CHAOS-6-MMA-Primary': "Use CHAOS-MMA-Primary instead.",
-    'CHAOS-6-MMA-Secondary': "Use CHAOS-MMA-Secondary instead.",
+    "IGRF12": "Use IGRF instead.",
+    "CHAOS-6-Core": "Use CHAOS-Core instead.",
+    "CHAOS-6-Static": "Use CHAOS-Static instead.",
+    "CHAOS-6-MMA-Primary": "Use CHAOS-MMA-Primary instead.",
+    "CHAOS-6-MMA-Secondary": "Use CHAOS-MMA-Secondary instead.",
 }
 
 COLLECTION_REFERENCES = {
-    "MAG": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#MAGX_LR_1B_Product ",
-            ),
-    "MAG_HR": ("https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#MAGX_HR_1B_Product ",
-            ),
-    "EFI": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#EFIX_LP_1B_Product ",
-            ),
-    "IBI": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#IBIxTMS_2F ",
-            " https://earth.esa.int/documents/10174/1514862/Swarm_L2_IBI_product_description "),
-    "TEC": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#TECxTMS_2F ",
-            " https://earth.esa.int/documents/10174/1514862/Swarm_Level-2_TEC_Product_Description "),
-    "FAC": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#FAC_TMS_2F ",
-            " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#FACxTMS_2F ",
-            " https://earth.esa.int/documents/10174/1514862/Swarm_L2_FAC_single_product_description ",
-            " https://earth.esa.int/documents/10174/1514862/Swarm-L2-FAC-Dual-Product-Description "),
-    "EEF": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#EEFxTMS_2F ",
-            " https://earth.esa.int/documents/10174/1514862/Swarm-Level-2-EEF-Product-Description "),
-    "IPD": (" https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#IPDxIPR_2F ",
-            ),
+    "MAG": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#MAGX_LR_1B_Product ",
+    ),
+    "MAG_HR": (
+        "https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#MAGX_HR_1B_Product ",
+    ),
+    "EFI": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-1b-product-definitions#EFIX_LP_1B_Product ",
+    ),
+    "IBI": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#IBIxTMS_2F ",
+        " https://earth.esa.int/documents/10174/1514862/Swarm_L2_IBI_product_description ",
+    ),
+    "TEC": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#TECxTMS_2F ",
+        " https://earth.esa.int/documents/10174/1514862/Swarm_Level-2_TEC_Product_Description ",
+    ),
+    "FAC": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#FAC_TMS_2F ",
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#FACxTMS_2F ",
+        " https://earth.esa.int/documents/10174/1514862/Swarm_L2_FAC_single_product_description ",
+        " https://earth.esa.int/documents/10174/1514862/Swarm-L2-FAC-Dual-Product-Description ",
+    ),
+    "EEF": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#EEFxTMS_2F ",
+        " https://earth.esa.int/documents/10174/1514862/Swarm-Level-2-EEF-Product-Description ",
+    ),
+    "IPD": (
+        " https://earth.esa.int/web/guest/missions/esa-eo-missions/swarm/data-handbook/level-2-product-definitions#IPDxIPR_2F ",
+    ),
     "AUX_OBSH": ("https://doi.org/10.5047/eps.2013.07.011",),
     "AUX_OBSM": ("https://doi.org/10.5047/eps.2013.07.011",),
     "AUX_OBSS": ("https://doi.org/10.5047/eps.2013.07.011",),
@@ -171,16 +206,24 @@
     "AEJ_PBL": ("https://earth.esa.int/eogateway/activities/swarm-aebs",),
     "AEJ_PBS": ("https://earth.esa.int/eogateway/activities/swarm-aebs",),
     "AOB_FAC": ("https://earth.esa.int/eogateway/activities/swarm-aebs",),
-    "MIT_LP": ("https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",),
-    "MIT_TEC": ("https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",),
-    "PPI_FAC": ("https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",),
+    "MIT_LP": (
+        "https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",
+    ),
+    "MIT_TEC": (
+        "https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",
+    ),
+    "PPI_FAC": (
+        "https://earth.esa.int/eogateway/activities/plasmapause-related-boundaries-in-the-topside-ionosphere-as-derived-from-swarm-measurements",
+    ),
     "MAG_CS": ("https://doi.org/10.1186/s40623-020-01171-9",),
     "MAG_GRACE": ("https://doi.org/10.1186/s40623-021-01373-9",),
     "MAG_GFO": ("https://doi.org/10.1186/s40623-021-01364-w",),
 }
 for mission in ("SW", "OR", "CH", "CR", "CO"):
     for cadence in ("1M", "4M"):
-        COLLECTION_REFERENCES[f"VOBS_{mission}_{cadence}"] = ("https://earth.esa.int/eogateway/activities/gvo",)
+        COLLECTION_REFERENCES[f"VOBS_{mission}_{cadence}"] = (
+            "https://earth.esa.int/eogateway/activities/gvo",
+        )
 
 DATA_CITATIONS = {
     "AUX_OBSH": "ftp://ftp.nerc-murchison.ac.uk/geomag/Swarm/AUX_OBS/hour/README",
@@ -194,31 +237,32 @@
 
 
 class SwarmWPSInputs(WPSInputs):
-    """Holds the set of inputs to be passed to the request template for Swarm
-    """
+    """Holds the set of inputs to be passed to the request template for Swarm"""
 
     NAMES = [
-        'collection_ids',
-        'model_expression',
-        'begin_time',
-        'end_time',
-        'variables',
-        'filters',
-        'sampling_step',
-        'response_type',
-        'custom_shc',
-        ]
+        "collection_ids",
+        "model_expression",
+        "begin_time",
+        "end_time",
+        "variables",
+        "filters",
+        "sampling_step",
+        "response_type",
+        "custom_shc",
+    ]
 
-    def __init__(self,
-                 collection_ids=None,
-                 model_expression=None,
-                 begin_time=None,
-                 end_time=None,
-                 variables=None,
-                 filters=None,
-                 sampling_step=None,
-                 response_type=None,
-                 custom_shc=None):
+    def __init__(
+        self,
+        collection_ids=None,
+        model_expression=None,
+        begin_time=None,
+        end_time=None,
+        variables=None,
+        filters=None,
+        sampling_step=None,
+        response_type=None,
+        custom_shc=None,
+    ):
         # Set up default values
         # Obligatory - these must be replaced before the request is made
         self.collection_ids = None if collection_ids is None else collection_ids
@@ -227,7 +271,7 @@ def __init__(self,
         self.response_type = None if response_type is None else response_type
         # Optional - these defaults will be used if not replaced before the
         #            request is made
-        self.model_expression = str() if model_expression is None else model_expression
+        self.model_expression = "" if model_expression is None else model_expression
         self.variables = [] if variables is None else variables
         self.filters = None if filters is None else filters
         self.sampling_step = None if sampling_step is None else sampling_step
@@ -407,132 +451,128 @@ class SwarmRequest(ClientRequest):
         logging_level (str):
 
     """
+
     MISSION_SPACECRAFTS = {
-        'Swarm': ['A', 'B', 'C'],
-        'GRACE': ['1', '2'],
-        'GRACE-FO': ['1', '2'],
-        'CryoSat-2': None,
+        "Swarm": ["A", "B", "C"],
+        "GRACE": ["1", "2"],
+        "GRACE-FO": ["1", "2"],
+        "CryoSat-2": None,
     }
 
     CONJUNCTION_MISISON_SPACECRAFT_PAIRS = {
-        (('Swarm', 'A'), ('Swarm', 'B')),
+        (("Swarm", "A"), ("Swarm", "B")),
     }
 
     COLLECTIONS = {
-        "MAG": ["SW_OPER_MAG{}_LR_1B".format(x) for x in "ABC"],
-        "MAG_HR": ["SW_OPER_MAG{}_HR_1B".format(x) for x in "ABC"],
-        "EFI": ["SW_OPER_EFI{}_LP_1B".format(x) for x in "ABC"],
-        "IBI": ["SW_OPER_IBI{}TMS_2F".format(x) for x in "ABC"],
-        "TEC": ["SW_OPER_TEC{}TMS_2F".format(x) for x in "ABC"],
-        "FAC": ["SW_OPER_FAC{}TMS_2F".format(x) for x in "ABC_"],
-        "EEF": ["SW_OPER_EEF{}TMS_2F".format(x) for x in "ABC"],
-        "IPD": ["SW_OPER_IPD{}IRR_2F".format(x) for x in "ABC"],
-
-        "AEJ_LPL": ["SW_OPER_AEJ{}LPL_2F".format(x) for x in "ABC"],
-        "AEJ_LPL:Quality": [
-            "SW_OPER_AEJ{}LPL_2F:Quality".format(x) for x in "ABC"
-        ],
-        "AEJ_LPS": ["SW_OPER_AEJ{}LPS_2F".format(x) for x in "ABC"],
-        "AEJ_LPS:Quality": [
-            "SW_OPER_AEJ{}LPS_2F:Quality".format(x) for x in "ABC"
-        ],
-        "AEJ_PBL": ["SW_OPER_AEJ{}PBL_2F".format(x) for x in "ABC"],
-        "AEJ_PBS": ["SW_OPER_AEJ{}PBS_2F".format(x) for x in "ABC"],
+        "MAG": [f"SW_OPER_MAG{x}_LR_1B" for x in "ABC"],
+        "MAG_HR": [f"SW_OPER_MAG{x}_HR_1B" for x in "ABC"],
+        "EFI": [f"SW_OPER_EFI{x}_LP_1B" for x in "ABC"],
+        "IBI": [f"SW_OPER_IBI{x}TMS_2F" for x in "ABC"],
+        "TEC": [f"SW_OPER_TEC{x}TMS_2F" for x in "ABC"],
+        "FAC": [f"SW_OPER_FAC{x}TMS_2F" for x in "ABC_"],
+        "EEF": [f"SW_OPER_EEF{x}TMS_2F" for x in "ABC"],
+        "IPD": [f"SW_OPER_IPD{x}IRR_2F" for x in "ABC"],
+        "AEJ_LPL": [f"SW_OPER_AEJ{x}LPL_2F" for x in "ABC"],
+        "AEJ_LPL:Quality": [f"SW_OPER_AEJ{x}LPL_2F:Quality" for x in "ABC"],
+        "AEJ_LPS": [f"SW_OPER_AEJ{x}LPS_2F" for x in "ABC"],
+        "AEJ_LPS:Quality": [f"SW_OPER_AEJ{x}LPS_2F:Quality" for x in "ABC"],
+        "AEJ_PBL": [f"SW_OPER_AEJ{x}PBL_2F" for x in "ABC"],
+        "AEJ_PBS": [f"SW_OPER_AEJ{x}PBS_2F" for x in "ABC"],
         "AEJ_PBS:GroundMagneticDisturbance": [
-            "SW_OPER_AEJ{}PBS_2F:GroundMagneticDisturbance".format(x) for x in "ABC"
+            f"SW_OPER_AEJ{x}PBS_2F:GroundMagneticDisturbance" for x in "ABC"
         ],
-        "AOB_FAC": ["SW_OPER_AOB{}FAC_2F".format(x) for x in "ABC"],
+        "AOB_FAC": [f"SW_OPER_AOB{x}FAC_2F" for x in "ABC"],
         "AUX_OBSH": [
             "SW_OPER_AUX_OBSH2_",
-            *[f"SW_OPER_AUX_OBSH2_:{code}" for code in IAGA_CODES]
+            *[f"SW_OPER_AUX_OBSH2_:{code}" for code in IAGA_CODES],
         ],
         "AUX_OBSM": [
             "SW_OPER_AUX_OBSM2_",
-            *[f"SW_OPER_AUX_OBSM2_:{code}" for code in IAGA_CODES]
+            *[f"SW_OPER_AUX_OBSM2_:{code}" for code in IAGA_CODES],
         ],
         "AUX_OBSS": [
             "SW_OPER_AUX_OBSS2_",
-            *[f"SW_OPER_AUX_OBSS2_:{code}" for code in IAGA_CODES]
+            *[f"SW_OPER_AUX_OBSS2_:{code}" for code in IAGA_CODES],
         ],
         "VOBS_SW_1M": [
             "SW_OPER_VOBS_1M_2_",
-            *[f"SW_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES]
+            *[f"SW_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_SW_4M": [
             "SW_OPER_VOBS_4M_2_",
-            *[f"SW_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES]
+            *[f"SW_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CH_1M": [
             "CH_OPER_VOBS_1M_2_",
-            *[f"CH_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES]
+            *[f"CH_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CR_1M": [
             "CR_OPER_VOBS_1M_2_",
-            *[f"CR_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES]
+            *[f"CR_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_OR_1M": [
             "OR_OPER_VOBS_1M_2_",
-            *[f"OR_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES]
+            *[f"OR_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CO_1M": [
             "CO_OPER_VOBS_1M_2_",
-            *[f"CO_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES]
+            *[f"CO_OPER_VOBS_1M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_OR_4M": [
             "OR_OPER_VOBS_4M_2_",
-            *[f"OR_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES]
+            *[f"OR_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CH_4M": [
             "CH_OPER_VOBS_4M_2_",
-            *[f"CH_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES]
+            *[f"CH_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CR_4M": [
             "CR_OPER_VOBS_4M_2_",
-            *[f"CR_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES]
+            *[f"CR_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CO_4M": [
             "CO_OPER_VOBS_4M_2_",
-            *[f"CO_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES]
+            *[f"CO_OPER_VOBS_4M_2_:{site}" for site in VOBS_SITES],
         ],
         "VOBS_SW_1M:SecularVariation": [
             "SW_OPER_VOBS_1M_2_:SecularVariation",
-            *[f"SW_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"SW_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_SW_4M:SecularVariation": [
             "SW_OPER_VOBS_4M_2_:SecularVariation",
-            *[f"SW_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"SW_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CH_1M:SecularVariation": [
             "CH_OPER_VOBS_1M_2_:SecularVariation",
-            *[f"CH_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CH_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CR_1M:SecularVariation": [
             "CR_OPER_VOBS_1M_2_:SecularVariation",
-            *[f"CR_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CR_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_OR_1M:SecularVariation": [
             "OR_OPER_VOBS_1M_2_:SecularVariation",
-            *[f"OR_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"OR_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CO_1M:SecularVariation": [
             "CO_OPER_VOBS_1M_2_:SecularVariation",
-            *[f"CO_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CO_OPER_VOBS_1M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_OR_4M:SecularVariation": [
             "OR_OPER_VOBS_4M_2_:SecularVariation",
-            *[f"OR_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"OR_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CH_4M:SecularVariation": [
             "CH_OPER_VOBS_4M_2_:SecularVariation",
-            *[f"CH_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CH_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CR_4M:SecularVariation": [
             "CR_OPER_VOBS_4M_2_:SecularVariation",
-            *[f"CR_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CR_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "VOBS_CO_4M:SecularVariation": [
             "CO_OPER_VOBS_4M_2_:SecularVariation",
-            *[f"CO_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES]
+            *[f"CO_OPER_VOBS_4M_2_:SecularVariation:{site}" for site in VOBS_SITES],
         ],
         "MIT_LP": [f"SW_OPER_MIT{x}_LP_2F" for x in "ABC"],
         "MIT_LP:ID": [f"SW_OPER_MIT{x}_LP_2F:ID" for x in "ABC"],
@@ -541,11 +581,13 @@ class SwarmRequest(ClientRequest):
         "PPI_FAC": [f"SW_OPER_PPI{x}FAC_2F" for x in "ABC"],
         "PPI_FAC:ID": [f"SW_OPER_PPI{x}FAC_2F:ID" for x in "ABC"],
         # Multi-mission magnetic products
-        "MAG_CS": ["CS_OPER_MAG",],
+        "MAG_CS": [
+            "CS_OPER_MAG",
+        ],
         "MAG_GRACE": ["GRACE_A_MAG", "GRACE_B_MAG"],
         "MAG_GFO": ["GF1_OPER_FGM_ACAL_CORR", "GF2_OPER_FGM_ACAL_CORR"],
         # Swarm spacecraft positions
-        "MOD_SC": [f"SW_OPER_MOD{x}_SC_1B" for x in "ABC"]
+        "MOD_SC": [f"SW_OPER_MOD{x}_SC_1B" for x in "ABC"],
     }
 
     OBS_COLLECTIONS = [
@@ -556,8 +598,7 @@ class SwarmRequest(ClientRequest):
         "SW_OPER_VOBS_4M_2_",
         "CH_OPER_VOBS_1M_2_",
         "CR_OPER_VOBS_1M_2_",
-        "OR_OPER_VOBS_1M_2_"
-        "CO_OPER_VOBS_1M_2_",
+        "OR_OPER_VOBS_1M_2_" "CO_OPER_VOBS_1M_2_",
         "OR_OPER_VOBS_4M_2_",
         "CH_OPER_VOBS_4M_2_",
         "CR_OPER_VOBS_4M_2_",
@@ -581,7 +622,7 @@ class SwarmRequest(ClientRequest):
         "MAG_HR": "PT0.019S",  # approx 50Hz (the sampling is not exactly 50Hz)
         "EFI": "PT0.5S",
         "IBI": "PT1S",
-        "TEC": "PT1S",      # Actually more complicated
+        "TEC": "PT1S",  # Actually more complicated
         "FAC": "PT1S",
         "EEF": "PT90M",
         "IPD": "PT1S",
@@ -620,63 +661,153 @@ class SwarmRequest(ClientRequest):
 
     PRODUCT_VARIABLES = {
         "MAG": [
-            "F", "dF_AOCS", "dF_other", "F_error", "B_VFM", "B_NEC", "dB_Sun",
-            "dB_AOCS", "dB_other", "B_error", "q_NEC_CRF", "Att_error",
-            "Flags_F", "Flags_B", "Flags_q", "Flags_Platform", "ASM_Freq_Dev",
-            ],
-        "MAG_HR": [ #NOTE: F is calculated on the fly from B_NEC (F = |B_NEC|)
-            "F", "B_VFM", "B_NEC", "dB_Sun", "dB_AOCS", "dB_other", "B_error",
-            "q_NEC_CRF", "Att_error", "Flags_B", "Flags_q", "Flags_Platform",
-            ],
+            "F",
+            "dF_AOCS",
+            "dF_other",
+            "F_error",
+            "B_VFM",
+            "B_NEC",
+            "dB_Sun",
+            "dB_AOCS",
+            "dB_other",
+            "B_error",
+            "q_NEC_CRF",
+            "Att_error",
+            "Flags_F",
+            "Flags_B",
+            "Flags_q",
+            "Flags_Platform",
+            "ASM_Freq_Dev",
+        ],
+        "MAG_HR": [  # NOTE: F is calculated on the fly from B_NEC (F = |B_NEC|)
+            "F",
+            "B_VFM",
+            "B_NEC",
+            "dB_Sun",
+            "dB_AOCS",
+            "dB_other",
+            "B_error",
+            "q_NEC_CRF",
+            "Att_error",
+            "Flags_B",
+            "Flags_q",
+            "Flags_Platform",
+        ],
         "EFI": [
-            "U_orbit", "Ne", "Ne_error", "Te", "Te_error", "Vs", "Vs_error",
-            "Flags_LP", "Flags_Ne", "Flags_Te", "Flags_Vs",
-            ],
+            "U_orbit",
+            "Ne",
+            "Ne_error",
+            "Te",
+            "Te_error",
+            "Vs",
+            "Vs_error",
+            "Flags_LP",
+            "Flags_Ne",
+            "Flags_Te",
+            "Flags_Vs",
+        ],
         "IBI": [
-            "Bubble_Index", "Bubble_Probability", "Flags_Bubble", "Flags_F",
-            "Flags_B", "Flags_q",
-            ],
+            "Bubble_Index",
+            "Bubble_Probability",
+            "Flags_Bubble",
+            "Flags_F",
+            "Flags_B",
+            "Flags_q",
+        ],
         "TEC": [
-            "GPS_Position", "LEO_Position", "PRN", "L1", "L2", "P1", "P2", "S1",
-            "S2", "Elevation_Angle", "Absolute_VTEC", "Absolute_STEC",
-            "Relative_STEC", "Relative_STEC_RMS", "DCB", "DCB_Error",
-            ],
+            "GPS_Position",
+            "LEO_Position",
+            "PRN",
+            "L1",
+            "L2",
+            "P1",
+            "P2",
+            "S1",
+            "S2",
+            "Elevation_Angle",
+            "Absolute_VTEC",
+            "Absolute_STEC",
+            "Relative_STEC",
+            "Relative_STEC_RMS",
+            "DCB",
+            "DCB_Error",
+        ],
         "FAC": [
-            "IRC", "IRC_Error", "FAC", "FAC_Error", "Flags", "Flags_F",
-            "Flags_B", "Flags_q"],
+            "IRC",
+            "IRC_Error",
+            "FAC",
+            "FAC_Error",
+            "Flags",
+            "Flags_F",
+            "Flags_B",
+            "Flags_q",
+        ],
         "EEF": ["EEF", "EEJ", "RelErr", "Flags"],
         "IPD": [
-            "Ne", "Te", "Background_Ne", "Foreground_Ne", "PCP_flag",
-            "Grad_Ne_at_100km", "Grad_Ne_at_50km", "Grad_Ne_at_20km",
-            "Grad_Ne_at_PCP_edge", "ROD", "RODI10s", "RODI20s",
-            "delta_Ne10s", "delta_Ne20s", "delta_Ne40s",
-            "Num_GPS_satellites", "mVTEC", "mROT", "mROTI10s", "mROTI20s",
-            "IBI_flag", "Ionosphere_region_flag", "IPIR_index",
-            "Ne_quality_flag", "TEC_STD"
-            ],
-        "AEJ_LPL": [
-            "Latitude_QD", "Longitude_QD", "MLT_QD",
-            "J_NE", "J_QD"
+            "Ne",
+            "Te",
+            "Background_Ne",
+            "Foreground_Ne",
+            "PCP_flag",
+            "Grad_Ne_at_100km",
+            "Grad_Ne_at_50km",
+            "Grad_Ne_at_20km",
+            "Grad_Ne_at_PCP_edge",
+            "ROD",
+            "RODI10s",
+            "RODI20s",
+            "delta_Ne10s",
+            "delta_Ne20s",
+            "delta_Ne40s",
+            "Num_GPS_satellites",
+            "mVTEC",
+            "mROT",
+            "mROTI10s",
+            "mROTI20s",
+            "IBI_flag",
+            "Ionosphere_region_flag",
+            "IPIR_index",
+            "Ne_quality_flag",
+            "TEC_STD",
         ],
+        "AEJ_LPL": ["Latitude_QD", "Longitude_QD", "MLT_QD", "J_NE", "J_QD"],
         "AEJ_LPL:Quality": ["RMS_misfit", "Confidence"],
         "AEJ_LPS": [
-            "Latitude_QD", "Longitude_QD", "MLT_QD",
-            "J_CF_NE", "J_DF_NE", "J_CF_SemiQD", "J_DF_SemiQD", "J_R"
-            ],
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "J_CF_NE",
+            "J_DF_NE",
+            "J_CF_SemiQD",
+            "J_DF_SemiQD",
+            "J_R",
+        ],
         "AEJ_LPS:Quality": ["RMS_misfit", "Confidence"],
         "AEJ_PBL": [
-            "Latitude_QD", "Longitude_QD", "MLT_QD",
-            "J_QD",  "Flags", "PointType"
-            ],
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "J_QD",
+            "Flags",
+            "PointType",
+        ],
         "AEJ_PBS": [
-            "Latitude_QD", "Longitude_QD", "MLT_QD",
-            "J_DF_SemiQD", "Flags", "PointType"
-            ],
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "J_DF_SemiQD",
+            "Flags",
+            "PointType",
+        ],
         "AEJ_PBS:GroundMagneticDisturbance": ["B_NE"],
         "AOB_FAC": [
-            "Latitude_QD", "Longitude_QD", "MLT_QD",
-            "Boundary_Flag", "Quality", "Pair_Indicator"
-            ],
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "Boundary_Flag",
+            "Quality",
+            "Pair_Indicator",
+        ],
         "AUX_OBSH": ["B_NEC", "F", "IAGA_code", "Quality", "ObsIndex"],
         "AUX_OBSM": ["B_NEC", "F", "IAGA_code", "Quality"],
         "AUX_OBSS": ["B_NEC", "F", "IAGA_code", "Quality"],
@@ -701,55 +832,158 @@ class SwarmRequest(ClientRequest):
         "VOBS_CR_4M:SecularVariation": ["SiteCode", "B_SV", "sigma_SV"],
         "VOBS_CO_4M:SecularVariation": ["SiteCode", "B_SV", "sigma_SV"],
         "MIT_LP": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "Ne", "Te", "Depth", "DR", "Width", "dL", "PW_Gradient", "EW_Gradient",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "Ne",
+            "Te",
+            "Depth",
+            "DR",
+            "Width",
+            "dL",
+            "PW_Gradient",
+            "EW_Gradient",
             "Quality",
         ],
         "MIT_LP:ID": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "Ne", "Te", "Position_Quality", "PointType",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "Ne",
+            "Te",
+            "Position_Quality",
+            "PointType",
         ],
         "MIT_TEC": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "TEC", "Depth", "DR", "Width", "dL", "PW_Gradient", "EW_Gradient",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "TEC",
+            "Depth",
+            "DR",
+            "Width",
+            "dL",
+            "PW_Gradient",
+            "EW_Gradient",
             "Quality",
         ],
         "MIT_TEC:ID": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "TEC", "Position_Quality", "PointType",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "TEC",
+            "Position_Quality",
+            "PointType",
         ],
         "PPI_FAC": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "Sigma", "PPI", "dL", "Quality",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "Sigma",
+            "PPI",
+            "dL",
+            "Quality",
         ],
         "PPI_FAC:ID": [
-            "Counter", "Latitude_QD", "Longitude_QD", "MLT_QD", "L_value", "SZA",
-            "Position_Quality", "PointType",
+            "Counter",
+            "Latitude_QD",
+            "Longitude_QD",
+            "MLT_QD",
+            "L_value",
+            "SZA",
+            "Position_Quality",
+            "PointType",
         ],
         "MAG_CS": [
-            "F", "B_NEC", "B_mod_NEC", "B_NEC1", "B_NEC2", "B_NEC3",
-            "B_FGM1", "B_FGM2", "B_FGM3", "q_NEC_CRF", "q_error",
+            "F",
+            "B_NEC",
+            "B_mod_NEC",
+            "B_NEC1",
+            "B_NEC2",
+            "B_NEC3",
+            "B_FGM1",
+            "B_FGM2",
+            "B_FGM3",
+            "q_NEC_CRF",
+            "q_error",
         ],
         "MAG_GRACE": [
-            "F", "B_NEC", "B_NEC_raw", "B_FGM",
-            "q_NEC_CRF", "q_error",
+            "F",
+            "B_NEC",
+            "B_NEC_raw",
+            "B_FGM",
+            "q_NEC_CRF",
+            "q_error",
         ],
         "MAG_GFO": [
-            "F", "B_NEC", "B_FGM", "dB_MTQ_FGM", "dB_XI_FGM", "dB_NY_FGM", "dB_BT_FGM",
-            "dB_ST_FGM", "dB_SA_FGM", "dB_BAT_FGM", "q_NEC_FGM", "B_FLAG",
+            "F",
+            "B_NEC",
+            "B_FGM",
+            "dB_MTQ_FGM",
+            "dB_XI_FGM",
+            "dB_NY_FGM",
+            "dB_BT_FGM",
+            "dB_ST_FGM",
+            "dB_SA_FGM",
+            "dB_BAT_FGM",
+            "q_NEC_FGM",
+            "B_FLAG",
         ],
         "MOD_SC": [],
     }
 
     AUXILIARY_VARIABLES = [
-        "Timestamp", "Latitude", "Longitude", "Radius", "Spacecraft",
-        "OrbitDirection", "QDOrbitDirection", "SyncStatus", "Kp10", "Kp", "Dst",
-        "F107", "IMF_BY_GSM", "IMF_BZ_GSM", "IMF_V", "F10_INDEX", "OrbitSource",
-        "OrbitNumber", "AscendingNodeTime", "AscendingNodeLongitude", "QDLat",
-        "QDLon", "QDBasis", "MLT", "SunDeclination", "SunHourAngle",
-        "SunRightAscension", "SunAzimuthAngle", "SunZenithAngle",
-        "SunLongitude", "SunVector", "DipoleAxisVector", "NGPLatitude",
-        "NGPLongitude", "DipoleTiltAngle", "dDst"
+        "Timestamp",
+        "Latitude",
+        "Longitude",
+        "Radius",
+        "Spacecraft",
+        "OrbitDirection",
+        "QDOrbitDirection",
+        "SyncStatus",
+        "Kp10",
+        "Kp",
+        "Dst",
+        "F107",
+        "IMF_BY_GSM",
+        "IMF_BZ_GSM",
+        "IMF_V",
+        "F10_INDEX",
+        "OrbitSource",
+        "OrbitNumber",
+        "AscendingNodeTime",
+        "AscendingNodeLongitude",
+        "QDLat",
+        "QDLon",
+        "QDBasis",
+        "MLT",
+        "SunDeclination",
+        "SunHourAngle",
+        "SunRightAscension",
+        "SunAzimuthAngle",
+        "SunZenithAngle",
+        "SunLongitude",
+        "SunVector",
+        "DipoleAxisVector",
+        "NGPLatitude",
+        "NGPLongitude",
+        "DipoleTiltAngle",
+        "dDst",
     ]
 
     MAGNETIC_MODEL_VARIABLES = {
@@ -761,24 +995,44 @@ class SwarmRequest(ClientRequest):
     }
 
     MAGNETIC_MODELS = [
-        "IGRF", "IGRF12", "LCS-1", "MF7",
-        "CHAOS-Core", "CHAOS-Static", "CHAOS-MMA-Primary", "CHAOS-MMA-Secondary",
-        "CHAOS-6-Core", "CHAOS-6-Static", "CHAOS-6-MMA-Primary", "CHAOS-6-MMA-Secondary",
-        "MCO_SHA_2C", "MCO_SHA_2D", "MLI_SHA_2C", "MLI_SHA_2D", "MLI_SHA_2E",
-        "MMA_SHA_2C-Primary", "MMA_SHA_2C-Secondary",
-        "MMA_SHA_2F-Primary", "MMA_SHA_2F-Secondary",
-        "MIO_SHA_2C-Primary", "MIO_SHA_2C-Secondary",
-        "MIO_SHA_2D-Primary", "MIO_SHA_2D-Secondary",
+        "IGRF",
+        "IGRF12",
+        "LCS-1",
+        "MF7",
+        "CHAOS-Core",
+        "CHAOS-Static",
+        "CHAOS-MMA-Primary",
+        "CHAOS-MMA-Secondary",
+        "CHAOS-6-Core",
+        "CHAOS-6-Static",
+        "CHAOS-6-MMA-Primary",
+        "CHAOS-6-MMA-Secondary",
+        "MCO_SHA_2C",
+        "MCO_SHA_2D",
+        "MLI_SHA_2C",
+        "MLI_SHA_2D",
+        "MLI_SHA_2E",
+        "MMA_SHA_2C-Primary",
+        "MMA_SHA_2C-Secondary",
+        "MMA_SHA_2F-Primary",
+        "MMA_SHA_2F-Secondary",
+        "MIO_SHA_2C-Primary",
+        "MIO_SHA_2C-Secondary",
+        "MIO_SHA_2D-Primary",
+        "MIO_SHA_2D-Secondary",
         "AMPS",
-        "MCO_SHA_2X", "CHAOS", "CHAOS-MMA", "MMA_SHA_2C", "MMA_SHA_2F", "MIO_SHA_2C", "MIO_SHA_2D", "SwarmCI",
+        "MCO_SHA_2X",
+        "CHAOS",
+        "CHAOS-MMA",
+        "MMA_SHA_2C",
+        "MMA_SHA_2F",
+        "MIO_SHA_2C",
+        "MIO_SHA_2D",
+        "SwarmCI",
     ]
 
-    def __init__(self, url=None, token=None,
-                 config=None, logging_level="NO_LOGGING"):
-        super().__init__(
-            url, token, config, logging_level,
-            server_type="Swarm"
-            )
+    def __init__(self, url=None, token=None, config=None, logging_level="NO_LOGGING"):
+        super().__init__(url, token, config, logging_level, server_type="Swarm")
 
         self._available = self._get_available_data()
         self._request_inputs = SwarmWPSInputs()
@@ -792,9 +1046,7 @@ def _get_available_data(cls):
         # Build the reverse mapping: "SW_OPER_MAGA_LR_1B": "MAG" etc
         collections_to_keys = {}
         for key, collections in cls.COLLECTIONS.items():
-            collections_to_keys.update({
-                collection: key for collection in collections
-            })
+            collections_to_keys.update({collection: key for collection in collections})
 
         return {
             "collections": cls.COLLECTIONS,
@@ -804,7 +1056,7 @@ def _get_available_data(cls):
             "models": cls.MAGNETIC_MODELS,
             "model_variables": cls.MAGNETIC_MODEL_VARIABLES,
             "auxiliaries": cls.AUXILIARY_VARIABLES,
-            }
+        }
 
     @staticmethod
     def _parse_models_input(models=None):
@@ -822,22 +1074,23 @@ def _parse_models_input(models=None):
         #  e.g. {"model_name": "model_expression", ..}
         # Check if models input is basic list of strings,
         #  If not, then handle inputs given as dicts or list of tuples
-        if (isinstance(models, list) and
-                all(isinstance(item, str) for item in models)):
+        if isinstance(models, list) and all(isinstance(item, str) for item in models):
             # Convert the models list to an OrderedDict
             model_expressions = OrderedDict()
             for model in models:
-                model_id, _, model_expression = [
+                model_id, _, model_expression = (
                     s.strip() for s in model.partition("=")
-                ]
+                )
                 model_expressions[model_id] = model_expression
         else:
             try:
                 model_expressions = OrderedDict(models)
                 # Check that everything is a string
-                if not all(isinstance(item, str)for item in
-                           [*model_expressions.values()] +
-                           [*model_expressions.keys()]):
+                if not all(
+                    isinstance(item, str)
+                    for item in [*model_expressions.values()]
+                    + [*model_expressions.keys()]
+                ):
                     raise ValueError
             except ValueError:
                 raise ValueError("Invalid models input!")
@@ -851,7 +1104,7 @@ def _parse_models_input(models=None):
             else:
                 s = "=".join([model_id, model_expression])
             model_expression_string = ",".join([model_expression_string, s])
-        model_ids = list(s.strip("\'\"") for s in model_expressions.keys())
+        model_ids = list(s.strip("'\"") for s in model_expressions.keys())
         return model_ids, model_expression_string[1:]
 
     def available_collections(self, groupname=None, details=True):
@@ -871,20 +1124,19 @@ def available_collections(self, groupname=None, details=True):
         collections_short["AUX_OBSH"] = ["SW_OPER_AUX_OBSH2_"]
         for mission in ("SW", "OR", "CH", "CR", "CO"):
             for cadence in ("1M", "4M"):
-                collections_short[f"VOBS_{mission}_{cadence}"] = \
-                    [f"{mission}_OPER_VOBS_{cadence}_2_"]
-                collections_short[f"VOBS_{mission}_{cadence}:SecularVariation"] = \
-                    [f"{mission}_OPER_VOBS_{cadence}_2_:SecularVariation"]
+                collections_short[f"VOBS_{mission}_{cadence}"] = [
+                    f"{mission}_OPER_VOBS_{cadence}_2_"
+                ]
+                collections_short[f"VOBS_{mission}_{cadence}:SecularVariation"] = [
+                    f"{mission}_OPER_VOBS_{cadence}_2_:SecularVariation"
+                ]
 
         def _filter_collections(groupname):
-            """ Reduce the full list to just one group, e.g. "MAG """
+            """Reduce the full list to just one group, e.g. "MAG"""
             if groupname:
                 groups = list(collections_short.keys())
                 if groupname in groups:
-                    return {
-                        groupname:
-                        collections_short[groupname]
-                    }
+                    return {groupname: collections_short[groupname]}
                 else:
                     raise ValueError("Invalid collection group name")
             else:
@@ -899,8 +1151,8 @@ def _filter_collections(groupname):
             for key, val in collections_filtered.items():
                 print(key)
                 for i in val:
-                    print('  ', i)
-                refs = COLLECTION_REFERENCES.get(key, ('No reference...',))
+                    print("  ", i)
+                refs = COLLECTION_REFERENCES.get(key, ("No reference...",))
                 for ref in refs:
                     print(ref)
                 print()
@@ -926,10 +1178,9 @@ def available_measurements(self, collection=None):
         else:
             raise Exception(
                 "collection must be one of {}\nor\n{}".format(
-                    ", ".join(keys),
-                    "\n".join(self._available["collections_to_keys"])
-                    )
+                    ", ".join(keys), "\n".join(self._available["collections_to_keys"])
                 )
+            )
 
     def available_models(self, param=None, details=True, nice_output=True):
         """Show details of avalable models.
@@ -957,7 +1208,7 @@ def available_models(self, param=None, details=True, nice_output=True):
 
         def filter_by_param(d, param):
             if param in ("F", "C", "D"):
-                param = '2' + param
+                param = "2" + param
             return [i for i in d if param in i]
 
         # get all models provided by the server
@@ -965,7 +1216,8 @@ def filter_by_param(d, param):
 
         # keep only models really provided by the server
         d = [
-            model_name for model_name in self._available["models"]
+            model_name
+            for model_name in self._available["models"]
             if model_name in models_info
         ]
 
@@ -977,7 +1229,7 @@ def filter_by_param(d, param):
             d = {
                 model_name: {
                     "description": MODEL_REFERENCES[model_name],
-                    "details": models_info[model_name]
+                    "details": models_info[model_name],
                 }
                 for model_name in d
             }
@@ -999,13 +1251,11 @@ def filter_by_param(d, param):
             return d
 
     def available_auxiliaries(self):
-        """Returns a list of the available auxiliary parameters.
-        """
+        """Returns a list of the available auxiliary parameters."""
         return self._available["auxiliaries"]
 
     def available_observatories(
-        self, collection, start_time=None, end_time=None,
-        details=False, verbose=True
+        self, collection, start_time=None, end_time=None, details=False, verbose=True
     ):
         """Get list of available observatories from server.
 
@@ -1039,26 +1289,23 @@ def available_observatories(
             list or DataFrame: IAGA codes (and start/end times)
 
         """
+
         def _request_get_observatories(collection=None, start_time=None, end_time=None):
-            """ Make the get_observatories request to the server """
+            """Make the get_observatories request to the server"""
             templatefile = TEMPLATE_FILES["get_observatories"]
             template = JINJA2_ENVIRONMENT.get_template(templatefile)
             request = template.render(
                 collection_id=collection,
                 begin_time=start_time,
                 end_time=end_time,
-                response_type="text/csv"
-            ).encode('UTF-8')
-            response = self._get(
-                request, asynchronous=False, show_progress=False
-            )
+                response_type="text/csv",
+            ).encode("UTF-8")
+            response = self._get(request, asynchronous=False, show_progress=False)
             return response
 
         def _csv_to_df(csv_data):
-            """ Convert bytes data to pandas dataframe """
-            return read_csv(
-                StringIO(str(csv_data, 'utf-8'))
-            )
+            """Convert bytes data to pandas dataframe"""
+            return read_csv(StringIO(str(csv_data, "utf-8")))
 
         if collection not in self.OBS_COLLECTIONS:
             raise ValueError(
@@ -1090,10 +1337,12 @@ def _detect_AUX_OBS(self, collections):
         # Output notification for each of aux_type
         for aux_type in ["AUX_OBSH", "AUX_OBSM", "AUX_OBSS"]:
             if aux_type in collection_types_requested:
-                output_text = dedent(f"""
+                output_text = dedent(
+                    f"""
                 Accessing INTERMAGNET and/or WDC data
                 Check usage terms at {DATA_CITATIONS.get(aux_type)}
-                """)
+                """
+                )
                 tqdm.write(output_text)
 
     def set_collection(self, *args, verbose=True):
@@ -1107,25 +1356,29 @@ def set_collection(self, *args, verbose=True):
         collections = [*args]
         for collection in collections:
             if not isinstance(collection, str):
-                raise TypeError(
-                    "{} invalid. Must be string."
-                    .format(collection)
-                    )
+                raise TypeError(f"{collection} invalid. Must be string.")
             if collection not in self._available["collections_to_keys"]:
                 raise ValueError(
                     "Invalid collection: {}. "
-                    "Check available with SwarmRequest().available_collections()"
-                    .format(collection)
+                    "Check available with SwarmRequest().available_collections()".format(
+                        collection
                     )
+                )
         if verbose:
             self._detect_AUX_OBS(collections)
         self._collection_list = collections
         self._request_inputs.set_collections(collections)
         return self
 
-    def set_products(self, measurements=None, models=None, custom_model=None,
-                     auxiliaries=None, residuals=False, sampling_step=None
-                     ):
+    def set_products(
+        self,
+        measurements=None,
+        models=None,
+        custom_model=None,
+        auxiliaries=None,
+        residuals=False,
+        sampling_step=None,
+    ):
         """Set the combination of products to retrieve.
 
         If residuals=True then just get the measurement-model residuals,
@@ -1170,7 +1423,8 @@ def set_products(self, measurements=None, models=None, custom_model=None,
                     "Check available with "
                     "SwarmRequest.available_measurements({})".format(
                         variable, collection_key, collection_key
-                    ))
+                    )
+                )
         # Check if at least one model defined when requesting residuals
         if residuals and not models:
             raise Exception("Residuals requested but no model defined!")
@@ -1182,7 +1436,7 @@ def set_products(self, measurements=None, models=None, custom_model=None,
                 raise Exception(
                     "'{}' not available. Check available with "
                     "SwarmRequest.available_auxiliaries()".format(variable)
-                    )
+                )
         # Load the custom .shc file
         if custom_model:
             if os.path.exists(custom_model):
@@ -1201,8 +1455,7 @@ def set_products(self, measurements=None, models=None, custom_model=None,
 
         # model-related measurements
         _requested_model_variables = [
-            variable for variable in measurements
-            if variable in model_variables
+            variable for variable in measurements if variable in model_variables
         ]
 
         if residuals:
@@ -1224,8 +1477,7 @@ def set_products(self, measurements=None, models=None, custom_model=None,
             variables.update(
                 f"{variable}_{model_id}"
                 for variable in (
-                    model_variables[variable]
-                    for variable in _requested_model_variables
+                    model_variables[variable] for variable in _requested_model_variables
                 )
                 for model_id in model_ids
             )
@@ -1256,12 +1508,12 @@ def set_range_filter(self, parameter=None, minimum=None, maximum=None):
         if not isinstance(parameter, str):
             raise TypeError("parameter must be a str")
         # Update the list that contains the separate filters
-        self._filterlist += [parameter+":"+str(minimum)+","+str(maximum)]
+        self._filterlist += [parameter + ":" + str(minimum) + "," + str(maximum)]
         # Convert the list into the string that gets passed to the xml template
         if len(self._filterlist) == 1:
             filters = self._filterlist[0]
         else:
-            filters = ';'.join(self._filterlist)
+            filters = ";".join(self._filterlist)
         # Update the SwarmWPSInputs object
         self._request_inputs.filters = filters
         return self
@@ -1272,7 +1524,9 @@ def clear_range_filter(self):
         self._request_inputs.filters = None
         return self
 
-    def get_times_for_orbits(self, start_orbit, end_orbit, mission="Swarm", spacecraft=None):
+    def get_times_for_orbits(
+        self, start_orbit, end_orbit, mission="Swarm", spacecraft=None
+    ):
         """Translate a pair of orbit numbers to a time interval.
 
         Args:
@@ -1293,9 +1547,9 @@ def get_times_for_orbits(self, start_orbit, end_orbit, mission="Swarm", spacecra
         """
         # check old function signature and print warning
         if (
-            isinstance(start_orbit, str) and
-            isinstance(mission, int) and
-            spacecraft is None
+            isinstance(start_orbit, str)
+            and isinstance(mission, int)
+            and spacecraft is None
         ):
             spacecraft, start_orbit, end_orbit = start_orbit, end_orbit, mission
             mission = "Swarm"
@@ -1322,13 +1576,12 @@ def get_times_for_orbits(self, start_orbit, end_orbit, mission="Swarm", spacecra
             mission=mission,
             spacecraft=spacecraft,
             start_orbit=start_orbit,
-            end_orbit=end_orbit
-        ).encode('UTF-8')
-        response = self._get(
-            request, asynchronous=False, show_progress=False)
-        responsedict = json.loads(response.decode('UTF-8'))
-        start_time = parse_datetime(responsedict['start_time'])
-        end_time = parse_datetime(responsedict['end_time'])
+            end_orbit=end_orbit,
+        ).encode("UTF-8")
+        response = self._get(request, asynchronous=False, show_progress=False)
+        responsedict = json.loads(response.decode("UTF-8"))
+        start_time = parse_datetime(responsedict["start_time"])
+        end_time = parse_datetime(responsedict["end_time"])
         return start_time, end_time
 
     def _fix_spacecraft(self, mission, spacecraft):
@@ -1358,13 +1611,12 @@ def _check_mission_spacecraft(self, mission, spacecraft):
                     f"Allowed options are: {','.join(self.MISSION_SPACECRAFTS[mission])}"
                 )
 
-        elif spacecraft: # mission without spacecraft id
+        elif spacecraft:  # mission without spacecraft id
             raise ValueError(
                 f"No {mission} spacecraft shall be specified! "
                 "Set spacecraft to None."
             )
 
-
     def get_orbit_number(self, spacecraft, input_time, mission="Swarm"):
         """Translate a time to an orbit number.
 
@@ -1385,8 +1637,7 @@ def get_orbit_number(self, spacecraft, input_time, mission="Swarm"):
             input_time = parse_datetime(input_time)
         except TypeError:
             raise TypeError(
-                "input_time must be datetime object or ISO-8601 "
-                "date/time string"
+                "input_time must be datetime object or ISO-8601 " "date/time string"
             )
         # Change to spacecraft = "A" etc. for this request
         if spacecraft in ("Alpha", "Bravo", "Charlie"):
@@ -1397,33 +1648,34 @@ def get_orbit_number(self, spacecraft, input_time, mission="Swarm"):
                 f"Allowed options are: {','.join(self.MISSION_SPACECRAFTS)}"
             )
         spacecraft = str(spacecraft)
-        if mission=="Swarm":
+        if mission == "Swarm":
             collection = f"SW_OPER_MOD{spacecraft}_SC_1B"
-        elif mission=="GRACE":
+        elif mission == "GRACE":
             if spacecraft in "12":
-                spacecraft = "AB"[int(spacecraft)-1]
+                spacecraft = "AB"[int(spacecraft) - 1]
             elif spacecraft not in "AB":
                 raise ValueError(f"Invalid spacecraft: {spacecraft}")
             collection = f"GRACE_{spacecraft}_MAG"
-        elif mission=="GRACE-FO":
+        elif mission == "GRACE-FO":
             collection = f"GF{spacecraft}_OPER_FGM_ACAL_CORR"
-        elif mission=="CryoSat-2":
+        elif mission == "CryoSat-2":
             collection = "CS_OPER_MAG"
         request_inputs = SwarmWPSInputs(
             collection_ids={collection: [collection]},
             begin_time=input_time,
-            end_time=input_time+datetime.timedelta(seconds=1),
+            end_time=input_time + datetime.timedelta(seconds=1),
             variables=["OrbitNumber"],
-            response_type="text/csv"
+            response_type="text/csv",
         )
-        request = request_inputs.as_xml(self._templatefiles['sync'])
+        request = request_inputs.as_xml(self._templatefiles["sync"])
         retdata = ReturnedDataFile(filetype="csv")
-        response_handler = self._response_handler(
-            retdata, show_progress=False
-        )
+        response_handler = self._response_handler(retdata, show_progress=False)
         self._get(
-            request, asynchronous=False, response_handler=response_handler,
-            show_progress=False)
+            request,
+            asynchronous=False,
+            response_handler=response_handler,
+            show_progress=False,
+        )
         df = retdata.as_dataframe()
         if len(df) == 0:
             raise ValueError(
@@ -1434,8 +1686,7 @@ def get_orbit_number(self, spacecraft, input_time, mission="Swarm"):
         else:
             return df["OrbitNumber"][0]
 
-    def get_model_info(self, models=None, custom_model=None,
-                       original_response=False):
+    def get_model_info(self, models=None, custom_model=None, original_response=False):
         """Get model info from server.
 
         Handles the same models input as .set_products(), and returns a dict
@@ -1464,25 +1715,21 @@ def get_model_info(self, models=None, custom_model=None,
         """
 
         def _request_get_model_info(model_expression=None, custom_shc=None):
-            """ Make the get_model_info request. """
+            """Make the get_model_info request."""
             templatefile = TEMPLATE_FILES["model_info"]
             template = JINJA2_ENVIRONMENT.get_template(templatefile)
             request = template.render(
                 model_expression=model_expression,
                 custom_shc=custom_shc,
-                response_type="application/json"
-            ).encode('UTF-8')
-            response = self._get(
-                request, asynchronous=False, show_progress=False)
-            response_list = json.loads(response.decode('UTF-8'))
+                response_type="application/json",
+            ).encode("UTF-8")
+            response = self._get(request, asynchronous=False, show_progress=False)
+            response_list = json.loads(response.decode("UTF-8"))
             return response_list
 
         def _build_dict(response_list):
-            """ Build dictionary output organised by model name. """
-            return {
-                model_dict.pop("name"): model_dict
-                for model_dict in response_list
-            }
+            """Build dictionary output organised by model name."""
+            return {model_dict.pop("name"): model_dict for model_dict in response_list}
 
         if custom_model:
             with open(custom_model) as custom_shc_file:
@@ -1506,7 +1753,7 @@ def _build_dict(response_list):
 
     @staticmethod
     def _check_deprecated_models(models):
-        """ Print deprecation warning for deprecated models. """
+        """Print deprecation warning for deprecated models."""
         deprecated_models = []
         for deprecated_model in DEPRECATED_MODELS:
             for model in models:
@@ -1514,15 +1761,24 @@ def _check_deprecated_models(models):
                     deprecated_models.append(deprecated_model)
                     break
         for deprecated_model in deprecated_models:
-            print("WARNING: Model {} is deprecated. {}".format(
-                deprecated_model, DEPRECATED_MODELS[deprecated_model]
-            ), file=sys.stdout)
-
+            print(
+                "WARNING: Model {} is deprecated. {}".format(
+                    deprecated_model, DEPRECATED_MODELS[deprecated_model]
+                ),
+                file=sys.stdout,
+            )
 
-    def get_conjunctions(self, start_time=None, end_time=None, threshold=1.0,
-                         spacecraft1='A', spacecraft2='B',
-                         mission1='Swarm', mission2='Swarm'):
-        """ Get times of the spacecraft conjunctions. Currently available for
+    def get_conjunctions(
+        self,
+        start_time=None,
+        end_time=None,
+        threshold=1.0,
+        spacecraft1="A",
+        spacecraft2="B",
+        mission1="Swarm",
+        mission2="Swarm",
+    ):
+        """Get times of the spacecraft conjunctions. Currently available for
         the following spacecraft pairs:
           - Swarm-A/Swarm-B
 
@@ -1558,13 +1814,11 @@ def get_conjunctions(self, start_time=None, end_time=None, threshold=1.0,
         self._check_mission_spacecraft(mission2, spacecraft2)
 
         if (mission1, spacecraft1) == (mission2, spacecraft2):
-            raise ValueError(
-                "The first and second spacecraft must not be the same!"
-            )
+            raise ValueError("The first and second spacecraft must not be the same!")
 
-        spacecraft_pair = tuple(sorted([
-            (mission1, spacecraft1), (mission2, spacecraft2)
-        ]))
+        spacecraft_pair = tuple(
+            sorted([(mission1, spacecraft1), (mission2, spacecraft2)])
+        )
 
         if spacecraft_pair not in self.CONJUNCTION_MISISON_SPACECRAFT_PAIRS:
             raise ValueError(
@@ -1582,7 +1836,7 @@ def get_conjunctions(self, start_time=None, end_time=None, threshold=1.0,
             mission1=mission1,
             mission2=mission2,
             threshold=threshold,
-        ).encode('UTF-8')
+        ).encode("UTF-8")
 
         show_progress = False
         leave_progress_bar = False
diff --git a/viresclient/_config.py b/src/viresclient/_config.py
similarity index 76%
rename from viresclient/_config.py
rename to src/viresclient/_config.py
index c2da8cb..0aaba1c 100644
--- a/viresclient/_config.py
+++ b/src/viresclient/_config.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # Configuration file handling.
 #
 # Authors: Ashley Smith <ashley.smith@ed.ac.uk>
 #          Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,21 +25,23 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
-import os
 import json
+import os
+from configparser import ConfigParser
+from getpass import getpass
 from io import StringIO
 from os.path import expanduser, join
-from getpass import getpass
-from configparser import ConfigParser
+
 from ._api.token import TokenManager
 
 # Identify whether code is running in Jupyter notebook or not
 try:
     from IPython import get_ipython
     from IPython.display import display_html
-    IN_JUPYTER = 'zmqshell' in str(type(get_ipython()))
+
+    IN_JUPYTER = "zmqshell" in str(type(get_ipython()))
     from IPython.core.error import StdinNotImplementedError
 except ImportError:
     IN_JUPYTER = False
@@ -53,17 +55,17 @@
 
 
 def _get_ows_url(url):
-    """ https//foo.bar(/ows)? -> https//foo.bar/ows """
+    """https//foo.bar(/ows)? -> https//foo.bar/ows"""
     return TokenManager.RE_URL_BASE.sub(DATA_API_PATH, url, count=1)
 
 
 def _get_token_gui_url(url):
-    """ https//foo.bar(/ows)? -> https//foo.bar/accounts/tokens/ """
+    """https//foo.bar(/ows)? -> https//foo.bar/accounts/tokens/"""
     return TokenManager.RE_URL_BASE.sub(TOKEN_GUI_PATH, url, count=1)
 
 
 def set_token(url="https://vires.services/ows", token=None, set_default=False):
-    """ Set the access token for a given URL, using user input.
+    """Set the access token for a given URL, using user input.
 
     Get an access token at https://vires.services/accounts/tokens/
 
@@ -91,28 +93,31 @@ def set_token(url="https://vires.services/ows", token=None, set_default=False):
         # Provide user with information on token setting URL
         # Nicer output in IPython, with a clickable link
         if IN_JUPYTER:
+
             def _linkify(_url):
                 if _url:
                     return '<a href="{url}">{url}</a>'.format(url=_url)
-                return '(link not found)'
+                return "(link not found)"
+
             display_html(
-                'Setting access token for {}...<br>'.format(url)
-                + 'Generate a token at {}'.format(_linkify(url4token)),
-                raw=True)
+                f"Setting access token for {url}...<br>"
+                + f"Generate a token at {_linkify(url4token)}",
+                raw=True,
+            )
         else:
-            print('Setting access token for', url, ' ...')
-            url4token = url4token if url4token else '(link not found)'
-            print('Generate a token at', url4token)
+            print("Setting access token for", url, " ...")
+            url4token = url4token if url4token else "(link not found)"
+            print("Generate a token at", url4token)
         # Prompt user to supply token, if input is allowed
         try:
-            token = getpass('Enter token:')
+            token = getpass("Enter token:")
         except EOFError:
             # getpass is meant to do something like this as a fallback
             # but it fails with EOFError in Jupyter (rare glitch)
             # https://github.com/ESA-VirES/VirES-Python-Client/issues/46
-            token = input('Enter token:')
+            token = input("Enter token:")
         except StdinNotImplementedError:
-            print('No input method available. Unable to set token.')
+            print("No input method available. Unable to set token.")
             return
     config = ClientConfig()
     config.set_site_config(url, token=token)
@@ -123,8 +128,8 @@ def _linkify(_url):
     print("Token saved for", url)
 
 
-class ClientConfig():
-    """ Client configuration.
+class ClientConfig:
+    """Client configuration.
 
     Example usage::
 
@@ -150,34 +155,34 @@ def __init__(self, path=None):
 
     @property
     def path(self):
-        """ Get path of the configuration file. """
+        """Get path of the configuration file."""
         return self._path
 
     @property
     def default_url(self):
-        """ Get default URL or None if not set. """
-        return self._get_section('default').get('url')
+        """Get default URL or None if not set."""
+        return self._get_section("default").get("url")
 
     @default_url.setter
     def default_url(self, value):
-        """ Set default URL. """
-        self._update_section('default', url=value)
+        """Set default URL."""
+        self._update_section("default", url=value)
 
     @default_url.deleter
     def default_url(self):
-        """ Unset the default URL. """
-        self._update_section('default', url=None)
+        """Unset the default URL."""
+        self._update_section("default", url=None)
 
     def set_site_config(self, url, **options):
-        """ Set configuration for the given URL. """
+        """Set configuration for the given URL."""
         self._set_section(url, **options)
 
     def get_site_config(self, url):
-        """ Get configuration for the given URL. """
+        """Get configuration for the given URL."""
         return dict(self._get_section(url))
 
     def _update_section(self, section, **options):
-        """ Update configuration file section. """
+        """Update configuration file section."""
         all_options = dict(self._get_section(section))
         all_options.update(options)
         self._set_section(section, **all_options)
@@ -189,56 +194,52 @@ def _get_section(self, section):
             return {}
 
     def _set_section(self, section, **options):
-        """ Set configuration file section. """
-        options = {
-            key: value
-            for key, value in options.items() if value is not None
-        }
+        """Set configuration file section."""
+        options = {key: value for key, value in options.items() if value is not None}
         if options:
             self._config[section] = options
         else:
             self._delete_section(section)
 
     def _delete_section(self, section):
-        """ Delete configuration file section. """
+        """Delete configuration file section."""
         try:
             del self._config[section]
         except KeyError:
             pass
 
     def save(self):
-        """ Save the configuration file. """
-        with open(self._path, 'w') as file_:
+        """Save the configuration file."""
+        with open(self._path, "w") as file_:
             self._config.write(file_)
             # make the saved file private
-            if os.name == 'posix':
+            if os.name == "posix":
                 os.chmod(file_.fileno(), 0o0600)
 
     def __str__(self):
-        """ Dump configuration to a string. """
+        """Dump configuration to a string."""
         fobj = StringIO()
         self._config.write(fobj)
         return fobj.getvalue()
 
-
     def init(self, env_var_name="VIRES_ACCESS_CONFIG"):
-        """ Initialize client configuration. """
+        """Initialize client configuration."""
         env_config = _parse_env_config(_get_env_config(env_var_name))
 
         if not self.default_url:
-            url = _get_ows_url(env_config['default_server'])
+            url = _get_ows_url(env_config["default_server"])
             print("Setting default URL to %s ..." % url)
             self.default_url = url
 
         # retrieve and server tokens
-        for server_url, token_dict in env_config['servers'].items():
+        for server_url, token_dict in env_config["servers"].items():
             url = _get_ows_url(server_url)
             if self.get_site_config(url):
                 continue
             print("Creating new access token for %s ..." % url)
             try:
                 token = _retrieve_access_token(
-                    server_url, token_dict['token'], env_config["instance_name"]
+                    server_url, token_dict["token"], env_config["instance_name"]
                 )
             except TokenManager.Error as error:
                 print("ERROR: Failed to create a new access token! Reason: %s" % error)
@@ -247,7 +248,7 @@ def init(self, env_var_name="VIRES_ACCESS_CONFIG"):
 
 
 def _retrieve_access_token(url, token, purpose):
-    return TokenManager(url, token).post(purpose=purpose)['token']
+    return TokenManager(url, token).post(purpose=purpose)["token"]
 
 
 def _get_env_config(env_var_name):
@@ -263,7 +264,6 @@ def _get_env_config(env_var_name):
     return {}
 
 
-
 def _parse_env_config(env_config):
     return {
         "instance_name": env_config.get("instance_name") or DEFAULT_SERVER,
diff --git a/src/viresclient/_data/__init__.py b/src/viresclient/_data/__init__.py
new file mode 100644
index 0000000..8d9cbaa
--- /dev/null
+++ b/src/viresclient/_data/__init__.py
@@ -0,0 +1,10 @@
+import json
+from os.path import dirname, join
+
+_DIRNAME = dirname(__file__)
+
+with open(join(_DIRNAME, "config_swarm.json"), encoding="utf-8") as f:
+    CONFIG_SWARM = json.load(f)
+
+with open(join(_DIRNAME, "config_aeolus.json"), encoding="utf-8") as f:
+    CONFIG_AEOLUS = json.load(f)
diff --git a/viresclient/_data/config_aeolus.json b/src/viresclient/_data/config_aeolus.json
similarity index 99%
rename from viresclient/_data/config_aeolus.json
rename to src/viresclient/_data/config_aeolus.json
index da8f093..2ec7287 100644
--- a/viresclient/_data/config_aeolus.json
+++ b/src/viresclient/_data/config_aeolus.json
@@ -833,4 +833,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/viresclient/_data/config_swarm.json b/src/viresclient/_data/config_swarm.json
similarity index 100%
rename from viresclient/_data/config_swarm.json
rename to src/viresclient/_data/config_swarm.json
diff --git a/viresclient/_data_handling.py b/src/viresclient/_data_handling.py
similarity index 79%
rename from viresclient/_data_handling.py
rename to src/viresclient/_data_handling.py
index d40f03f..d9a33b7 100644
--- a/viresclient/_data_handling.py
+++ b/src/viresclient/_data_handling.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # Handles the WPS requests to the VirES server
 #
 # Authors: Ashley Smith <ashley.smith@ed.ac.uk>
 #          Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,22 +25,24 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
+import json
 import os
-import tempfile
 import shutil
+import tempfile
+
+import cdflib
+import netCDF4
 import numpy
 import pandas
-import json
 import xarray
-import cdflib
-import netCDF4
+
 from ._wps import time_util
+
 if os.name == "nt":
     import atexit
 
-from ._data import CONFIG_SWARM
 from ._data import CONFIG_AEOLUS
 
 CDF_EPOCH_1970 = 62167219200000.0
@@ -79,9 +81,8 @@
 }
 
 
-class FileReader(object):
-    """Provides access to file contents (wrapper around cdflib)
-    """
+class FileReader:
+    """Provides access to file contents (wrapper around cdflib)"""
 
     def __init__(self, file, filetype="cdf"):
         """
@@ -93,18 +94,17 @@ def __init__(self, file, filetype="cdf"):
             self._cdf = self._open_cdf(file)
             globalatts = self._cdf.globalattsget()
             self.sources = self._ensure_list(
-                globalatts.get('ORIGINAL_PRODUCT_NAMES', []))
+                globalatts.get("ORIGINAL_PRODUCT_NAMES", [])
+            )
             self.magnetic_models = self._ensure_list(
-                globalatts.get('MAGNETIC_MODELS', []))
-            self.range_filters = self._ensure_list(
-                globalatts.get('DATA_FILTERS', []))
-            self.variables = self._cdf.cdf_info()['zVariables']
-            self._varatts = {var: self._cdf.varattsget(var)
-                             for var in self.variables}
-            self._varinfo = {var: self._cdf.varinq(var)
-                             for var in self.variables}
+                globalatts.get("MAGNETIC_MODELS", [])
+            )
+            self.range_filters = self._ensure_list(globalatts.get("DATA_FILTERS", []))
+            self.variables = self._cdf.cdf_info()["zVariables"]
+            self._varatts = {var: self._cdf.varattsget(var) for var in self.variables}
+            self._varinfo = {var: self._cdf.varinq(var) for var in self.variables}
         else:
-            raise NotImplementedError("{} not supported".format(filetype))
+            raise NotImplementedError(f"{filetype} not supported")
 
     def __enter__(self):
         return self
@@ -155,10 +155,7 @@ def get_variable_dimsizes(self, var):
     @staticmethod
     def _cdftime_to_datetime(t):
         try:
-            return pandas.to_datetime(
-                (t - CDF_EPOCH_1970)/1e3,
-                unit='s'
-            )
+            return pandas.to_datetime((t - CDF_EPOCH_1970) / 1e3, unit="s")
         except TypeError:
             return []
 
@@ -169,9 +166,11 @@ def as_pandas_dataframe(self, expand=False):
         columns.remove("Timestamp")
         # Split columns according to those to be expanded into multiple columns
         if expand:
-            columns_to_expand = set(c for c in columns
-                                    if c in DATANAMES_TO_FRAME_NAMES.keys()
-                                    or "B_NEC" in c)
+            columns_to_expand = {
+                c
+                for c in columns
+                if c in DATANAMES_TO_FRAME_NAMES.keys() or "B_NEC" in c
+            }
             # Avoid conflict with 2D AOB_FAC Quality variable
             # when accessing AUX_OBS Quality
             if any(["AUX_OBS" in s for s in self.sources]):
@@ -203,9 +202,9 @@ def as_pandas_dataframe(self, expand=False):
             framename = DATANAMES_TO_FRAME_NAMES.get(column, "NEC")
             suffixes = FRAME_LABELS[framename]
             if len(vector_data.shape) > 2:
-                raise NotImplementedError("{}".format(column))
+                raise NotImplementedError(f"{column}")
             if vector_data.shape[1] != len(suffixes):
-                raise NotImplementedError("{}".format(column))
+                raise NotImplementedError(f"{column}")
             for i, suffix in enumerate(suffixes):
                 df[column + "_" + str(suffix)] = vector_data[:, i]
         return df
@@ -216,12 +215,18 @@ def as_xarray_dataset(self, reshape=False):
         #  (this is done in ReturnedData)
         # Initialise dataset with time coordinate
         ds = xarray.Dataset(
-            coords={"Timestamp":
-                    self._cdftime_to_datetime(self.get_variable("Timestamp"))})
+            coords={
+                "Timestamp": self._cdftime_to_datetime(self.get_variable("Timestamp"))
+            }
+        )
         # Add Spacecraft variable as Categorical to save memory
         if "Spacecraft" in self.variables:
-            ds["Spacecraft"] = (("Timestamp",), pandas.Categorical(
-                self.get_variable("Spacecraft"), categories=ALLOWED_SPACECRFTS))
+            ds["Spacecraft"] = (
+                ("Timestamp",),
+                pandas.Categorical(
+                    self.get_variable("Spacecraft"), categories=ALLOWED_SPACECRFTS
+                ),
+            )
         datanames = set(self.variables) - {"Timestamp", "Spacecraft"}
         # Loop through each variable available and append them to the Dataset,
         #  attaching the Timestamp coordinate to each.
@@ -244,17 +249,17 @@ def as_xarray_dataset(self, reshape=False):
                     dims_used.add(dimname)
                 else:
                     dimname = "%s_dim1" % dataname
-                ds[dataname] = (("Timestamp", dimname),
-                                self.get_variable(dataname))
+                ds[dataname] = (("Timestamp", dimname), self.get_variable(dataname))
             # 3D case (matrix series), e.g. QDBasis
             elif numdims == 2:
                 dimname1 = "%s_dim1" % dataname
                 dimname2 = "%s_dim2" % dataname
-                ds[dataname] = (("Timestamp", dimname1, dimname2),
-                                self.get_variable(dataname))
+                ds[dataname] = (
+                    ("Timestamp", dimname1, dimname2),
+                    self.get_variable(dataname),
+                )
             else:
-                raise NotImplementedError("%s: array too complicated" %
-                                          dataname)
+                raise NotImplementedError("%s: array too complicated" % dataname)
         # Add named coordinates
         for dimname, dimlabels in FRAME_LABELS.items():
             if dimname in dims_used:
@@ -306,10 +311,12 @@ def reshape_dataset(ds):
             pos_vars = ["Longitude", "Latitude", "Radius", codevar]
             _ds_locs = next(iter(ds[pos_vars].groupby("Timestamp")))[1]
             if len(sites) > 1:
-                _ds_locs = _ds_locs.drop(("Timestamp")).rename({"Timestamp": "Site"})
+                _ds_locs = _ds_locs.drop("Timestamp").rename({"Timestamp": "Site"})
             else:
-                _ds_locs = _ds_locs.drop(("Timestamp")).expand_dims("Site")
-            _ds_locs["Site"] = [sites_inv.get(code) for code in _ds_locs[codevar].values]
+                _ds_locs = _ds_locs.drop("Timestamp").expand_dims("Site")
+            _ds_locs["Site"] = [
+                sites_inv.get(code) for code in _ds_locs[codevar].values
+            ]
             _ds_locs = _ds_locs.sortby("Site")
         # Create dataset initialised with the (V)OBS positional info as coords
         # and datavars (empty) reshaped to (Site, Timestamp, ...)
@@ -321,11 +328,17 @@ def reshape_dataset(ds):
                 "Latitude": ("Site", _ds_locs["Latitude"].data),
                 "Longitude": ("Site", _ds_locs["Longitude"].data),
                 "Radius": ("Site", _ds_locs["Radius"].data),
-                "NEC": ["N", "E", "C"]
+                "NEC": ["N", "E", "C"],
             },
         )
         # (Dropping unused Spacecraft var)
-        data_vars = set(ds.data_vars) - {"Latitude", "Longitude", "Radius", codevar, "Spacecraft"}
+        data_vars = set(ds.data_vars) - {
+            "Latitude",
+            "Longitude",
+            "Radius",
+            codevar,
+            "Spacecraft",
+        }
         N_sites = len(_ds_locs[codevar])
         # Create empty data variables to be infilled
         for var in data_vars:
@@ -361,24 +374,22 @@ def make_pandas_DataFrame_from_csv(csv_filename):
     except Exception:
         raise Exception("Bad or empty csv.")
     # Convert to datetime objects
-    df['Timestamp'] = df['Timestamp'].apply(
-        time_util.parse_datetime)
+    df["Timestamp"] = df["Timestamp"].apply(time_util.parse_datetime)
     # Convert the columns of vectors from strings to lists
     # Returns empty dataframe when retrieval from server is empty
     if len(df) != 0:
         # Convert the columns of vectors from strings to lists
         for col in df:
             if type(df[col][0]) is str:
-                if df[col][0][0] == '{':
+                if df[col][0][0] == "{":
                     df[col] = df[col].apply(
-                        lambda x: [
-                            float(y) for y in x.strip('{}').split(';')
-                        ])
-    df.set_index('Timestamp', inplace=True)
+                        lambda x: [float(y) for y in x.strip("{}").split(";")]
+                    )
+    df.set_index("Timestamp", inplace=True)
     return df
 
 
-class ReturnedDataFile(object):
+class ReturnedDataFile:
     """For handling individual files returned from the server.
 
     Holds the data returned from the server and the data type.
@@ -390,35 +401,34 @@ class ReturnedDataFile(object):
 
     def __init__(self, filetype=None, tmpdir=None):
         self._supported_filetypes = ("csv", "cdf", "nc")
-        self.filetype = str() if filetype is None else filetype
+        self.filetype = "" if filetype is None else filetype
         if tmpdir is not None:
             if not os.path.exists(tmpdir):
                 raise Exception("tmpdir does not exist")
         if os.name == "nt":
             self._file = tempfile.NamedTemporaryFile(
-                prefix="vires_", dir=tmpdir, delete=False)
+                prefix="vires_", dir=tmpdir, delete=False
+            )
             self._file.close()
             atexit.register(os.remove, self._file.name)
         else:
-            self._file = tempfile.NamedTemporaryFile(
-                prefix="vires_", dir=tmpdir)
+            self._file = tempfile.NamedTemporaryFile(prefix="vires_", dir=tmpdir)
 
     def __str__(self):
-        return "viresclient ReturnedDataFile object of type " + \
-               self.filetype + \
-               "\nSave it to a file with .to_file('filename')" + \
-               "\nLoad it as a pandas dataframe with .as_dataframe()" + \
-               "\nLoad it as an xarray dataset with .as_xarray()"
+        return (
+            "viresclient ReturnedDataFile object of type "
+            + self.filetype
+            + "\nSave it to a file with .to_file('filename')"
+            + "\nLoad it as a pandas dataframe with .as_dataframe()"
+            + "\nLoad it as an xarray dataset with .as_xarray()"
+        )
 
     def open_cdf(self):
-        """Returns the opened file as cdflib.CDF
-        """
+        """Returns the opened file as cdflib.CDF"""
         return FileReader._open_cdf(self._file.name)
 
     def _write_new_data(self, data):
-        """Replace the tempfile contents with 'data' (bytes)
-
-        """
+        """Replace the tempfile contents with 'data' (bytes)"""
         if not isinstance(data, bytes):
             raise TypeError("data must be of type bytes")
         # If on Windows, the file will be closed so needs to be re-opened:
@@ -426,17 +436,14 @@ def _write_new_data(self, data):
             temp_file.write(data)
 
     def _write_file(self, filename):
-        """Write the tempfile out to a regular file
-
-        """
+        """Write the tempfile out to a regular file"""
         with open(self._file.name, "rb") as temp_file:
-            with open(filename, 'wb') as out_file:
+            with open(filename, "wb") as out_file:
                 shutil.copyfileobj(temp_file, out_file)
 
     @property
     def filetype(self):
-        """Filetype is one of ("csv", "cdf", "nc")
-        """
+        """Filetype is one of ("csv", "cdf", "nc")"""
         return self._filetype
 
     @filetype.setter
@@ -445,26 +452,20 @@ def filetype(self, value):
             raise TypeError("filetype must be a string")
         value = value.lower()
         if value not in self._supported_filetypes:
-            raise TypeError("Chosen filetype must be one of: {}".format(
-                            self._supported_filetypes
-                            ))
+            raise TypeError(
+                f"Chosen filetype must be one of: {self._supported_filetypes}"
+            )
         self._filetype = value
 
     @staticmethod
     def _check_outfile(path, path_extension, overwrite=False):
-        """Check validity of path and extension, and if it exists already
-
-        """
+        """Check validity of path and extension, and if it exists already"""
         if not isinstance(path, str):
             raise TypeError("path must be a string")
-        if path.split('.')[-1].lower() != path_extension:
-            raise TypeError("Filename extension should be {}".format(
-                path_extension
-                ))
+        if path.split(".")[-1].lower() != path_extension:
+            raise TypeError(f"Filename extension should be {path_extension}")
         if os.path.isfile(path) and not overwrite:
-            raise Exception(
-                "File not written as it already exists and overwrite=False"
-                )
+            raise Exception("File not written as it already exists and overwrite=False")
 
     def to_file(self, path, overwrite=False):
         """Saves the data to the specified file.
@@ -487,7 +488,7 @@ def to_netcdf(self, path, overwrite=False):
         Extension should be .nc
 
         """
-        self._check_outfile(path, 'nc', overwrite)
+        self._check_outfile(path, "nc", overwrite)
         # Convert to xarray Dataset
         ds = self.as_xarray()
         ds.to_netcdf(path)
@@ -500,13 +501,13 @@ def as_dataframe(self, expand=False):
             pandas.DataFrame
 
         """
-        if self.filetype == 'csv':
+        if self.filetype == "csv":
             if expand:
                 raise NotImplementedError
             df = make_pandas_DataFrame_from_csv(self._file.name)
-        elif self.filetype == 'nc':
+        elif self.filetype == "nc":
             df = self.as_xarray().to_dataframe()
-        elif self.filetype == 'cdf':
+        elif self.filetype == "cdf":
             with FileReader(self._file) as f:
                 df = f.as_pandas_dataframe(expand=expand)
         return df
@@ -523,12 +524,12 @@ def as_xarray(self, group=None, reshape=False):
             xarray.Dataset
 
         """
-        if self.filetype == 'csv':
+        if self.filetype == "csv":
             raise NotImplementedError("csv to xarray is not supported")
-        elif self.filetype == 'cdf':
+        elif self.filetype == "cdf":
             with FileReader(self._file) as f:
                 ds = f.as_xarray_dataset(reshape=reshape)
-        elif self.filetype == 'nc':
+        elif self.filetype == "nc":
             # xarrays open_dataset does not retrieve data in groups
             # group needs to be specified while opening
             # we iterate here over the available groups
@@ -539,21 +540,21 @@ def as_xarray(self, group=None, reshape=False):
             # some datasets do not have groups
             if nc.groups:
                 for group in nc.groups:
-                    ds = ds.merge(xarray.open_dataset(
-                        self._file.name, group=group, engine='netcdf4'
-                    ))
+                    ds = ds.merge(
+                        xarray.open_dataset(
+                            self._file.name, group=group, engine="netcdf4"
+                        )
+                    )
             else:
-                ds = xarray.open_dataset(
-                    self._file.name, engine='netcdf4'
-                )
+                ds = xarray.open_dataset(self._file.name, engine="netcdf4")
             # Go through Aeolus parameters and check if unit information is available
             # TODO: We are "flattening" the list of parameters, same parameter
             # id in different collection types could select incorrect one
             for parameter in ds:
                 for coll_obj in CONFIG_AEOLUS["collections"].values():
                     for field_type in coll_obj.values():
-                        if parameter in field_type and field_type[parameter]['uom']:
-                            ds[parameter].attrs["units"] = field_type[parameter]['uom']
+                        if parameter in field_type and field_type[parameter]["uom"]:
+                            ds[parameter].attrs["units"] = field_type[parameter]["uom"]
             # TODO: Go through Swarm parameters
         return ds
 
@@ -567,31 +568,38 @@ def as_xarray_dict(self):
             dict of xarray.Dataset
 
         """
-        if self.filetype == 'csv':
+        if self.filetype == "csv":
             raise NotImplementedError("csv to xarray dict is not supported")
-        elif self.filetype == 'cdf':
+        elif self.filetype == "cdf":
             raise NotImplementedError("cdf to xarray dict is not supported")
-        elif self.filetype == 'nc':
+        elif self.filetype == "nc":
             result_dict = {}
             nc = netCDF4.Dataset(self._file.name)
             # some datasets do not have groups
             if nc.groups:
                 for group in nc.groups:
                     ds = xarray.Dataset()
-                    ds = ds.merge(xarray.open_dataset(
-                        self._file.name, group=group, engine='netcdf4'
-                    ))
+                    ds = ds.merge(
+                        xarray.open_dataset(
+                            self._file.name, group=group, engine="netcdf4"
+                        )
+                    )
                     for parameter in ds:
                         for coll_obj in CONFIG_AEOLUS["collections"].values():
                             for field_type in coll_obj.values():
-                                if parameter in field_type and field_type[parameter]['uom']:
-                                    ds[parameter].attrs["units"] = field_type[parameter]['uom']
+                                if (
+                                    parameter in field_type
+                                    and field_type[parameter]["uom"]
+                                ):
+                                    ds[parameter].attrs["units"] = field_type[
+                                        parameter
+                                    ]["uom"]
                     result_dict[group] = ds
             else:
-                result_dict['group'] = xarray.open_dataset(
-                    self._file.name, engine='netcdf4'
+                result_dict["group"] = xarray.open_dataset(
+                    self._file.name, engine="netcdf4"
                 )
-            
+
         return result_dict
 
     @property
@@ -599,11 +607,14 @@ def sources(self):
         if self.filetype == "nc":
             nc = netCDF4.Dataset(self._file.name)
             json_hist = json.loads(nc.history)
-            sources = [elem for elem in zip(
-                json_hist["inputFiles"],
-                json_hist["baselines"],
-                json_hist["software_vers"],
-            )]
+            sources = [
+                elem
+                for elem in zip(
+                    json_hist["inputFiles"],
+                    json_hist["baselines"],
+                    json_hist["software_vers"],
+                )
+            ]
         else:
             with FileReader(self._file) as f:
                 sources = f.sources
@@ -622,7 +633,7 @@ def range_filters(self):
         return range_filters
 
 
-class ReturnedData(object):
+class ReturnedData:
     """Flexible object for working with data returned from the server
 
     Holds a list of ReturnedDataFile objects under self.contents
@@ -642,21 +653,24 @@ class ReturnedData(object):
     """
 
     def __init__(self, filetype=None, N=1, tmpdir=None):
-        self.contents = [ReturnedDataFile(filetype=filetype, tmpdir=tmpdir)
-                         for i in range(N)]
+        self.contents = [
+            ReturnedDataFile(filetype=filetype, tmpdir=tmpdir) for i in range(N)
+        ]
         # filetype checking / conversion has been done in ReturnedDataFile
         self.filetype = self.contents[0].filetype
 
     def __str__(self):
-        return "viresclient ReturnedData object of type "+self.filetype +\
-            "\nSave it to a file with .to_file('filename')" + \
-            "\nLoad it as a pandas dataframe with .as_dataframe()" + \
-            "\nLoad it as an xarray dataset with .as_xarray()"
+        return (
+            "viresclient ReturnedData object of type "
+            + self.filetype
+            + "\nSave it to a file with .to_file('filename')"
+            + "\nLoad it as a pandas dataframe with .as_dataframe()"
+            + "\nLoad it as an xarray dataset with .as_xarray()"
+        )
 
     @property
     def filetype(self):
-        """Filetype string
-        """
+        """Filetype string"""
         return self._filetype
 
     @filetype.setter
@@ -667,8 +681,7 @@ def filetype(self, value):
 
     @property
     def sources(self):
-        """ Get list of source product identifiers.
-        """
+        """Get list of source product identifiers."""
         sources = set()
         for item in self._contents:
             sources.update(item.sources)
@@ -676,8 +689,7 @@ def sources(self):
 
     @property
     def magnetic_models(self):
-        """ Get list of magnetic models used.
-        """
+        """Get list of magnetic models used."""
         models = set()
         for item in self._contents:
             models.update(item.magnetic_models)
@@ -685,8 +697,7 @@ def magnetic_models(self):
 
     @property
     def range_filters(self):
-        """ Get list of filters applied.
-        """
+        """Get list of filters applied."""
         filters = set()
         for item in self._contents:
             filters.update(item.range_filters)
@@ -694,8 +705,7 @@ def range_filters(self):
 
     @property
     def contents(self):
-        """List of ReturnedDataFile objects
-        """
+        """List of ReturnedDataFile objects"""
         return self._contents
 
     @contents.setter
@@ -706,7 +716,8 @@ def contents(self, value):
             if not isinstance(i, ReturnedDataFile):
                 raise TypeError(
                     "Items in ReturnedData.contents should be"
-                    "of type ReturnedDataFile")
+                    "of type ReturnedDataFile"
+                )
         self._contents = value
 
     def as_dataframe(self, expand=False):
@@ -725,8 +736,7 @@ def as_dataframe(self, expand=False):
             pandas.DataFrame
 
         """
-        return pandas.concat(
-            [d.as_dataframe(expand=expand) for d in self.contents])
+        return pandas.concat([d.as_dataframe(expand=expand) for d in self.contents])
 
     def as_xarray(self, reshape=False):
         """Convert the data to an xarray Dataset.
@@ -746,10 +756,13 @@ def as_xarray(self, reshape=False):
         for i, data in enumerate(self.contents):
             ds_part = data.as_xarray(reshape=reshape)
             if ds_part is None:
-                print("Warning: ",
+                print(
+                    "Warning: ",
                     "Unable to create dataset from part {} of {}".format(
-                        i+1, len(self.contents)),
-                    "\n(This part is likely empty)")
+                        i + 1, len(self.contents)
+                    ),
+                    "\n(This part is likely empty)",
+                )
             else:
                 ds_list.append(ds_part)
         ds_list = [i for i in ds_list if i is not None]
@@ -768,7 +781,11 @@ def as_xarray(self, reshape=False):
                 ds_list_per_dim = []
                 for d in dims:
                     drop_dims = [dd for dd in dims if dd != d]
-                    ds_list_per_dim.append(xarray.concat([_ds.drop_dims(drop_dims) for _ds in ds_list], dim=d))
+                    ds_list_per_dim.append(
+                        xarray.concat(
+                            [_ds.drop_dims(drop_dims) for _ds in ds_list], dim=d
+                        )
+                    )
                 ds = xarray.merge(ds_list_per_dim)
         # # Test this other option:
         # ds = self.contents[0].as_xarray()
@@ -802,10 +819,13 @@ def as_xarray_dict(self):
         for i, data in enumerate(self.contents):
             ds_part = data.as_xarray_dict()
             if ds_part is None:
-                print("Warning: ",
+                print(
+                    "Warning: ",
                     "Unable to create dataset from part {} of {}".format(
-                        i+1, len(self.contents)),
-                    "\n(This part is likely empty)")
+                        i + 1, len(self.contents)
+                    ),
+                    "\n(This part is likely empty)",
+                )
             else:
                 ds_list.append(ds_part)
         ds_list = [i for i in ds_list if i is not None]
@@ -842,10 +862,7 @@ def to_files(self, paths, overwrite=False):
         if not isinstance(paths, list) or not isinstance(paths[0], str):
             raise TypeError("paths must be a list of strings")
         if len(paths) != nfiles:
-            raise Exception(
-                "Number of paths must equal number of files ({})".format(
-                    nfiles
-                ))
+            raise Exception(f"Number of paths must equal number of files ({nfiles})")
         for path, retdata in zip(paths, self.contents):
             retdata.to_file(path, overwrite)
 
@@ -872,5 +889,6 @@ def to_file(self, path, overwrite=False):
         """
         if len(self.contents) != 1:
             raise NotImplementedError(
-                "Data is split into multiple files. Use .to_files instead")
+                "Data is split into multiple files. Use .to_files instead"
+            )
         self.contents[0].to_file(path, overwrite)
diff --git a/viresclient/_api/__init__.py b/src/viresclient/_wps/__init__.py
similarity index 100%
rename from viresclient/_api/__init__.py
rename to src/viresclient/_wps/__init__.py
diff --git a/viresclient/_wps/environment.py b/src/viresclient/_wps/environment.py
similarity index 84%
rename from viresclient/_wps/environment.py
rename to src/viresclient/_wps/environment.py
index a49c360..fc2c222 100644
--- a/viresclient/_wps/environment.py
+++ b/src/viresclient/_wps/environment.py
@@ -1,10 +1,10 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # Jinja2 environment initialization
 #
 # Author: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,10 +24,11 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
 import json
-from os.path import join, dirname
+from os.path import dirname, join
+
 from jinja2 import Environment, FileSystemLoader
 
 _DIRNAME = dirname(__file__)
diff --git a/viresclient/_wps/http_util.py b/src/viresclient/_wps/http_util.py
similarity index 70%
rename from viresclient/_wps/http_util.py
rename to src/viresclient/_wps/http_util.py
index 6b22fc1..422e986 100644
--- a/viresclient/_wps/http_util.py
+++ b/src/viresclient/_wps/http_util.py
@@ -1,10 +1,10 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # http utilities
 #
 # Author: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,31 +24,27 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
 from base64 import standard_b64encode
 
 
 def encode_no_auth(**kwargs):
-    """ Dummy encoder.  """
+    """Dummy encoder."""
     return {}
 
 
 def encode_basic_auth(username, password, **kwargs):
-    """ Encode username and password as the basic HTTP access authentication
+    """Encode username and password as the basic HTTP access authentication
     header.
     """
     return {
-        b"Authorization": b"Basic " + standard_b64encode(
-            ("%s:%s" % (username, password)).encode("UTF-8")
-        )
+        b"Authorization": b"Basic "
+        + standard_b64encode((f"{username}:{password}").encode())
     }
 
 
 def encode_token_auth(token, **kwargs):
-    """ Encode token as the bearer authentication header.
-    """
+    """Encode token as the bearer authentication header."""
     # NOTE: Only ASCII characters are allowed in HTTP headers.
-    return {
-        b"Authorization": b"Bearer " + token.encode("ascii")
-    }
+    return {b"Authorization": b"Bearer " + token.encode("ascii")}
diff --git a/viresclient/_wps/log_util.py b/src/viresclient/_wps/log_util.py
similarity index 79%
rename from viresclient/_wps/log_util.py
rename to src/viresclient/_wps/log_util.py
index 8b6c0d9..5d6f20a 100644
--- a/viresclient/_wps/log_util.py
+++ b/src/viresclient/_wps/log_util.py
@@ -1,10 +1,10 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # logging utilities
 #
 # Author: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,17 +24,17 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
-from logging import StreamHandler, Formatter, INFO
 from datetime import datetime
+from logging import INFO, Formatter, StreamHandler
 
-DEFAULT_LOG_FORMAT = '%(asctime)s %(levelname)s %(message)s'
-DEFAULT_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
+DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
+DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
 
 
 def set_stream_handler(logger, level=INFO, log_format=None, time_format=None):
-    """ Set stream handler to the given logger. """
+    """Set stream handler to the given logger."""
     formatter = FormatterUTC(
         log_format or DEFAULT_LOG_FORMAT,
         time_format or DEFAULT_TIME_FORMAT,
@@ -47,18 +47,20 @@ def set_stream_handler(logger, level=INFO, log_format=None, time_format=None):
 
 
 class FormatterUTC(Formatter):
-    """" Custom log formatter class printing UTC timestamps with microsecond
+    """ " Custom log formatter class printing UTC timestamps with microsecond
     precision.
 
     The formatter uses 'datetime.datetime.strftime' method
     rather then 'time.strftime` used by the default formatter.
     """
+
     converter = datetime.utcfromtimestamp
 
     def formatTime(self, record, datefmt=None):
         dts = self.converter(record.created)
         return dts.strftime(datefmt) if datefmt else dts.isoformat(" ")
 
+
 #    def format(self, record):
 #        record.msg = record.msg.encode('unicode_escape')
 #        return Formatter.format(self, record)
diff --git a/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data.xml b/src/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_aeolus_fetch_filtered_data.xml
rename to src/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data.xml
diff --git a/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data_async.xml b/src/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data_async.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_aeolus_fetch_filtered_data_async.xml
rename to src/viresclient/_wps/templates/vires_aeolus_fetch_filtered_data_async.xml
diff --git a/viresclient/_wps/templates/vires_fetch_filtered_data.xml b/src/viresclient/_wps/templates/vires_fetch_filtered_data.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_fetch_filtered_data.xml
rename to src/viresclient/_wps/templates/vires_fetch_filtered_data.xml
diff --git a/viresclient/_wps/templates/vires_fetch_filtered_data_async.xml b/src/viresclient/_wps/templates/vires_fetch_filtered_data_async.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_fetch_filtered_data_async.xml
rename to src/viresclient/_wps/templates/vires_fetch_filtered_data_async.xml
diff --git a/viresclient/_wps/templates/vires_getTimeData.xml b/src/viresclient/_wps/templates/vires_getTimeData.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_getTimeData.xml
rename to src/viresclient/_wps/templates/vires_getTimeData.xml
diff --git a/viresclient/_wps/templates/vires_get_conjunctions.xml b/src/viresclient/_wps/templates/vires_get_conjunctions.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_get_conjunctions.xml
rename to src/viresclient/_wps/templates/vires_get_conjunctions.xml
diff --git a/viresclient/_wps/templates/vires_get_model_info.xml b/src/viresclient/_wps/templates/vires_get_model_info.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_get_model_info.xml
rename to src/viresclient/_wps/templates/vires_get_model_info.xml
diff --git a/viresclient/_wps/templates/vires_get_observatories.xml b/src/viresclient/_wps/templates/vires_get_observatories.xml
similarity index 98%
rename from viresclient/_wps/templates/vires_get_observatories.xml
rename to src/viresclient/_wps/templates/vires_get_observatories.xml
index 21977ee..b51df44 100644
--- a/viresclient/_wps/templates/vires_get_observatories.xml
+++ b/src/viresclient/_wps/templates/vires_get_observatories.xml
@@ -28,4 +28,4 @@
       <ows:Identifier>output</ows:Identifier>
     </wps:RawDataOutput>
   </wps:ResponseForm>
-</wps:Execute>
\ No newline at end of file
+</wps:Execute>
diff --git a/viresclient/_wps/templates/vires_list_jobs.xml b/src/viresclient/_wps/templates/vires_list_jobs.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_list_jobs.xml
rename to src/viresclient/_wps/templates/vires_list_jobs.xml
diff --git a/viresclient/_wps/templates/vires_remove_job.xml b/src/viresclient/_wps/templates/vires_remove_job.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_remove_job.xml
rename to src/viresclient/_wps/templates/vires_remove_job.xml
diff --git a/viresclient/_wps/templates/vires_times_from_orbits.xml b/src/viresclient/_wps/templates/vires_times_from_orbits.xml
similarity index 100%
rename from viresclient/_wps/templates/vires_times_from_orbits.xml
rename to src/viresclient/_wps/templates/vires_times_from_orbits.xml
diff --git a/viresclient/_wps/time_util.py b/src/viresclient/_wps/time_util.py
similarity index 62%
rename from viresclient/_wps/time_util.py
rename to src/viresclient/_wps/time_util.py
index 69f6b74..f225b06 100644
--- a/viresclient/_wps/time_util.py
+++ b/src/viresclient/_wps/time_util.py
@@ -1,10 +1,10 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 #  Various time handling utilities
 #
 # Authors:  Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2015 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,12 +24,12 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
+import math
 import re
 from datetime import date, datetime, timedelta, tzinfo
 from time import time
-import math
 
 RE_ISO_8601_DATETIME_LONG = re.compile(
     r"^(\d{4,4})-(\d{2,2})-(\d{2,2})(?:"
@@ -60,8 +60,10 @@
 
 ZERO = timedelta(0)
 
+
 class TimeZone(tzinfo):
-    """ UTC time-zone class. """
+    """UTC time-zone class."""
+
     def __init__(self, offset, label):
         tzinfo.__init__(self)
         self._offset = offset
@@ -81,17 +83,17 @@ def dst(self, dt):
 
 
 def now():
-    """ Get current UTC timestamp. """
+    """Get current UTC timestamp."""
     return datetime.utcnow()
 
 
 def today():
-    """ Get current UTC date. """
+    """Get current UTC date."""
     return now().date()
 
 
 def to_utc_naive(value):
-    """ convert a time-zone aware `datetime` value to a UTC and strip the
+    """convert a time-zone aware `datetime` value to a UTC and strip the
     timezone.
     """
     if value.tzinfo:
@@ -100,7 +102,7 @@ def to_utc_naive(value):
 
 
 def parse_datetime(value, tz_local=None):
-    """ Parse an ISO 8601 date-time value. The parser supports time-zones and
+    """Parse an ISO 8601 date-time value. The parser supports time-zones and
     both long and short formats.
     Raises a `ValueError` if the conversion was not possible.
     """
@@ -113,24 +115,45 @@ def parse_datetime(value, tz_local=None):
             raise ValueError("Invalid date-time input!")
 
         (
-            year, month, day, hour, minute, sec, usec, tzone, tz_hour, tz_min
+            year,
+            month,
+            day,
+            hour,
+            minute,
+            sec,
+            usec,
+            tzone,
+            tz_hour,
+            tz_min,
         ) = match.groups()
 
         if tzone:
             if tzone == "Z":
                 tz_obj = UTC
             else:
-                tz_obj = UTC if tzone == "Z" else TimeZone(timedelta(
-                    hours=int(tz_hour or 0),
-                    minutes=int(tz_hour[0] + (tz_min or '0'))
-                ), "%s:%s" % (tz_hour, tz_min or "00"))
+                tz_obj = (
+                    UTC
+                    if tzone == "Z"
+                    else TimeZone(
+                        timedelta(
+                            hours=int(tz_hour or 0),
+                            minutes=int(tz_hour[0] + (tz_min or "0")),
+                        ),
+                        "{}:{}".format(tz_hour, tz_min or "00"),
+                    )
+                )
         else:
             tz_obj = tz_local
 
         value = datetime(
-            int(year), int(month), int(day or 0),
-            int(hour or 0), int(minute or 0), int(sec or 0),
-            int(((usec or '') + '000000')[:6]), tz_obj
+            int(year),
+            int(month),
+            int(day or 0),
+            int(hour or 0),
+            int(minute or 0),
+            int(sec or 0),
+            int(((usec or "") + "000000")[:6]),
+            tz_obj,
         )
 
     # convert time-zone aware date-time to UTC
@@ -138,7 +161,7 @@ def parse_datetime(value, tz_local=None):
 
 
 def parse_date(value):
-    """ Parse an ISO 8601 date.
+    """Parse an ISO 8601 date.
     Raises a `ValueError` if the conversion was not possible.
     """
     # NOTE: datetime.datetime is inherited from datetime.date!
@@ -148,7 +171,7 @@ def parse_date(value):
 
 
 def parse_duration(value):
-    """ Parses an ISO 8601 duration string into a python timedelta object.
+    """Parses an ISO 8601 duration string into a python timedelta object.
     Raises a `ValueError` if the conversion was not possible.
     """
     if isinstance(value, timedelta):
@@ -156,18 +179,16 @@ def parse_duration(value):
 
     match = RE_ISO_8601_DURATION.match(value)
     if not match:
-        raise ValueError(
-            "Could not parse ISO 8601 duration from '%s'." % value
-        )
+        raise ValueError("Could not parse ISO 8601 duration from '%s'." % value)
     match = match.groupdict()
 
-    sign = -1 if match['sign'] == "-" else 1
-    days = float(match['days'] or 0)
-    days += float(match['months'] or 0) * 30  # ?!
-    days += float(match['years'] or 0) * 365  # ?!
-    fsec = float(match['seconds'] or 0)
-    fsec += float(match['minutes'] or 0) * 60
-    fsec += float(match['hours'] or 0) * 3600
+    sign = -1 if match["sign"] == "-" else 1
+    days = float(match["days"] or 0)
+    days += float(match["months"] or 0) * 30  # ?!
+    days += float(match["years"] or 0) * 365  # ?!
+    fsec = float(match["seconds"] or 0)
+    fsec += float(match["minutes"] or 0) * 60
+    fsec += float(match["hours"] or 0) * 3600
 
     if sign < 0:
         raise ValueError("Duration %s must not be negative!" % value)
@@ -176,67 +197,73 @@ def parse_duration(value):
 
 
 def encode_duration(value):
-    """ Encode python timedelta object as ISO 8601 duration string.
+    """Encode python timedelta object as ISO 8601 duration string.
     Raises a `ValueError` if the conversion was not possible.
     """
-    #NOTE: The months and years are ambiguous and we do not encode them.
+    # NOTE: The months and years are ambiguous and we do not encode them.
     if not isinstance(value, timedelta):
         raise ValueError("Invalid input type!")
     items = []
     if value.days < 0:
-        items.append('-')
+        items.append("-")
         value = -value
-    items.append('P')
+    items.append("P")
     if value.days != 0:
-        items.append('%dD'%value.days)
+        items.append("%dD" % value.days)
     elif value.seconds == 0 and value.microseconds == 0:
-        items.append('T0S') # zero interval
+        items.append("T0S")  # zero interval
     if value.seconds != 0 or value.microseconds != 0:
         minutes, seconds = divmod(value.seconds, 60)
         hours, minutes = divmod(minutes, 60)
-        items.append('T')
+        items.append("T")
         if hours != 0:
-            items.append('%dH'%hours)
+            items.append("%dH" % hours)
         if minutes != 0:
-            items.append('%dM'%minutes)
+            items.append("%dM" % minutes)
         if value.microseconds != 0:
-            items.append("%.6fS"%(seconds+1e-6*value.microseconds))
+            items.append("%.6fS" % (seconds + 1e-6 * value.microseconds))
         elif seconds != 0:
-            items.append('%dS'%seconds)
+            items.append("%dS" % seconds)
     return "".join(items)
 
 
 def day2k_to_date(day2k):
-    """ Convert integer day number since 2000-01-01 to date as (year, month, day)
+    """Convert integer day number since 2000-01-01 to date as (year, month, day)
     tuple.
     """
     # ref: https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number
     # Gregorian date formula applied since 1582-10-15
     # Julian date formula applied until 1582-10-04
     d__ = int(day2k) + 2451545
-    f__ = d__ + 1401 # Julian calender
-    if d__ > 2299160: # Gregorian calender
-        f__ += (((4*d__ + 274277)//146097)*3)//4 - 38
-    e__ = 4*f__ + 3
-    h__ = 5*((e__ % 1461)//4) + 2
-    day = (h__%153)//5 + 1
-    month = (h__//153 + 2)%12 + 1
-    year = e__//1461 - 4716 + (14 - month)//12
+    f__ = d__ + 1401  # Julian calender
+    if d__ > 2299160:  # Gregorian calender
+        f__ += (((4 * d__ + 274277) // 146097) * 3) // 4 - 38
+    e__ = 4 * f__ + 3
+    h__ = 5 * ((e__ % 1461) // 4) + 2
+    day = (h__ % 153) // 5 + 1
+    month = (h__ // 153 + 2) % 12 + 1
+    year = e__ // 1461 - 4716 + (14 - month) // 12
     return year, month, day
 
 
 def day_fraction_to_time(fraction):
-    """ Convert day fraction to to time as (hour, min, sec, usec) tuple. """
-    subsec, sec = math.modf(round(fraction * 86400.0, 6)) # round to usec
+    """Convert day fraction to to time as (hour, min, sec, usec) tuple."""
+    subsec, sec = math.modf(round(fraction * 86400.0, 6))  # round to usec
     sec, usec = int(sec), int(subsec * 1e6)
-    min_, sec = sec / 60, sec % 60,
-    hour, min_ = min_ / 60, min_ % 60,
-    hour, min_ = int(hour), int(min_)   # Added for Python 3
+    min_, sec = (
+        sec / 60,
+        sec % 60,
+    )
+    hour, min_ = (
+        min_ / 60,
+        min_ % 60,
+    )
+    hour, min_ = int(hour), int(min_)  # Added for Python 3
     return hour, min_, sec, usec
 
 
 def mjd2000_to_datetime(mjd2k):
-    """ Convert Modified Julian Date 2000 to `datetime.datetime` object. """
+    """Convert Modified Julian Date 2000 to `datetime.datetime` object."""
     day2k = math.floor(mjd2k)
     year, month, day = day2k_to_date(day2k)
     hour, min_, sec, usec = day_fraction_to_time(mjd2k - day2k)
@@ -244,23 +271,23 @@ def mjd2000_to_datetime(mjd2k):
 
 
 def unix_epoch_to_datetime(ux_epoch):
-    """ Convert number of seconds since 1970-01-01 to `datetime.datetime`
+    """Convert number of seconds since 1970-01-01 to `datetime.datetime`
     object.
     """
     return datetime.utcfromtimestamp(ux_epoch)
 
 
-class Timer(object):
-    """ Timer class measuring elapsed amount of time.
-    """
+class Timer:
+    """Timer class measuring elapsed amount of time."""
+
     def __init__(self):
         self._start = time()
 
     def restart(self):
-        """ Restart the timer. """
+        """Restart the timer."""
         self._start = time()
 
     @property
     def elapsed_time(self):
-        """ Get time time in seconds elapsed since the last timer restart. """
+        """Get time time in seconds elapsed since the last timer restart."""
         return time() - self._start
diff --git a/viresclient/_wps/wps.py b/src/viresclient/_wps/wps.py
similarity index 76%
rename from viresclient/_wps/wps.py
rename to src/viresclient/_wps/wps.py
index 39d5292..fd40ca1 100644
--- a/viresclient/_wps/wps.py
+++ b/src/viresclient/_wps/wps.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # WPS 1.0 service proxy
 #
 # Authors: Martin Paces <martin.paces@eox.at>
 #          Ashley Smith <ashley.smith@ed.ac.uk>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,29 +25,31 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
 try:
-    from urllib.request import urlopen, Request
     from urllib.error import HTTPError
+    from urllib.request import Request, urlopen
 except ImportError:
     # Python 2 backward compatibility
     from urllib2 import urlopen, Request, HTTPError
 
-from time import sleep
-from logging import getLogger, LoggerAdapter
 from contextlib import closing
+from logging import LoggerAdapter, getLogger
+from time import sleep
 from xml.etree import ElementTree
+
 from .time_util import Timer
 
 NS_OWS11 = "http://www.opengis.net/ows/1.1"
 NS_OWS20 = "http://www.opengis.net/ows/2.0"
 
 
-class WPSStatus(object):
-    """ WPS status information.
+class WPSStatus:
+    """WPS status information.
     Matches output from WPS10Service.poll_status()
     """
+
     def __init__(self):
         self.status = None
         self.percentCompleted = 0
@@ -62,35 +64,46 @@ def update(self, status, percentCompleted, url, execute_response):
 
 
 class WPSError(Exception):
-    """ WPS error exception """
+    """WPS error exception"""
+
     def __init__(self, code, locator, text):
         self.ows_exception_code = code
         self.ows_exception_text = code
         self.ows_exception_locator = locator
-        Exception.__init__(self, "WPS Request Failed! Reason: %s%s%s" % (
-            code or "", " [%s]" % locator if locator else "",
-            ": %s" % text if text else ""
-        ))
+        Exception.__init__(
+            self,
+            "WPS Request Failed! Reason: %s%s%s"
+            % (
+                code or "",
+                " [%s]" % locator if locator else "",
+                ": %s" % text if text else "",
+            ),
+        )
 
 
 class AuthenticationError(Exception):
-    """ Authentication error exception """
+    """Authentication error exception"""
+
     def __init__(self, text=None):
         Exception.__init__(
-            self, "Perhaps credentials are missing or invalid. {}"
-            .format(text if text else ""))
+            self,
+            "Perhaps credentials are missing or invalid. {}".format(
+                text if text else ""
+            ),
+        )
 
 
-class WPS10Service(object):
-    """ WPS 1.0 service proxy class.
+class WPS10Service:
+    """WPS 1.0 service proxy class.
 
-        wps = WPS10Service(url, headers)
+    wps = WPS10Service(url, headers)
 
-        Parameters:
-            url     - service URL
-            headers - optional dictionary of the HTTP headers sent with each
-                      request
+    Parameters:
+        url     - service URL
+        headers - optional dictionary of the HTTP headers sent with each
+                  request
     """
+
     STATUS = {
         "{http://www.opengis.net/wps/1.0.0}ProcessAccepted": "ACCEPTED",
         "{http://www.opengis.net/wps/1.0.0}ProcessFailed": "FAILED",
@@ -100,7 +113,7 @@ class WPS10Service(object):
 
     class _LoggerAdapter(LoggerAdapter):
         def process(self, msg, kwargs):
-            return 'WPS10Service: %s' % msg, kwargs
+            return "WPS10Service: %s" % msg, kwargs
 
     def __init__(self, url, headers=None, logger=None):
         self.url = url
@@ -108,35 +121,42 @@ def __init__(self, url, headers=None, logger=None):
         self.logger = self._LoggerAdapter(logger or getLogger(__name__), {})
 
     def retrieve(self, request, handler=None):
-        """ Send a synchronous POST WPS request to a server and retrieve
+        """Send a synchronous POST WPS request to a server and retrieve
         the output.
         """
         return self._retrieve(
-            Request(self.url, request, self.headers),
-            handler, self.error_handler
+            Request(self.url, request, self.headers), handler, self.error_handler
         )
 
-    def retrieve_async(self, request, handler=None, status_handler=None,
-                       cleanup_handler=None, polling_interval=1,
-                       output_name="output"):
-        """ Send an asynchronous POST WPS request to a server and retrieve
+    def retrieve_async(
+        self,
+        request,
+        handler=None,
+        status_handler=None,
+        cleanup_handler=None,
+        polling_interval=1,
+        output_name="output",
+    ):
+        """Send an asynchronous POST WPS request to a server and retrieve
         the output.
         """
         timer = Timer()
-        status, percentCompleted, status_url, execute_response = self.submit_async(request)
+        status, percentCompleted, status_url, execute_response = self.submit_async(
+            request
+        )
         wpsstatus = WPSStatus()
         wpsstatus.update(status, percentCompleted, status_url, execute_response)
 
         def log_wpsstatus(wpsstatus):
             self.logger.info(
-                "%s %s %.3fs",
-                wpsstatus.status, wpsstatus.url, timer.elapsed_time
-                )
+                "%s %s %.3fs", wpsstatus.status, wpsstatus.url, timer.elapsed_time
+            )
 
         def log_wpsstatus_percentCompleted(wpsstatus):
             self.logger.info(
                 "{:.3f}s elapsed: Percent Completed: {}\n".format(
-                    timer.elapsed_time, wpsstatus.percentCompleted)
+                    timer.elapsed_time, wpsstatus.percentCompleted
+                )
             )
 
         try:
@@ -159,7 +179,9 @@ def log_wpsstatus_percentCompleted(wpsstatus):
                     status_handler(wpsstatus)
 
             if wpsstatus.status == "FAILED":
-                ows_exception, namespace = self.find_exception(wpsstatus.execute_response)
+                ows_exception, namespace = self.find_exception(
+                    wpsstatus.execute_response
+                )
                 raise self.parse_ows_exception(ows_exception, namespace)
 
             output = self.retrieve_async_output(
@@ -172,16 +194,14 @@ def log_wpsstatus_percentCompleted(wpsstatus):
         return output
 
     def retrieve_async_output(self, status_url, output_name, handler=None):
-        """ Retrieve asynchronous job output reference. """
-        self.logger.debug(
-            "Retrieving asynchronous job output '%s'.", output_name
-        )
+        """Retrieve asynchronous job output reference."""
+        self.logger.debug("Retrieving asynchronous job output '%s'.", output_name)
         output_url = self.parse_output_reference(status_url, output_name)
         return self._retrieve(Request(output_url, None, self.headers), handler)
 
     @staticmethod
     def parse_output_reference(xml, identifier):
-        """ Parse output reference. """
+        """Parse output reference."""
         root = xml.getroot()
         wps_outputs = root.find("{http://www.opengis.net/wps/1.0.0}ProcessOutputs")
         for elm in wps_outputs.findall("./{http://www.opengis.net/wps/1.0.0}Output"):
@@ -192,19 +212,19 @@ def parse_output_reference(xml, identifier):
                 )
                 return elm_reference.attrib["href"]
 
-
     def submit_async(self, request):
-        """ Send a POST WPS asynchronous request to a server and retrieve
+        """Send a POST WPS asynchronous request to a server and retrieve
         the status URL.
         """
         self.logger.debug("Submitting asynchronous job.")
         return self._retrieve(
             Request(self.url, request, self.headers),
-            self.parse_status, self.error_handler
+            self.parse_status,
+            self.error_handler,
         )
 
     def poll_status(self, status_url):
-        """ Poll status of an asynchronous WPS job. """
+        """Poll status of an asynchronous WPS job."""
         self.logger.debug("Polling asynchronous job status.")
         return self._retrieve(
             Request(status_url, None, self.headers), self.parse_status
@@ -212,23 +232,23 @@ def poll_status(self, status_url):
 
     @classmethod
     def parse_status(cls, response):
-        """ Parse process status and status location. """
+        """Parse process status and status location."""
         xml = ElementTree.parse(response)
         return (
             cls.parse_process_status(xml),
             cls.parse_process_percentCompleted(xml),
             cls.parse_status_location(xml),
-            xml
+            xml,
         )
 
     @staticmethod
     def parse_status_location(xml):
-        """ Parse status location from an asynchronous WPS execute response. """
+        """Parse status location from an asynchronous WPS execute response."""
         return xml.getroot().attrib["statusLocation"]
 
     @classmethod
     def parse_process_status(cls, xml):
-        """ Parse status from an asynchronous WPS execute response. """
+        """Parse status from an asynchronous WPS execute response."""
         root = xml.getroot()
         elm_status = root.find("{http://www.opengis.net/wps/1.0.0}Status")
         if elm_status is None:
@@ -239,7 +259,7 @@ def parse_process_status(cls, xml):
 
     @classmethod
     def parse_process_percentCompleted(cls, xml):
-        """ Parse percentCompleted from an asynchronous WPS execute response. """
+        """Parse percentCompleted from an asynchronous WPS execute response."""
         root = xml.getroot()
         elm_status = root.find("{http://www.opengis.net/wps/1.0.0}Status")
         if elm_status is None:
@@ -258,7 +278,7 @@ def parse_process_percentCompleted(cls, xml):
 
     @classmethod
     def error_handler(cls, http_error):
-        """ Handle HTTP error and parse OWS exception. """
+        """Handle HTTP error and parse OWS exception."""
         if http_error.status in [401, 403]:
             raise AuthenticationError
         try:
@@ -270,7 +290,7 @@ def error_handler(cls, http_error):
 
     @classmethod
     def parse_ows_exception(cls, ows_exception, namespace):
-        """ Parse OWS exception. """
+        """Parse OWS exception."""
         locator = ows_exception.attrib.get("locator")
         code = ows_exception.attrib.get("exceptionCode")
         text_element = ows_exception.find("{%s}ExceptionText" % namespace)
@@ -279,7 +299,7 @@ def parse_ows_exception(cls, ows_exception, namespace):
 
     @classmethod
     def find_exception(cls, xml):
-        """ Find OWS exception element in the given XML document. """
+        """Find OWS exception element in the given XML document."""
         element = xml.getroot()
         for namespace in (NS_OWS11, NS_OWS20):
             ows_exception = element.find(".//{%s}Exception" % namespace)
@@ -289,16 +309,14 @@ def find_exception(cls, xml):
             raise ElementTree.ParseError
 
     def _retrieve(self, request, response_handler=None, error_handler=None):
-        """ Retrieve and parse HTTP response. """
+        """Retrieve and parse HTTP response."""
         method = request.get_method()
         url = request.get_full_url()
         timer = Timer()
         try:
             with closing(urlopen(request)) as file_in:
                 output = (response_handler or self._default_handler)(file_in)
-            self.logger.info(
-                "%d %s %s %.3fs", 200, method, url, timer.elapsed_time
-            )
+            self.logger.info("%d %s %s %.3fs", 200, method, url, timer.elapsed_time)
             return output
         except HTTPError as error:
             self.logger.error(
diff --git a/viresclient/_wps/wps_vires.py b/src/viresclient/_wps/wps_vires.py
similarity index 84%
rename from viresclient/_wps/wps_vires.py
rename to src/viresclient/_wps/wps_vires.py
index 5b23512..3789492 100644
--- a/viresclient/_wps/wps_vires.py
+++ b/src/viresclient/_wps/wps_vires.py
@@ -1,10 +1,10 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # WPS 1.0 service proxy
 #
 # Author: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2018 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,7 +24,7 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 
 try:
     from urllib.parse import urlparse
@@ -33,9 +33,9 @@
     from urlparse import urlparse
 
 import re
-from .wps import WPS10Service
-from .environment import JINJA2_ENVIRONMENT
 
+from .environment import JINJA2_ENVIRONMENT
+from .wps import WPS10Service
 
 RE_MATCH_JOB_ID = re.compile(
     r"^[0-9a-f]{8,8}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]{12,12}$"
@@ -43,7 +43,7 @@
 
 
 class ViresWPS10Service(WPS10Service):
-    """ VirES specific WPS 1.0 service proxy class.
+    """VirES specific WPS 1.0 service proxy class.
     This class implement additional VirES specific synchronous clean-up handler.
 
         wps = ViresWPS10Service(url, headers)
@@ -53,18 +53,19 @@ class ViresWPS10Service(WPS10Service):
             headers - optional dictionary of the HTTP headers sent with each
                       request
     """
+
     template_remove_job = JINJA2_ENVIRONMENT.get_template("vires_remove_job.xml")
 
     def _default_cleanup_handler(self, status_url):
-        """ Remove asynchronous job using the VirES specific interface. """
+        """Remove asynchronous job using the VirES specific interface."""
         job_id = status_url_to_job_id(status_url)
         self.logger.debug("Removing VirES asynchronous job %s.", job_id)
-        request = self.template_remove_job.render(job_id=job_id).encode('UTF-8')
+        request = self.template_remove_job.render(job_id=job_id).encode("UTF-8")
         self.retrieve(request)
 
 
 def status_url_to_job_id(status_url):
-    """ Extract job id from a VirES WPS status URL. """
+    """Extract job id from a VirES WPS status URL."""
     for item in urlparse(status_url).path.split("/"):
         if RE_MATCH_JOB_ID.match(item):
             return item
diff --git a/viresclient/_wps/__init__.py b/src/viresclient/commands/__init__.py
similarity index 100%
rename from viresclient/_wps/__init__.py
rename to src/viresclient/commands/__init__.py
diff --git a/viresclient/commands/common.py b/src/viresclient/commands/common.py
similarity index 69%
rename from viresclient/commands/common.py
rename to src/viresclient/commands/common.py
index 0eafe35..babde41 100644
--- a/viresclient/commands/common.py
+++ b/src/viresclient/commands/common.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # viresclient CLI - data upload
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,16 +25,17 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 
-class Command():
-    """ Base command class. """
-    help = None #command help
+class Command:
+    """Base command class."""
+
+    help = None  # command help
 
     class Error(Exception):
-        """ Generic command error exception. """
+        """Generic command error exception."""
 
     def add_arguments_to_parser(self, parser):
         raise NotImplementedError
@@ -44,20 +45,22 @@ def execute(self, **kwargs):
 
 
 class ConfigurationCommand(Command):
-    """ Base class for commands working with a configuration file. """
+    """Base class for commands working with a configuration file."""
 
     def add_arguments_to_parser(self, parser):
         parser.add_argument(
-            "-c", "--config", action="store", type=str, dest="config_path",
-            help="Path to a configuration file."
+            "-c",
+            "--config",
+            action="store",
+            type=str,
+            dest="config_path",
+            help="Path to a configuration file.",
         )
 
 
 class UrlConfigurationCommand(ConfigurationCommand):
-    """ Base class for commands working with a URL configuration file. """
+    """Base class for commands working with a URL configuration file."""
 
     def add_arguments_to_parser(self, parser):
         super().add_arguments_to_parser(parser)
-        parser.add_argument(
-            "server_url", action="store", type=str, help="server URL"
-        )
+        parser.add_argument("server_url", action="store", type=str, help="server URL")
diff --git a/viresclient/commands/configuration.py b/src/viresclient/commands/configuration.py
similarity index 88%
rename from viresclient/commands/configuration.py
rename to src/viresclient/commands/configuration.py
index 0f4cce0..e2c5319 100644
--- a/viresclient/commands/configuration.py
+++ b/src/viresclient/commands/configuration.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # viresclient CLI - configuration management commands
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,14 +25,15 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 import sys
-from getpass import getpass, getuser
 from os import remove
 from os.path import exists
+
 from viresclient import ClientConfig
+
 from .common import ConfigurationCommand, UrlConfigurationCommand
 
 
@@ -94,20 +95,20 @@ def execute(self, config_path):
 
 
 class ShowConfigurationCommand(ConfigurationCommand):
-    """ Command dumping configuration to a standard output. """
+    """Command dumping configuration to a standard output."""
+
     help = "Print the configuration to standard output."
 
     def execute(self, config_path):
         config = ClientConfig(path=config_path)
         if not exists(config.path):
-            raise self.Error(
-                "Configuration file %s does not exist!" % config.path
-            )
+            raise self.Error("Configuration file %s does not exist!" % config.path)
         sys.stdout.write(str(config))
 
 
 class ClearCredentialsCommand(ConfigurationCommand):
-    """ Command to remove stored configuration. """
+    """Command to remove stored configuration."""
+
     help = "Delete the default configuration file."
 
     def execute(self, config_path):
diff --git a/viresclient/commands/upload.py b/src/viresclient/commands/upload.py
similarity index 71%
rename from viresclient/commands/upload.py
rename to src/viresclient/commands/upload.py
index ce88f9a..e0a08e5 100644
--- a/viresclient/commands/upload.py
+++ b/src/viresclient/commands/upload.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # viresclient CLI - data upload commands
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,20 +25,21 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 from os.path import exists
+
 from viresclient import ClientConfig
-from .common import ConfigurationCommand
-from .._api.upload import DataUpload
 
+from .._api.upload import DataUpload
+from .common import ConfigurationCommand
 
-JSON_OPTS = {'sort_keys': False, 'indent': 2, 'separators': (',', ': ')}
+JSON_OPTS = {"sort_keys": False, "indent": 2, "separators": (",", ": ")}
 
 
 class DataUploadCommand(ConfigurationCommand):
-    """ Base data upload command. """
+    """Base data upload command."""
 
     def add_arguments_to_parser(self, parser):
         super().add_arguments_to_parser(parser)
@@ -47,19 +48,18 @@ def add_arguments_to_parser(self, parser):
         )
 
     def get_data_upload_instance(self, config_path, server_url):
-        """ Get instance of the DataUpload class. """
+        """Get instance of the DataUpload class."""
         config = ClientConfig(path=config_path)
         server_url = server_url or config.default_url
 
         if not server_url:
             raise self.Error(
-                "No default URL is configured. "
-                "Enter server URL as a CLI argument!"
+                "No default URL is configured. " "Enter server URL as a CLI argument!"
             )
 
         api_url = DataUpload.get_api_url(server_url)
         ows_url = DataUpload.get_ows_url(server_url)
-        access_token = config.get_site_config(ows_url).get('token')
+        access_token = config.get_site_config(ows_url).get("token")
 
         if not access_token:
             raise self.Error("No access token configured for %s!" % ows_url)
@@ -72,9 +72,7 @@ class UploadDataFileCommand(DataUploadCommand):
 
     def add_arguments_to_parser(self, parser):
         super().add_arguments_to_parser(parser)
-        parser.add_argument(
-            "filename", action="store", type=str, help="uploaded file"
-        )
+        parser.add_argument("filename", action="store", type=str, help="uploaded file")
 
     def execute(self, config_path, server_url, filename):
 
@@ -84,7 +82,7 @@ def execute(self, config_path, server_url, filename):
         api_proxy = self.get_data_upload_instance(config_path, server_url)
 
         item = api_proxy.post(filename)
-        print("%s[%s] uploaded" % (item['identifier'], item['filename']))
+        print("{}[{}] uploaded".format(item["identifier"], item["filename"]))
 
 
 class RemoveUploadsCommand(DataUploadCommand):
@@ -93,8 +91,8 @@ class RemoveUploadsCommand(DataUploadCommand):
     def execute(self, config_path, server_url):
         api_proxy = self.get_data_upload_instance(config_path, server_url)
         for item in api_proxy.get():
-            api_proxy.delete(item['identifier'])
-            print("%s[%s] removed" % (item['identifier'], item['filename']))
+            api_proxy.delete(item["identifier"])
+            print("{}[{}] removed".format(item["identifier"], item["filename"]))
 
 
 class SetConstantParameters(DataUploadCommand):
@@ -103,13 +101,12 @@ class SetConstantParameters(DataUploadCommand):
     def add_arguments_to_parser(self, parser):
         super().add_arguments_to_parser(parser)
         parser.add_argument(
-            "-p", "--parameter", action="append", type=str,
-            help="key=value parameter"
+            "-p", "--parameter", action="append", type=str, help="key=value parameter"
         )
 
     def execute(self, config_path, server_url, parameter):
         if not parameter:
-            return # nothing to be done
+            return  # nothing to be done
 
         new_parameters = dict(self._parse_parameter(item) for item in parameter)
 
@@ -117,14 +114,14 @@ def execute(self, config_path, server_url, parameter):
         for item in api_proxy.get():
             if item.get("constant_fields") is not None:
                 parmeters = {
-                    field: data['value']
+                    field: data["value"]
                     for field, data in item["constant_fields"].items()
                 }
                 parmeters.update(new_parameters)
                 api_proxy.set_constant_parameters(
-                    item['identifier'], parmeters, replace=True
+                    item["identifier"], parmeters, replace=True
                 )
-                print("%s: parameters updated" % (item['identifier']))
+                print("%s: parameters updated" % (item["identifier"]))
 
             else:
                 raise self.Error(
@@ -140,9 +137,8 @@ def _parse_parameter(cls, raw_parameter):
                 raise ValueError
             return name, float(value)
         except ValueError:
-            raise cls.Error(
-                "Invalid parameter specification '%s' !" % raw_parameter
-            )
+            raise cls.Error("Invalid parameter specification '%s' !" % raw_parameter)
+
 
 class RemoveConstantParameters(DataUploadCommand):
     help = """ Remove constant parameters. """
@@ -151,10 +147,8 @@ def execute(self, config_path, server_url):
         api_proxy = self.get_data_upload_instance(config_path, server_url)
         for item in api_proxy.get():
             if item.get("constant_fields"):
-                api_proxy.set_constant_parameters(
-                    item['identifier'], {}, replace=True
-                )
-                print("%s: parameters removed" % (item['identifier']))
+                api_proxy.set_constant_parameters(item["identifier"], {}, replace=True)
+                print("%s: parameters removed" % (item["identifier"]))
 
 
 class ShowUploadsCommand(DataUploadCommand):
@@ -167,15 +161,15 @@ def execute(self, config_path, server_url):
 
     @staticmethod
     def print_info(info):
-        print(info['identifier'])
-        print("  filename:     ", info['filename'])
-        print("  is valid:     ", info.get('is_valid', True))
-        print("  data start:   ", info['start'])
-        print("  data end:     ", info['end'])
-        print("  uploaded on:  ", info['created'])
-        print("  content type: ", info['content_type'])
-        print("  size:         ", info['size'])
-        print("  MD5 checksum: ", info['checksum'])
+        print(info["identifier"])
+        print("  filename:     ", info["filename"])
+        print("  is valid:     ", info.get("is_valid", True))
+        print("  data start:   ", info["start"])
+        print("  data end:     ", info["end"])
+        print("  uploaded on:  ", info["created"])
+        print("  content type: ", info["content_type"])
+        print("  size:         ", info["size"])
+        print("  MD5 checksum: ", info["checksum"])
 
         missing_fields = info.get("missing_fields", {})
         if missing_fields:
@@ -187,8 +181,8 @@ def print_info(info):
         if constant_fields:
             print("  constant fields:")
             for field, data in sorted(constant_fields.items()):
-                print("    %s=%s" % (field, data['value']))
+                print("    {}={}".format(field, data["value"]))
 
         print("  fields:")
-        for field in sorted(info.get('fields') or info.get('info') or []):
+        for field in sorted(info.get("fields") or info.get("info") or []):
             print("    %s" % field)
diff --git a/viresclient/commands/viresclient.py b/src/viresclient/commands/viresclient.py
similarity index 82%
rename from viresclient/commands/viresclient.py
rename to src/viresclient/commands/viresclient.py
index 8fc07e8..3f7033d 100644
--- a/viresclient/commands/viresclient.py
+++ b/src/viresclient/commands/viresclient.py
@@ -1,11 +1,11 @@
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 #
 # viresclient CLI
 #
 # Project: VirES-Python-Client
 # Authors: Martin Paces <martin.paces@eox.at>
 #
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # Copyright (C) 2019 EOX IT Services GmbH
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,22 +25,28 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
-#-------------------------------------------------------------------------------
+# -------------------------------------------------------------------------------
 # pylint: disable=missing-docstring,arguments-differ
 
 import sys
-import logging
 from argparse import ArgumentParser
+
 from .common import Command
 from .configuration import (
-    SetTokenCommand, RemoveServerCommand,
-    SetDefaultServerCommand, RemoveDefaultServerCommand,
-    ShowConfigurationCommand, InitializeConfigurationCommand,
-    ClearCredentialsCommand
+    ClearCredentialsCommand,
+    InitializeConfigurationCommand,
+    RemoveDefaultServerCommand,
+    RemoveServerCommand,
+    SetDefaultServerCommand,
+    SetTokenCommand,
+    ShowConfigurationCommand,
 )
 from .upload import (
-    UploadDataFileCommand, ShowUploadsCommand, RemoveUploadsCommand,
-    RemoveConstantParameters, SetConstantParameters,
+    RemoveConstantParameters,
+    RemoveUploadsCommand,
+    SetConstantParameters,
+    ShowUploadsCommand,
+    UploadDataFileCommand,
 )
 
 # dictionary of registered commands
@@ -64,7 +70,7 @@
 
 
 def main(*cli_args):
-    """ main function. """
+    """main function."""
     parser = ArgumentParser()
 
     # add registered subcommands
@@ -82,7 +88,7 @@ def main(*cli_args):
     parsed_args = dict(parser.parse_args(args=cli_args[1:]).__dict__)
 
     # execute selected subcommand
-    requested_command = parsed_args.pop('command')
+    requested_command = parsed_args.pop("command")
     return COMMANDS[requested_command].execute(**parsed_args)
 
 
diff --git a/viresclient/_data/__init__.py b/viresclient/_data/__init__.py
deleted file mode 100644
index a8451e5..0000000
--- a/viresclient/_data/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from os.path import join, dirname
-import json
-
-_DIRNAME = dirname(__file__)
-
-with open(join(_DIRNAME, "config_swarm.json"), "r", encoding="utf-8") as f:
-    CONFIG_SWARM = json.load(f)
-
-with open(join(_DIRNAME, "config_aeolus.json"), "r", encoding="utf-8") as f:
-    CONFIG_AEOLUS = json.load(f)


Test patch:
diff --git a/test/test_ClientRequest.py b/test/test_ClientRequest.py
deleted file mode 100644
index 3576675..0000000
--- a/test/test_ClientRequest.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import pytest
-
-from viresclient._client import ClientRequest
-from viresclient import SwarmRequest, AeolusRequest
-import viresclient
-
-
-def test_ClientRequest():
-    """Test that a ClientRequest gets set up correctly.
-    """
-    request = ClientRequest('dummy_url')
-    assert isinstance(request._wps_service,
-                      viresclient._wps.wps_vires.ViresWPS10Service
-                      )
-    request = SwarmRequest('dummy_url')
-    assert isinstance(request._wps_service,
-                      viresclient._wps.wps_vires.ViresWPS10Service
-                      )
-    request = AeolusRequest('dummy_url')
-    assert isinstance(request._wps_service,
-                      viresclient._wps.wps_vires.ViresWPS10Service
-                      )
diff --git a/viresclient/commands/__init__.py b/tests/__init__.py
similarity index 100%
rename from viresclient/commands/__init__.py
rename to tests/__init__.py
diff --git a/test/conftest.py b/tests/conftest.py
similarity index 99%
rename from test/conftest.py
rename to tests/conftest.py
index b822b29..4d0455d 100644
--- a/test/conftest.py
+++ b/tests/conftest.py
@@ -2,14 +2,17 @@
 
 print("setting up")
 
+
 @pytest.fixture(scope="session")
 def tmpfile(tmpdir_factory):
     """Sets up a temporary file
 
     https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-factory-fixture
     """
+
     def make(filename):
         fn = tmpdir_factory.mktemp("data").join(filename)
         return fn
+
     # fn = tmpdir_factory.mktemp("data").join(filename)
     return make
diff --git a/test/data/test_data_01.cdf b/tests/data/test_data_01.cdf
similarity index 100%
rename from test/data/test_data_01.cdf
rename to tests/data/test_data_01.cdf
diff --git a/test/data/test_data_01.csv b/tests/data/test_data_01.csv
similarity index 100%
rename from test/data/test_data_01.csv
rename to tests/data/test_data_01.csv
diff --git a/test/data/test_data_01_from_cdf.json b/tests/data/test_data_01_from_cdf.json
similarity index 99%
rename from test/data/test_data_01_from_cdf.json
rename to tests/data/test_data_01_from_cdf.json
index 430cbe1..b40659f 100644
--- a/test/data/test_data_01_from_cdf.json
+++ b/tests/data/test_data_01_from_cdf.json
@@ -1,1 +1,1 @@
-{"B_NEC":{"1451608080000":[23432.0698,2929.1195,7001.0706],"1451608090000":[23450.9161,2900.0733,7554.7711],"1451608100000":[23465.5579,2872.2875,8110.0848],"1451608110000":[23476.0961,2844.6195,8666.9527],"1451608120000":[23482.2611,2817.4788,9225.4296],"1451608130000":[23483.7305,2791.4418,9785.9031],"1451608140000":[23480.7606,2765.0694,10348.1705],"1451608150000":[23473.3483,2738.8811,10911.7339],"1451608160000":[23461.8746,2714.0217,11475.2695],"1451608170000":[23445.3569,2690.6571,12040.0636],"1451608180000":[23424.171,2668.0164,12605.5356],"1451608190000":[23398.2049,2646.3827,13171.4466],"1451608200000":[23367.905,2623.5412,13737.0976],"1451608210000":[23332.4142,2601.8444,14303.242],"1451608220000":[23292.1384,2580.2121,14869.1609],"1451608230000":[23247.1733,2559.3295,15434.4944],"1451608240000":[23197.443,2540.0891,15999.0812],"1451608250000":[23143.0971,2519.5869,16562.9784],"1451608260000":[23083.8496,2499.6672,17126.3037],"1451608270000":[23020.0172,2481.1638,17688.1775],"1451608280000":[22951.0829,2463.3073,18249.3113],"1451608290000":[22877.5153,2446.2538,18809.1623],"1451608300000":[22799.7383,2430.1555,19366.7738],"1451608310000":[22717.0494,2414.553,19923.0709],"1451608320000":[22629.961,2397.7601,20477.4945],"1451608330000":[22538.543,2383.0698,21029.418],"1451608340000":[22442.1229,2368.7208,21579.8326],"1451608350000":[22341.15,2354.9218,22127.9093],"1451608360000":[22235.8724,2341.3574,22673.0688],"1451608370000":[22126.1144,2328.2601,23215.3879],"1451608380000":[22011.7563,2315.2349,23755.4868],"1451608390000":[21893.4965,2302.9224,24292.371],"1451608400000":[21771.2944,2290.4303,24826.2958],"1451608410000":[21645.2087,2278.8584,25356.84],"1451608420000":[21514.9309,2267.1869,25884.482],"1451608430000":[21381.2396,2256.9781,26408.338],"1451608440000":[21243.3124,2247.3755,26929.1382],"1451608450000":[21100.765,2237.0813,27447.0339],"1451608460000":[20954.6382,2228.0043,27961.1089],"1451608470000":[20804.8938,2218.2377,28471.2215],"1451608480000":[20650.194,2207.0246,28977.6479],"1451608490000":[20489.9142,2196.6924,29480.2737],"1451608500000":[20325.8407,2187.6858,29979.2208],"1451608510000":[20159.6999,2179.2673,30473.8893],"1451608520000":[19991.2017,2170.9005,30964.1746],"1451608530000":[19818.5676,2160.6222,31450.4361],"1451608540000":[19641.5361,2150.0191,31932.2093],"1451608550000":[19459.5343,2139.066,32409.5796],"1451608560000":[19272.6095,2128.2777,32882.1531],"1451608570000":[19080.6077,2117.5848,33349.9103],"1451608580000":[18885.2364,2107.0966,33812.2502],"1451608590000":[18686.7238,2095.1847,34268.8645],"1451608600000":[18484.1435,2082.6037,34720.2676],"1451608610000":[18278.4234,2070.2655,35165.753],"1451608620000":[18069.4894,2057.4281,35605.3007],"1451608630000":[17856.9885,2041.7874,36039.0],"1451608640000":[17639.977,2025.3822,36466.9087],"1451608650000":[17419.1825,2008.7774,36888.8158],"1451608660000":[17194.333,1992.3104,37304.748],"1451608670000":[16966.3469,1976.8922,37714.512],"1451608680000":[16735.5632,1960.5121,38117.6946],"1451608690000":[16502.0811,1941.3664,38514.1975],"1451608700000":[16265.6321,1920.6402,38903.8763],"1451608710000":[16024.7346,1898.5381,39287.3186],"1451608720000":[15780.3965,1874.1972,39663.8883],"1451608730000":[15532.3029,1849.5028,40033.6245],"1451608740000":[15279.5263,1825.4945,40396.8295],"1451608750000":[15023.6927,1801.3419,40752.8871],"1451608760000":[14763.9563,1777.93,41101.7729],"1451608770000":[14501.2447,1753.5304,41442.8961],"1451608780000":[14236.4817,1722.2657,41775.8023],"1451608790000":[13971.3653,1681.3367,42099.5071],"1451608800000":[13696.9616,1646.0606,42415.5735],"1451608810000":[13417.4511,1607.6536,42722.709],"1451608820000":[13128.7717,1571.7185,43021.1606],"1451608830000":[12786.5826,1630.1635,43319.6849],"1451608840000":[12403.1062,1785.1025,43611.6349],"1451608850000":[12119.7286,1741.0039,43867.8131],"1451608860000":[11804.3785,1779.5489,44115.8091],"1451608870000":[11512.291,1803.2467,44345.2827],"1451608880000":[11186.3861,1850.0925,44571.0204],"1451608890000":[10963.0587,1705.2131,44769.3456],"1451608900000":[10650.3643,1746.5147,44974.3039],"1451608910000":[10363.7252,1707.6477,45170.0057],"1451608920000":[10138.5062,1465.5675,45355.5124],"1451608930000":[9908.6233,1266.5303,45536.3933],"1451608940000":[9686.9224,1096.6243,45708.7241],"1451608950000":[9412.8174,1034.6506,45883.2184],"1451608960000":[9142.3121,1020.49,46047.8374],"1451608970000":[8856.1516,973.819,46206.5991],"1451608980000":[8569.8373,923.057,46354.794],"1451608990000":[8288.9173,870.1733,46491.289],"1451609000000":[8007.3192,815.844,46617.8809],"1451609010000":[7728.4071,760.4939,46734.1824],"1451609020000":[7451.9183,705.5425,46840.7487],"1451609030000":[7178.7142,656.4439,46937.9094],"1451609040000":[6906.7896,604.5927,47026.185],"1451609050000":[6639.0914,555.1535,47105.1767],"1451609060000":[6378.0966,504.222,47174.6472],"1451609070000":[6120.2416,449.7921,47235.524],"1451609080000":[5865.2663,394.4091,47288.0243],"1451609090000":[5614.2754,342.0604,47332.3252],"1451609100000":[5368.8786,286.3519,47368.9051],"1451609110000":[5127.9939,229.3493,47398.1795],"1451609120000":[4893.567,172.7458,47420.3617],"1451609130000":[4667.5882,119.0716,47435.7252],"1451609140000":[4443.237,63.9945,47445.2182],"1451609150000":[4221.4387,8.8426,47449.3568],"1451609160000":[4006.1138,-47.9056,47447.9496],"1451609170000":[3797.1979,-110.4784,47441.2014],"1451609180000":[3592.5961,-169.1601,47429.9216],"1451609190000":[3395.981,-225.6409,47414.2226],"1451609200000":[3204.6406,-283.767,47394.8484],"1451609210000":[3016.8832,-344.1675,47372.2112],"1451609220000":[2839.1086,-404.2971,47346.4945],"1451609230000":[2663.2976,-464.2841,47318.6429],"1451609240000":[2496.7522,-524.1598,47288.6529],"1451609250000":[2334.9278,-586.2819,47257.0355],"1451609260000":[2179.8107,-648.7282,47224.1508],"1451609270000":[2028.8988,-713.3616,47190.3647],"1451609280000":[1887.0422,-776.4594,47155.7844],"1451609290000":[1751.8495,-842.6458,47120.8045],"1451609300000":[1623.5138,-909.7449,47085.9799],"1451609310000":[1501.1362,-976.7106,47051.5832],"1451609320000":[1386.4933,-1042.3359,47017.7956],"1451609330000":[1280.1922,-1110.2261,46985.0591],"1451609340000":[1180.3371,-1178.0712,46953.7172],"1451609350000":[1090.5913,-1246.4347,46923.9414],"1451609360000":[1011.2794,-1317.2986,46895.8115],"1451609370000":[944.3251,-1386.8466,46869.3877],"1451609380000":[887.9008,-1457.3806,46844.6718],"1451609390000":[846.4719,-1528.731,46821.4011],"1451609400000":[827.5203,-1598.8378,46799.6377],"1451609410000":[834.0166,-1662.4186,46779.6246],"1451609420000":[879.2809,-1718.5866,46761.2746],"1451609430000":[971.601,-1754.933,46745.2001],"1451609440000":[1122.2114,-1761.2988,46731.6076],"1451609450000":[1341.9835,-1711.0095,46720.6835],"1451609460000":[1626.9148,-1572.3194,46712.8487],"1451609470000":[1954.6926,-1317.96,46707.9834],"1451609480000":[2274.9431,-943.4807,46705.8689],"1451609490000":[2522.2405,-493.8839,46706.9693],"1451609500000":[2678.3748,-49.7261,46711.272],"1451609510000":[2772.733,336.4464,46718.2931],"1451609520000":[2836.5678,653.6744,46727.9856],"1451609530000":[2894.4164,910.1218,46740.0752],"1451609540000":[2958.0094,1120.2389,46754.3726],"1451609550000":[3028.5557,1293.1742,46771.0419],"1451609560000":[3110.6883,1437.6671,46789.6748],"1451609570000":[3205.7996,1564.197,46810.0695],"1451609580000":[3313.2256,1674.0447,46832.2725],"1451609590000":[3432.9291,1772.4698,46855.8327],"1451609600000":[3563.9382,1860.4406,46880.7518],"1451609610000":[3704.9302,1941.5065,46906.3135],"1451609620000":[3856.4425,2019.1155,46931.9735],"1451609630000":[4020.0444,2093.7166,46956.7887],"1451609640000":[4196.6067,2168.7203,46980.0004],"1451609650000":[4397.0757,2240.0737,46999.529],"1451609660000":[4675.0555,1965.9628,47023.9524],"1451609670000":[4876.6359,2090.036,47033.4881],"1451609680000":[4991.1371,2030.4203,47056.2513],"1451609690000":[5274.0046,1992.0039,47055.5644],"1451609700000":[5532.3114,2068.3485,47046.1853],"1451609710000":[5796.8395,2086.0273,47030.3958],"1451609720000":[6052.4348,2074.8953,47006.6694],"1451609730000":[6356.5823,1974.1175,46967.055],"1451609740000":[6645.0677,1934.0503,46911.0327],"1451609750000":[6905.5172,1831.6631,46844.628],"1451609760000":[7073.6484,1757.5812,46777.2926],"1451609770000":[7226.0462,1917.1986,46691.178],"1451609780000":[7416.7905,2058.1509,46590.9942],"1451609790000":[7524.1806,2235.5041,46499.2516],"1451609800000":[7702.1677,2328.7968,46402.4598],"1451609810000":[7970.8301,2307.6007,46300.3195],"1451609820000":[8275.0709,2389.0771,46190.9844],"1451609830000":[8520.9433,2528.4453,46093.2432],"1451609840000":[8653.8484,2503.8916,46032.2094],"1451609850000":[8857.0976,2572.1956,45957.1137],"1451609860000":[9049.1811,2608.5315,45889.036],"1451609870000":[9295.6481,2595.1048,45810.9288],"1451609880000":[9464.1058,2616.0861,45742.3203],"1451609890000":[9739.3102,2621.0336,45649.208],"1451609900000":[10007.8602,2621.7187,45552.3767],"1451609910000":[10269.7251,2638.706,45449.8182],"1451609920000":[10531.9396,2643.2554,45340.9053],"1451609930000":[10792.175,2639.861,45226.1655],"1451609940000":[11064.5007,2634.5472,45101.3698],"1451609950000":[11347.588,2621.5053,44966.4423],"1451609960000":[11642.0347,2609.4927,44820.6353],"1451609970000":[11931.8998,2597.5328,44667.7204],"1451609980000":[12230.1732,2581.3201,44504.0574],"1451609990000":[12532.2113,2564.9601,44330.5303]},"F":{"1451608080000":24630.6298,"1451608090000":24808.1068,"1451608100000":24993.369,"1451608110000":25186.2222,"1451608120000":25386.4754,"1451608130000":25594.0544,"1451608140000":25808.7205,"1451608150000":26030.3246,"1451608160000":26258.6446,"1451608170000":26493.4236,"1451608180000":26734.3125,"1451608190000":26981.0928,"1451608200000":27233.5343,"1451608210000":27491.2577,"1451608220000":27754.04,"1451608230000":28021.7322,"1451608240000":28294.1291,"1451608250000":28570.9327,"1451608260000":28851.9286,"1451608270000":29136.8868,"1451608280000":29425.6756,"1451608290000":29717.9814,"1451608300000":30013.5817,"1451608310000":30312.2222,"1451608320000":30613.7606,"1451608330000":30917.7725,"1451608340000":31224.2407,"1451608350000":31532.8721,"1451608360000":31843.1821,"1451608370000":32155.0085,"1451608380000":32468.5198,"1451608390000":32783.3809,"1451608400000":33099.5634,"1451608410000":33416.7455,"1451608420000":33734.772,"1451608430000":34053.5905,"1451608440000":34372.9684,"1451608450000":34692.6353,"1451608460000":35012.4511,"1451608470000":35332.1968,"1451608480000":35651.0036,"1451608490000":35968.5093,"1451608500000":36285.8592,"1451608510000":36603.3048,"1451608520000":36920.4517,"1451608530000":37236.4002,"1451608540000":37550.7015,"1451608550000":37862.9721,"1451608560000":38172.9085,"1451608570000":38480.4311,"1451608580000":38785.7019,"1451608590000":39088.489,"1451608600000":39388.6287,"1451608610000":39686.0544,"1451608620000":39980.5619,"1451608630000":40271.772,"1451608640000":40559.4598,"1451608650000":40843.6739,"1451608660000":41124.4654,"1451608670000":41401.7882,"1451608680000":41675.4273,"1451608690000":41945.1023,"1451608700000":42210.5968,"1451608710000":42471.7433,"1451608720000":42728.3825,"1451608730000":42980.5018,"1451608740000":43227.9668,"1451608750000":43470.7588,"1451608760000":43708.6105,"1451608770000":43941.1945,"1451608780000":44167.9801,"1451608790000":44388.5544,"1451608800000":44602.1349,"1451608810000":44808.3786,"1451608820000":45006.7503,"1451608830000":45196.2476,"1451608840000":45375.6774,"1451608850000":45544.0235,"1451608860000":45701.9658,"1451608870000":45850.1765,"1451608880000":45990.0877,"1451608890000":46123.1382,"1451608900000":46250.6634,"1451608910000":46374.6029,"1451608920000":46497.4574,"1451608930000":46618.691,"1451608940000":46736.2447,"1451608950000":46849.6724,"1451608960000":46957.1839,"1451608970000":47057.2047,"1451608980000":47148.8028,"1451608990000":47231.917,"1451609000000":47307.0659,"1451609010000":47374.4713,"1451609020000":47434.5611,"1451609030000":47487.6922,"1451609040000":47533.9746,"1451609050000":47573.3963,"1451609060000":47605.9768,"1451609070000":47631.8976,"1451609080000":47651.3917,"1451609090000":47664.7581,"1451609100000":47672.423,"1451609110000":47674.7073,"1451609120000":47671.8885,"1451609130000":47664.3515,"1451609140000":47652.3042,"1451609150000":47636.2235,"1451609160000":47616.2465,"1451609170000":47592.6027,"1451609180000":47565.6553,"1451609190000":47535.8172,"1451609200000":47503.5331,"1451609210000":47469.1189,"1451609220000":47432.9624,"1451609230000":47395.5258,"1451609240000":47357.1631,"1451609250000":47318.1379,"1451609260000":47278.7258,"1451609270000":47239.1832,"1451609280000":47199.7831,"1451609290000":47160.7668,"1451609300000":47122.6259,"1451609310000":47085.4994,"1451609320000":47049.6814,"1451609330000":47015.5074,"1451609340000":46983.238,"1451609350000":46953.1161,"1451609360000":46925.1633,"1451609370000":46899.3653,"1451609380000":46875.6645,"1451609390000":46853.9638,"1451609400000":46834.2236,"1451609410000":46816.5305,"1451609420000":46801.081,"1451609430000":46788.1524,"1451609440000":46778.1636,"1451609450000":46771.1713,"1451609460000":46767.5074,"1451609470000":46767.3134,"1451609480000":46770.6176,"1451609490000":46777.5205,"1451609500000":46787.8841,"1451609510000":46801.58,"1451609520000":46818.3767,"1451609530000":46838.2544,"1451609540000":46861.0402,"1451609550000":46886.6184,"1451609560000":46914.7728,"1451609570000":46945.5368,"1451609580000":46978.9177,"1451609590000":47014.5863,"1451609600000":47052.5113,"1451609610000":47092.1493,"1451609620000":47133.1318,"1451609630000":47174.7864,"1451609640000":47216.5936,"1451609650000":47257.5531,"1451609660000":47296.3362,"1451609670000":47331.5161,"1451609680000":47363.4223,"1451609690000":47391.7117,"1451609700000":47415.1386,"1451609710000":47431.8804,"1451609720000":47439.7646,"1451609730000":47435.9606,"1451609740000":47418.3848,"1451609750000":47385.8761,"1451609760000":47341.3624,"1451609770000":47285.5518,"1451609780000":47222.1106,"1451609790000":47156.6958,"1451609800000":47094.5341,"1451609810000":47037.6319,"1451609820000":46986.794,"1451609830000":46942.0004,"1451609840000":46905.133,"1451609850000":46873.1318,"1451609860000":46845.1251,"1451609870000":46816.0974,"1451609880000":46783.8825,"1451609890000":46749.8147,"1451609900000":46712.0565,"1451609910000":46669.8923,"1451609920000":46622.6658,"1451609930000":46570.5326,"1451609940000":46513.0327,"1451609950000":46449.8151,"1451609960000":46381.079,"1451609970000":46306.4719,"1451609980000":46225.7465,"1451609990000":46138.8951},"Latitude":{"1451608080000":0.1976098,"1451608090000":0.8381574,"1451608100000":1.4787237,"1451608110000":2.1193083,"1451608120000":2.7599111,"1451608130000":3.4005317,"1451608140000":4.0411699,"1451608150000":4.6818255,"1451608160000":5.3224982,"1451608170000":5.9631878,"1451608180000":6.6038939,"1451608190000":7.2446163,"1451608200000":7.8853547,"1451608210000":8.5261088,"1451608220000":9.1668784,"1451608230000":9.8076631,"1451608240000":10.4484627,"1451608250000":11.0892769,"1451608260000":11.7301053,"1451608270000":12.3709477,"1451608280000":13.0118037,"1451608290000":13.652673,"1451608300000":14.2935554,"1451608310000":14.9344503,"1451608320000":15.5753577,"1451608330000":16.2162769,"1451608340000":16.8572078,"1451608350000":17.49815,"1451608360000":18.1391031,"1451608370000":18.7800668,"1451608380000":19.4210406,"1451608390000":20.0620242,"1451608400000":20.7030171,"1451608410000":21.3440191,"1451608420000":21.9850295,"1451608430000":22.6260481,"1451608440000":23.2670744,"1451608450000":23.908108,"1451608460000":24.5491483,"1451608470000":25.1901949,"1451608480000":25.8312474,"1451608490000":26.4723053,"1451608500000":27.113368,"1451608510000":27.754435,"1451608520000":28.3955059,"1451608530000":29.03658,"1451608540000":29.6776568,"1451608550000":30.3187357,"1451608560000":30.9598161,"1451608570000":31.6008974,"1451608580000":32.241979,"1451608590000":32.8830602,"1451608600000":33.5241403,"1451608610000":34.1652187,"1451608620000":34.8062945,"1451608630000":35.4473671,"1451608640000":36.0884356,"1451608650000":36.7294993,"1451608660000":37.3705573,"1451608670000":38.0116087,"1451608680000":38.6526526,"1451608690000":39.293688,"1451608700000":39.934714,"1451608710000":40.5757295,"1451608720000":41.2167334,"1451608730000":41.8577246,"1451608740000":42.4987019,"1451608750000":43.1396641,"1451608760000":43.7806099,"1451608770000":44.4215378,"1451608780000":45.0624465,"1451608790000":45.7033345,"1451608800000":46.3442002,"1451608810000":46.9850419,"1451608820000":47.6258579,"1451608830000":48.2666462,"1451608840000":48.907405,"1451608850000":49.5481322,"1451608860000":50.1888255,"1451608870000":50.8294827,"1451608880000":51.4701013,"1451608890000":52.1106786,"1451608900000":52.7512119,"1451608910000":53.3916982,"1451608920000":54.0321343,"1451608930000":54.6725168,"1451608940000":55.3128422,"1451608950000":55.9531064,"1451608960000":56.5933054,"1451608970000":57.2334346,"1451608980000":57.8734893,"1451608990000":58.5134642,"1451609000000":59.1533537,"1451609010000":59.7931517,"1451609020000":60.4328516,"1451609030000":61.0724463,"1451609040000":61.711928,"1451609050000":62.3512881,"1451609060000":62.9905175,"1451609070000":63.6296058,"1451609080000":64.2685419,"1451609090000":64.9073136,"1451609100000":65.5459073,"1451609110000":66.1843081,"1451609120000":66.8224992,"1451609130000":67.4604622,"1451609140000":68.0981766,"1451609150000":68.7356193,"1451609160000":69.3727644,"1451609170000":70.0095828,"1451609180000":70.6460415,"1451609190000":71.2821032,"1451609200000":71.9177254,"1451609210000":72.5528592,"1451609220000":73.1874487,"1451609230000":73.8214291,"1451609240000":74.4547252,"1451609250000":75.087249,"1451609260000":75.7188974,"1451609270000":76.349548,"1451609280000":76.9790551,"1451609290000":77.6072438,"1451609300000":78.2339022,"1451609310000":78.8587714,"1451609320000":79.4815316,"1451609330000":80.1017842,"1451609340000":80.7190256,"1451609350000":81.3326117,"1451609360000":81.9417067,"1451609370000":82.545209,"1451609380000":83.1416412,"1451609390000":83.7289849,"1451609400000":84.3044269,"1451609410000":84.8639645,"1451609420000":85.4017872,"1451609430000":85.9093203,"1451609440000":86.3738222,"1451609450000":86.7766352,"1451609460000":87.0920092,"1451609470000":87.2892515,"1451609480000":87.341932,"1451609490000":87.2417551,"1451609500000":87.0040147,"1451609510000":86.6579401,"1451609520000":86.2332742,"1451609530000":85.7535304,"1451609540000":85.235317,"1451609550000":84.6898852,"1451609560000":84.1248108,"1451609570000":83.5452511,"1451609580000":82.9547802,"1451609590000":82.3559267,"1451609600000":81.7505164,"1451609610000":81.1398936,"1451609620000":80.5250667,"1451609630000":79.9068042,"1451609640000":79.2857013,"1451609650000":78.6622255,"1451609660000":78.0367481,"1451609670000":77.4095679,"1451609680000":76.7809279,"1451609690000":76.1510272,"1451609700000":75.520031,"1451609710000":74.888077,"1451609720000":74.2552811,"1451609730000":73.6217413,"1451609740000":72.9875413,"1451609750000":72.3527528,"1451609760000":71.7174376,"1451609770000":71.0816494,"1451609780000":70.4454347,"1451609790000":69.8088346,"1451609800000":69.1718848,"1451609810000":68.534617,"1451609820000":67.8970594,"1451609830000":67.2592368,"1451609840000":66.6211714,"1451609850000":65.9828833,"1451609860000":65.3443902,"1451609870000":64.7057082,"1451609880000":64.066852,"1451609890000":63.4278345,"1451609900000":62.7886678,"1451609910000":62.1493627,"1451609920000":61.5099292,"1451609930000":60.8703761,"1451609940000":60.2307119,"1451609950000":59.5909441,"1451609960000":58.9510798,"1451609970000":58.3111252,"1451609980000":57.6710866,"1451609990000":57.0309692},"Longitude":{"1451608080000":-102.6818411,"1451608090000":-102.6939464,"1451608100000":-102.7060411,"1451608110000":-102.7181177,"1451608120000":-102.7301688,"1451608130000":-102.7421868,"1451608140000":-102.7541643,"1451608150000":-102.7660937,"1451608160000":-102.7779673,"1451608170000":-102.7897776,"1451608180000":-102.8015168,"1451608190000":-102.813177,"1451608200000":-102.8247504,"1451608210000":-102.8362291,"1451608220000":-102.8476049,"1451608230000":-102.8588697,"1451608240000":-102.8700152,"1451608250000":-102.8810329,"1451608260000":-102.8919143,"1451608270000":-102.9026508,"1451608280000":-102.9132335,"1451608290000":-102.9236533,"1451608300000":-102.933901,"1451608310000":-102.9439674,"1451608320000":-102.9538427,"1451608330000":-102.9635173,"1451608340000":-102.9729811,"1451608350000":-102.9822238,"1451608360000":-102.9912349,"1451608370000":-103.0000038,"1451608380000":-103.0085192,"1451608390000":-103.01677,"1451608400000":-103.0247443,"1451608410000":-103.0324303,"1451608420000":-103.0398156,"1451608430000":-103.0468874,"1451608440000":-103.0536327,"1451608450000":-103.060038,"1451608460000":-103.0660892,"1451608470000":-103.0717721,"1451608480000":-103.0770717,"1451608490000":-103.0819726,"1451608500000":-103.0864588,"1451608510000":-103.0905139,"1451608520000":-103.0941207,"1451608530000":-103.0972615,"1451608540000":-103.0999179,"1451608550000":-103.1020707,"1451608560000":-103.1037001,"1451608570000":-103.1047853,"1451608580000":-103.1053049,"1451608590000":-103.1052365,"1451608600000":-103.1045567,"1451608610000":-103.1032413,"1451608620000":-103.1012647,"1451608630000":-103.0986006,"1451608640000":-103.0952212,"1451608650000":-103.0910975,"1451608660000":-103.0861993,"1451608670000":-103.0804949,"1451608680000":-103.073951,"1451608690000":-103.0665328,"1451608700000":-103.0582037,"1451608710000":-103.0489254,"1451608720000":-103.0386576,"1451608730000":-103.0273578,"1451608740000":-103.0149814,"1451608750000":-103.0014815,"1451608760000":-102.9868084,"1451608770000":-102.97091,"1451608780000":-102.9537311,"1451608790000":-102.9352135,"1451608800000":-102.9152954,"1451608810000":-102.8939116,"1451608820000":-102.8709931,"1451608830000":-102.8464665,"1451608840000":-102.820254,"1451608850000":-102.7922731,"1451608860000":-102.7624358,"1451608870000":-102.7306486,"1451608880000":-102.6968118,"1451608890000":-102.6608191,"1451608900000":-102.6225567,"1451608910000":-102.5819034,"1451608920000":-102.5387289,"1451608930000":-102.4928941,"1451608940000":-102.4442494,"1451608950000":-102.3926339,"1451608960000":-102.337875,"1451608970000":-102.2797861,"1451608980000":-102.2181664,"1451608990000":-102.1527985,"1451609000000":-102.0834473,"1451609010000":-102.0098579,"1451609020000":-101.9317535,"1451609030000":-101.8488331,"1451609040000":-101.7607685,"1451609050000":-101.6672014,"1451609060000":-101.5677401,"1451609070000":-101.4619549,"1451609080000":-101.3493739,"1451609090000":-101.2294775,"1451609100000":-101.1016924,"1451609110000":-100.9653841,"1451609120000":-100.8198491,"1451609130000":-100.6643048,"1451609140000":-100.4978783,"1451609150000":-100.3195934,"1451609160000":-100.1283541,"1451609170000":-99.9229264,"1451609180000":-99.7019162,"1451609190000":-99.4637418,"1451609200000":-99.2066023,"1451609210000":-98.928438,"1451609220000":-98.6268831,"1451609230000":-98.2992066,"1451609240000":-97.9422405,"1451609250000":-97.5522891,"1451609260000":-97.125016,"1451609270000":-96.6553012,"1451609280000":-96.1370585,"1451609290000":-95.5629999,"1451609300000":-94.9243302,"1451609310000":-94.2103451,"1451609320000":-93.4078967,"1451609330000":-92.5006755,"1451609340000":-91.4682334,"1451609350000":-90.2846367,"1451609360000":-88.9165881,"1451609370000":-87.3207743,"1451609380000":-85.440085,"1451609390000":-83.1981871,"1451609400000":-80.4917588,"1451609410000":-77.1795803,"1451609420000":-73.0680795,"1451609430000":-67.8951244,"1451609440000":-61.3213981,"1451609450000":-52.9588384,"1451609460000":-42.5020115,"1451609470000":-30.0322849,"1451609480000":-16.3652148,"1451609490000":-2.9385079,"1451609500000":8.9464356,"1451609510000":18.7267995,"1451609520000":26.4852865,"1451609530000":32.5773643,"1451609540000":37.3833182,"1451609550000":41.2185659,"1451609560000":44.3220472,"1451609570000":46.8691128,"1451609580000":48.9876622,"1451609590000":50.7715119,"1451609600000":52.2902347,"1451609610000":53.5961042,"1451609620000":54.728936,"1451609630000":55.7194666,"1451609640000":56.5917301,"1451609650000":57.364747,"1451609660000":58.0537406,"1451609670000":58.6710224,"1451609680000":59.2266455,"1451609690000":59.7288925,"1451609700000":60.184644,"1451609710000":60.5996597,"1451609720000":60.9787952,"1451609730000":61.3261716,"1451609740000":61.6453074,"1451609750000":61.9392245,"1451609760000":62.2105316,"1451609770000":62.4614925,"1451609780000":62.6940808,"1451609790000":62.9100245,"1451609800000":63.110843,"1451609810000":63.2978776,"1451609820000":63.4723164,"1451609830000":63.6352158,"1451609840000":63.7875181,"1451609850000":63.9300663,"1451609860000":64.0636171,"1451609870000":64.1888512,"1451609880000":64.306383,"1451609890000":64.4167681,"1451609900000":64.5205107,"1451609910000":64.6180688,"1451609920000":64.7098595,"1451609930000":64.7962638,"1451609940000":64.8776301,"1451609950000":64.9542776,"1451609960000":65.0264996,"1451609970000":65.0945657,"1451609980000":65.1587245,"1451609990000":65.2192051},"Radius":{"1451608080000":6828390.6100000003,"1451608090000":6828288.8700000001,"1451608100000":6828186.3200000003,"1451608110000":6828082.9699999997,"1451608120000":6827978.8300000001,"1451608130000":6827873.9199999999,"1451608140000":6827768.2400000002,"1451608150000":6827661.8200000003,"1451608160000":6827554.6699999999,"1451608170000":6827446.7999999998,"1451608180000":6827338.2400000002,"1451608190000":6827228.9900000002,"1451608200000":6827119.0700000003,"1451608210000":6827008.5099999998,"1451608220000":6826897.3100000005,"1451608230000":6826785.5099999998,"1451608240000":6826673.1000000006,"1451608250000":6826560.1200000001,"1451608260000":6826446.5800000001,"1451608270000":6826332.5,"1451608280000":6826217.9100000001,"1451608290000":6826102.8100000005,"1451608300000":6825987.2400000002,"1451608310000":6825871.21,"1451608320000":6825754.7400000002,"1451608330000":6825637.8600000003,"1451608340000":6825520.5899999999,"1451608350000":6825402.9400000004,"1451608360000":6825284.9400000004,"1451608370000":6825166.6200000001,"1451608380000":6825047.9900000002,"1451608390000":6824929.0899999999,"1451608400000":6824809.9199999999,"1451608410000":6824690.5200000005,"1451608420000":6824570.9100000001,"1451608430000":6824451.1200000001,"1451608440000":6824331.1600000001,"1451608450000":6824211.0700000003,"1451608460000":6824090.8700000001,"1451608470000":6823970.5800000001,"1451608480000":6823850.2300000004,"1451608490000":6823729.8500000006,"1451608500000":6823609.46,"1451608510000":6823489.1000000006,"1451608520000":6823368.7800000003,"1451608530000":6823248.54,"1451608540000":6823128.3900000006,"1451608550000":6823008.3700000001,"1451608560000":6822888.5099999998,"1451608570000":6822768.8300000001,"1451608580000":6822649.3600000003,"1451608590000":6822530.1299999999,"1451608600000":6822411.1600000001,"1451608610000":6822292.4800000004,"1451608620000":6822174.1200000001,"1451608630000":6822056.1100000003,"1451608640000":6821938.4699999997,"1451608650000":6821821.2300000004,"1451608660000":6821704.4199999999,"1451608670000":6821588.0700000003,"1451608680000":6821472.2000000002,"1451608690000":6821356.8399999999,"1451608700000":6821242.0200000005,"1451608710000":6821127.7599999998,"1451608720000":6821014.1000000006,"1451608730000":6820901.0600000005,"1451608740000":6820788.6699999999,"1451608750000":6820676.96,"1451608760000":6820565.9500000002,"1451608770000":6820455.6699999999,"1451608780000":6820346.1500000004,"1451608790000":6820237.4100000001,"1451608800000":6820129.4900000002,"1451608810000":6820022.4100000001,"1451608820000":6819916.2000000002,"1451608830000":6819810.8799999999,"1451608840000":6819706.4800000004,"1451608850000":6819603.04,"1451608860000":6819500.5700000003,"1451608870000":6819399.1100000003,"1451608880000":6819298.6799999997,"1451608890000":6819199.3100000005,"1451608900000":6819101.0200000005,"1451608910000":6819003.8399999999,"1451608920000":6818907.8100000005,"1451608930000":6818812.9299999997,"1451608940000":6818719.2400000002,"1451608950000":6818626.7700000005,"1451608960000":6818535.54,"1451608970000":6818445.5800000001,"1451608980000":6818356.9000000004,"1451608990000":6818269.54,"1451609000000":6818183.5200000005,"1451609010000":6818098.8600000003,"1451609020000":6818015.5899999999,"1451609030000":6817933.7300000004,"1451609040000":6817853.2999999998,"1451609050000":6817774.3200000003,"1451609060000":6817696.8100000005,"1451609070000":6817620.8100000005,"1451609080000":6817546.3100000005,"1451609090000":6817473.3600000003,"1451609100000":6817401.96,"1451609110000":6817332.1400000006,"1451609120000":6817263.9000000004,"1451609130000":6817197.2800000003,"1451609140000":6817132.2999999998,"1451609150000":6817068.9500000002,"1451609160000":6817007.2700000005,"1451609170000":6816947.2700000005,"1451609180000":6816888.96,"1451609190000":6816832.3700000001,"1451609200000":6816777.4900000002,"1451609210000":6816724.3600000003,"1451609220000":6816672.9699999997,"1451609230000":6816623.3500000006,"1451609240000":6816575.5099999998,"1451609250000":6816529.4500000002,"1451609260000":6816485.2000000002,"1451609270000":6816442.75,"1451609280000":6816402.1299999999,"1451609290000":6816363.3399999999,"1451609300000":6816326.4000000004,"1451609310000":6816291.2999999998,"1451609320000":6816258.0700000003,"1451609330000":6816226.7000000002,"1451609340000":6816197.21,"1451609350000":6816169.6000000006,"1451609360000":6816143.8799999999,"1451609370000":6816120.0600000005,"1451609380000":6816098.1400000006,"1451609390000":6816078.1299999999,"1451609400000":6816060.0300000003,"1451609410000":6816043.8500000006,"1451609420000":6816029.5800000001,"1451609430000":6816017.2400000002,"1451609440000":6816006.8300000001,"1451609450000":6815998.3500000006,"1451609460000":6815991.7999999998,"1451609470000":6815987.1799999997,"1451609480000":6815984.4900000002,"1451609490000":6815983.7400000002,"1451609500000":6815984.9299999997,"1451609510000":6815988.04,"1451609520000":6815993.0899999999,"1451609530000":6816000.0700000003,"1451609540000":6816008.9800000004,"1451609550000":6816019.8200000003,"1451609560000":6816032.5800000001,"1451609570000":6816047.25,"1451609580000":6816063.8399999999,"1451609590000":6816082.3399999999,"1451609600000":6816102.75,"1451609610000":6816125.0600000005,"1451609620000":6816149.2599999998,"1451609630000":6816175.3500000006,"1451609640000":6816203.3200000003,"1451609650000":6816233.1600000001,"1451609660000":6816264.8700000001,"1451609670000":6816298.4400000004,"1451609680000":6816333.8600000003,"1451609690000":6816371.1100000003,"1451609700000":6816410.1900000004,"1451609710000":6816451.0899999999,"1451609720000":6816493.7999999998,"1451609730000":6816538.2999999998,"1451609740000":6816584.5800000001,"1451609750000":6816632.6200000001,"1451609760000":6816682.4299999997,"1451609770000":6816733.9699999997,"1451609780000":6816787.2400000002,"1451609790000":6816842.2199999997,"1451609800000":6816898.9000000004,"1451609810000":6816957.25,"1451609820000":6817017.2700000005,"1451609830000":6817078.9299999997,"1451609840000":6817142.2199999997,"1451609850000":6817207.1200000001,"1451609860000":6817273.6100000003,"1451609870000":6817341.6699999999,"1451609880000":6817411.29,"1451609890000":6817482.4400000004,"1451609900000":6817555.1100000003,"1451609910000":6817629.2700000005,"1451609920000":6817704.9000000004,"1451609930000":6817782.0,"1451609940000":6817860.5200000005,"1451609950000":6817940.4500000002,"1451609960000":6818021.7800000003,"1451609970000":6818104.4699999997,"1451609980000":6818188.5099999998,"1451609990000":6818273.8700000001},"Spacecraft":{"1451608080000":"A","1451608090000":"A","1451608100000":"A","1451608110000":"A","1451608120000":"A","1451608130000":"A","1451608140000":"A","1451608150000":"A","1451608160000":"A","1451608170000":"A","1451608180000":"A","1451608190000":"A","1451608200000":"A","1451608210000":"A","1451608220000":"A","1451608230000":"A","1451608240000":"A","1451608250000":"A","1451608260000":"A","1451608270000":"A","1451608280000":"A","1451608290000":"A","1451608300000":"A","1451608310000":"A","1451608320000":"A","1451608330000":"A","1451608340000":"A","1451608350000":"A","1451608360000":"A","1451608370000":"A","1451608380000":"A","1451608390000":"A","1451608400000":"A","1451608410000":"A","1451608420000":"A","1451608430000":"A","1451608440000":"A","1451608450000":"A","1451608460000":"A","1451608470000":"A","1451608480000":"A","1451608490000":"A","1451608500000":"A","1451608510000":"A","1451608520000":"A","1451608530000":"A","1451608540000":"A","1451608550000":"A","1451608560000":"A","1451608570000":"A","1451608580000":"A","1451608590000":"A","1451608600000":"A","1451608610000":"A","1451608620000":"A","1451608630000":"A","1451608640000":"A","1451608650000":"A","1451608660000":"A","1451608670000":"A","1451608680000":"A","1451608690000":"A","1451608700000":"A","1451608710000":"A","1451608720000":"A","1451608730000":"A","1451608740000":"A","1451608750000":"A","1451608760000":"A","1451608770000":"A","1451608780000":"A","1451608790000":"A","1451608800000":"A","1451608810000":"A","1451608820000":"A","1451608830000":"A","1451608840000":"A","1451608850000":"A","1451608860000":"A","1451608870000":"A","1451608880000":"A","1451608890000":"A","1451608900000":"A","1451608910000":"A","1451608920000":"A","1451608930000":"A","1451608940000":"A","1451608950000":"A","1451608960000":"A","1451608970000":"A","1451608980000":"A","1451608990000":"A","1451609000000":"A","1451609010000":"A","1451609020000":"A","1451609030000":"A","1451609040000":"A","1451609050000":"A","1451609060000":"A","1451609070000":"A","1451609080000":"A","1451609090000":"A","1451609100000":"A","1451609110000":"A","1451609120000":"A","1451609130000":"A","1451609140000":"A","1451609150000":"A","1451609160000":"A","1451609170000":"A","1451609180000":"A","1451609190000":"A","1451609200000":"A","1451609210000":"A","1451609220000":"A","1451609230000":"A","1451609240000":"A","1451609250000":"A","1451609260000":"A","1451609270000":"A","1451609280000":"A","1451609290000":"A","1451609300000":"A","1451609310000":"A","1451609320000":"A","1451609330000":"A","1451609340000":"A","1451609350000":"A","1451609360000":"A","1451609370000":"A","1451609380000":"A","1451609390000":"A","1451609400000":"A","1451609410000":"A","1451609420000":"A","1451609430000":"A","1451609440000":"A","1451609450000":"A","1451609460000":"A","1451609470000":"A","1451609480000":"A","1451609490000":"A","1451609500000":"A","1451609510000":"A","1451609520000":"A","1451609530000":"A","1451609540000":"A","1451609550000":"A","1451609560000":"A","1451609570000":"A","1451609580000":"A","1451609590000":"A","1451609600000":"A","1451609610000":"A","1451609620000":"A","1451609630000":"A","1451609640000":"A","1451609650000":"A","1451609660000":"A","1451609670000":"A","1451609680000":"A","1451609690000":"A","1451609700000":"A","1451609710000":"A","1451609720000":"A","1451609730000":"A","1451609740000":"A","1451609750000":"A","1451609760000":"A","1451609770000":"A","1451609780000":"A","1451609790000":"A","1451609800000":"A","1451609810000":"A","1451609820000":"A","1451609830000":"A","1451609840000":"A","1451609850000":"A","1451609860000":"A","1451609870000":"A","1451609880000":"A","1451609890000":"A","1451609900000":"A","1451609910000":"A","1451609920000":"A","1451609930000":"A","1451609940000":"A","1451609950000":"A","1451609960000":"A","1451609970000":"A","1451609980000":"A","1451609990000":"A"}}
\ No newline at end of file
+{"B_NEC":{"1451608080000":[23432.0698,2929.1195,7001.0706],"1451608090000":[23450.9161,2900.0733,7554.7711],"1451608100000":[23465.5579,2872.2875,8110.0848],"1451608110000":[23476.0961,2844.6195,8666.9527],"1451608120000":[23482.2611,2817.4788,9225.4296],"1451608130000":[23483.7305,2791.4418,9785.9031],"1451608140000":[23480.7606,2765.0694,10348.1705],"1451608150000":[23473.3483,2738.8811,10911.7339],"1451608160000":[23461.8746,2714.0217,11475.2695],"1451608170000":[23445.3569,2690.6571,12040.0636],"1451608180000":[23424.171,2668.0164,12605.5356],"1451608190000":[23398.2049,2646.3827,13171.4466],"1451608200000":[23367.905,2623.5412,13737.0976],"1451608210000":[23332.4142,2601.8444,14303.242],"1451608220000":[23292.1384,2580.2121,14869.1609],"1451608230000":[23247.1733,2559.3295,15434.4944],"1451608240000":[23197.443,2540.0891,15999.0812],"1451608250000":[23143.0971,2519.5869,16562.9784],"1451608260000":[23083.8496,2499.6672,17126.3037],"1451608270000":[23020.0172,2481.1638,17688.1775],"1451608280000":[22951.0829,2463.3073,18249.3113],"1451608290000":[22877.5153,2446.2538,18809.1623],"1451608300000":[22799.7383,2430.1555,19366.7738],"1451608310000":[22717.0494,2414.553,19923.0709],"1451608320000":[22629.961,2397.7601,20477.4945],"1451608330000":[22538.543,2383.0698,21029.418],"1451608340000":[22442.1229,2368.7208,21579.8326],"1451608350000":[22341.15,2354.9218,22127.9093],"1451608360000":[22235.8724,2341.3574,22673.0688],"1451608370000":[22126.1144,2328.2601,23215.3879],"1451608380000":[22011.7563,2315.2349,23755.4868],"1451608390000":[21893.4965,2302.9224,24292.371],"1451608400000":[21771.2944,2290.4303,24826.2958],"1451608410000":[21645.2087,2278.8584,25356.84],"1451608420000":[21514.9309,2267.1869,25884.482],"1451608430000":[21381.2396,2256.9781,26408.338],"1451608440000":[21243.3124,2247.3755,26929.1382],"1451608450000":[21100.765,2237.0813,27447.0339],"1451608460000":[20954.6382,2228.0043,27961.1089],"1451608470000":[20804.8938,2218.2377,28471.2215],"1451608480000":[20650.194,2207.0246,28977.6479],"1451608490000":[20489.9142,2196.6924,29480.2737],"1451608500000":[20325.8407,2187.6858,29979.2208],"1451608510000":[20159.6999,2179.2673,30473.8893],"1451608520000":[19991.2017,2170.9005,30964.1746],"1451608530000":[19818.5676,2160.6222,31450.4361],"1451608540000":[19641.5361,2150.0191,31932.2093],"1451608550000":[19459.5343,2139.066,32409.5796],"1451608560000":[19272.6095,2128.2777,32882.1531],"1451608570000":[19080.6077,2117.5848,33349.9103],"1451608580000":[18885.2364,2107.0966,33812.2502],"1451608590000":[18686.7238,2095.1847,34268.8645],"1451608600000":[18484.1435,2082.6037,34720.2676],"1451608610000":[18278.4234,2070.2655,35165.753],"1451608620000":[18069.4894,2057.4281,35605.3007],"1451608630000":[17856.9885,2041.7874,36039.0],"1451608640000":[17639.977,2025.3822,36466.9087],"1451608650000":[17419.1825,2008.7774,36888.8158],"1451608660000":[17194.333,1992.3104,37304.748],"1451608670000":[16966.3469,1976.8922,37714.512],"1451608680000":[16735.5632,1960.5121,38117.6946],"1451608690000":[16502.0811,1941.3664,38514.1975],"1451608700000":[16265.6321,1920.6402,38903.8763],"1451608710000":[16024.7346,1898.5381,39287.3186],"1451608720000":[15780.3965,1874.1972,39663.8883],"1451608730000":[15532.3029,1849.5028,40033.6245],"1451608740000":[15279.5263,1825.4945,40396.8295],"1451608750000":[15023.6927,1801.3419,40752.8871],"1451608760000":[14763.9563,1777.93,41101.7729],"1451608770000":[14501.2447,1753.5304,41442.8961],"1451608780000":[14236.4817,1722.2657,41775.8023],"1451608790000":[13971.3653,1681.3367,42099.5071],"1451608800000":[13696.9616,1646.0606,42415.5735],"1451608810000":[13417.4511,1607.6536,42722.709],"1451608820000":[13128.7717,1571.7185,43021.1606],"1451608830000":[12786.5826,1630.1635,43319.6849],"1451608840000":[12403.1062,1785.1025,43611.6349],"1451608850000":[12119.7286,1741.0039,43867.8131],"1451608860000":[11804.3785,1779.5489,44115.8091],"1451608870000":[11512.291,1803.2467,44345.2827],"1451608880000":[11186.3861,1850.0925,44571.0204],"1451608890000":[10963.0587,1705.2131,44769.3456],"1451608900000":[10650.3643,1746.5147,44974.3039],"1451608910000":[10363.7252,1707.6477,45170.0057],"1451608920000":[10138.5062,1465.5675,45355.5124],"1451608930000":[9908.6233,1266.5303,45536.3933],"1451608940000":[9686.9224,1096.6243,45708.7241],"1451608950000":[9412.8174,1034.6506,45883.2184],"1451608960000":[9142.3121,1020.49,46047.8374],"1451608970000":[8856.1516,973.819,46206.5991],"1451608980000":[8569.8373,923.057,46354.794],"1451608990000":[8288.9173,870.1733,46491.289],"1451609000000":[8007.3192,815.844,46617.8809],"1451609010000":[7728.4071,760.4939,46734.1824],"1451609020000":[7451.9183,705.5425,46840.7487],"1451609030000":[7178.7142,656.4439,46937.9094],"1451609040000":[6906.7896,604.5927,47026.185],"1451609050000":[6639.0914,555.1535,47105.1767],"1451609060000":[6378.0966,504.222,47174.6472],"1451609070000":[6120.2416,449.7921,47235.524],"1451609080000":[5865.2663,394.4091,47288.0243],"1451609090000":[5614.2754,342.0604,47332.3252],"1451609100000":[5368.8786,286.3519,47368.9051],"1451609110000":[5127.9939,229.3493,47398.1795],"1451609120000":[4893.567,172.7458,47420.3617],"1451609130000":[4667.5882,119.0716,47435.7252],"1451609140000":[4443.237,63.9945,47445.2182],"1451609150000":[4221.4387,8.8426,47449.3568],"1451609160000":[4006.1138,-47.9056,47447.9496],"1451609170000":[3797.1979,-110.4784,47441.2014],"1451609180000":[3592.5961,-169.1601,47429.9216],"1451609190000":[3395.981,-225.6409,47414.2226],"1451609200000":[3204.6406,-283.767,47394.8484],"1451609210000":[3016.8832,-344.1675,47372.2112],"1451609220000":[2839.1086,-404.2971,47346.4945],"1451609230000":[2663.2976,-464.2841,47318.6429],"1451609240000":[2496.7522,-524.1598,47288.6529],"1451609250000":[2334.9278,-586.2819,47257.0355],"1451609260000":[2179.8107,-648.7282,47224.1508],"1451609270000":[2028.8988,-713.3616,47190.3647],"1451609280000":[1887.0422,-776.4594,47155.7844],"1451609290000":[1751.8495,-842.6458,47120.8045],"1451609300000":[1623.5138,-909.7449,47085.9799],"1451609310000":[1501.1362,-976.7106,47051.5832],"1451609320000":[1386.4933,-1042.3359,47017.7956],"1451609330000":[1280.1922,-1110.2261,46985.0591],"1451609340000":[1180.3371,-1178.0712,46953.7172],"1451609350000":[1090.5913,-1246.4347,46923.9414],"1451609360000":[1011.2794,-1317.2986,46895.8115],"1451609370000":[944.3251,-1386.8466,46869.3877],"1451609380000":[887.9008,-1457.3806,46844.6718],"1451609390000":[846.4719,-1528.731,46821.4011],"1451609400000":[827.5203,-1598.8378,46799.6377],"1451609410000":[834.0166,-1662.4186,46779.6246],"1451609420000":[879.2809,-1718.5866,46761.2746],"1451609430000":[971.601,-1754.933,46745.2001],"1451609440000":[1122.2114,-1761.2988,46731.6076],"1451609450000":[1341.9835,-1711.0095,46720.6835],"1451609460000":[1626.9148,-1572.3194,46712.8487],"1451609470000":[1954.6926,-1317.96,46707.9834],"1451609480000":[2274.9431,-943.4807,46705.8689],"1451609490000":[2522.2405,-493.8839,46706.9693],"1451609500000":[2678.3748,-49.7261,46711.272],"1451609510000":[2772.733,336.4464,46718.2931],"1451609520000":[2836.5678,653.6744,46727.9856],"1451609530000":[2894.4164,910.1218,46740.0752],"1451609540000":[2958.0094,1120.2389,46754.3726],"1451609550000":[3028.5557,1293.1742,46771.0419],"1451609560000":[3110.6883,1437.6671,46789.6748],"1451609570000":[3205.7996,1564.197,46810.0695],"1451609580000":[3313.2256,1674.0447,46832.2725],"1451609590000":[3432.9291,1772.4698,46855.8327],"1451609600000":[3563.9382,1860.4406,46880.7518],"1451609610000":[3704.9302,1941.5065,46906.3135],"1451609620000":[3856.4425,2019.1155,46931.9735],"1451609630000":[4020.0444,2093.7166,46956.7887],"1451609640000":[4196.6067,2168.7203,46980.0004],"1451609650000":[4397.0757,2240.0737,46999.529],"1451609660000":[4675.0555,1965.9628,47023.9524],"1451609670000":[4876.6359,2090.036,47033.4881],"1451609680000":[4991.1371,2030.4203,47056.2513],"1451609690000":[5274.0046,1992.0039,47055.5644],"1451609700000":[5532.3114,2068.3485,47046.1853],"1451609710000":[5796.8395,2086.0273,47030.3958],"1451609720000":[6052.4348,2074.8953,47006.6694],"1451609730000":[6356.5823,1974.1175,46967.055],"1451609740000":[6645.0677,1934.0503,46911.0327],"1451609750000":[6905.5172,1831.6631,46844.628],"1451609760000":[7073.6484,1757.5812,46777.2926],"1451609770000":[7226.0462,1917.1986,46691.178],"1451609780000":[7416.7905,2058.1509,46590.9942],"1451609790000":[7524.1806,2235.5041,46499.2516],"1451609800000":[7702.1677,2328.7968,46402.4598],"1451609810000":[7970.8301,2307.6007,46300.3195],"1451609820000":[8275.0709,2389.0771,46190.9844],"1451609830000":[8520.9433,2528.4453,46093.2432],"1451609840000":[8653.8484,2503.8916,46032.2094],"1451609850000":[8857.0976,2572.1956,45957.1137],"1451609860000":[9049.1811,2608.5315,45889.036],"1451609870000":[9295.6481,2595.1048,45810.9288],"1451609880000":[9464.1058,2616.0861,45742.3203],"1451609890000":[9739.3102,2621.0336,45649.208],"1451609900000":[10007.8602,2621.7187,45552.3767],"1451609910000":[10269.7251,2638.706,45449.8182],"1451609920000":[10531.9396,2643.2554,45340.9053],"1451609930000":[10792.175,2639.861,45226.1655],"1451609940000":[11064.5007,2634.5472,45101.3698],"1451609950000":[11347.588,2621.5053,44966.4423],"1451609960000":[11642.0347,2609.4927,44820.6353],"1451609970000":[11931.8998,2597.5328,44667.7204],"1451609980000":[12230.1732,2581.3201,44504.0574],"1451609990000":[12532.2113,2564.9601,44330.5303]},"F":{"1451608080000":24630.6298,"1451608090000":24808.1068,"1451608100000":24993.369,"1451608110000":25186.2222,"1451608120000":25386.4754,"1451608130000":25594.0544,"1451608140000":25808.7205,"1451608150000":26030.3246,"1451608160000":26258.6446,"1451608170000":26493.4236,"1451608180000":26734.3125,"1451608190000":26981.0928,"1451608200000":27233.5343,"1451608210000":27491.2577,"1451608220000":27754.04,"1451608230000":28021.7322,"1451608240000":28294.1291,"1451608250000":28570.9327,"1451608260000":28851.9286,"1451608270000":29136.8868,"1451608280000":29425.6756,"1451608290000":29717.9814,"1451608300000":30013.5817,"1451608310000":30312.2222,"1451608320000":30613.7606,"1451608330000":30917.7725,"1451608340000":31224.2407,"1451608350000":31532.8721,"1451608360000":31843.1821,"1451608370000":32155.0085,"1451608380000":32468.5198,"1451608390000":32783.3809,"1451608400000":33099.5634,"1451608410000":33416.7455,"1451608420000":33734.772,"1451608430000":34053.5905,"1451608440000":34372.9684,"1451608450000":34692.6353,"1451608460000":35012.4511,"1451608470000":35332.1968,"1451608480000":35651.0036,"1451608490000":35968.5093,"1451608500000":36285.8592,"1451608510000":36603.3048,"1451608520000":36920.4517,"1451608530000":37236.4002,"1451608540000":37550.7015,"1451608550000":37862.9721,"1451608560000":38172.9085,"1451608570000":38480.4311,"1451608580000":38785.7019,"1451608590000":39088.489,"1451608600000":39388.6287,"1451608610000":39686.0544,"1451608620000":39980.5619,"1451608630000":40271.772,"1451608640000":40559.4598,"1451608650000":40843.6739,"1451608660000":41124.4654,"1451608670000":41401.7882,"1451608680000":41675.4273,"1451608690000":41945.1023,"1451608700000":42210.5968,"1451608710000":42471.7433,"1451608720000":42728.3825,"1451608730000":42980.5018,"1451608740000":43227.9668,"1451608750000":43470.7588,"1451608760000":43708.6105,"1451608770000":43941.1945,"1451608780000":44167.9801,"1451608790000":44388.5544,"1451608800000":44602.1349,"1451608810000":44808.3786,"1451608820000":45006.7503,"1451608830000":45196.2476,"1451608840000":45375.6774,"1451608850000":45544.0235,"1451608860000":45701.9658,"1451608870000":45850.1765,"1451608880000":45990.0877,"1451608890000":46123.1382,"1451608900000":46250.6634,"1451608910000":46374.6029,"1451608920000":46497.4574,"1451608930000":46618.691,"1451608940000":46736.2447,"1451608950000":46849.6724,"1451608960000":46957.1839,"1451608970000":47057.2047,"1451608980000":47148.8028,"1451608990000":47231.917,"1451609000000":47307.0659,"1451609010000":47374.4713,"1451609020000":47434.5611,"1451609030000":47487.6922,"1451609040000":47533.9746,"1451609050000":47573.3963,"1451609060000":47605.9768,"1451609070000":47631.8976,"1451609080000":47651.3917,"1451609090000":47664.7581,"1451609100000":47672.423,"1451609110000":47674.7073,"1451609120000":47671.8885,"1451609130000":47664.3515,"1451609140000":47652.3042,"1451609150000":47636.2235,"1451609160000":47616.2465,"1451609170000":47592.6027,"1451609180000":47565.6553,"1451609190000":47535.8172,"1451609200000":47503.5331,"1451609210000":47469.1189,"1451609220000":47432.9624,"1451609230000":47395.5258,"1451609240000":47357.1631,"1451609250000":47318.1379,"1451609260000":47278.7258,"1451609270000":47239.1832,"1451609280000":47199.7831,"1451609290000":47160.7668,"1451609300000":47122.6259,"1451609310000":47085.4994,"1451609320000":47049.6814,"1451609330000":47015.5074,"1451609340000":46983.238,"1451609350000":46953.1161,"1451609360000":46925.1633,"1451609370000":46899.3653,"1451609380000":46875.6645,"1451609390000":46853.9638,"1451609400000":46834.2236,"1451609410000":46816.5305,"1451609420000":46801.081,"1451609430000":46788.1524,"1451609440000":46778.1636,"1451609450000":46771.1713,"1451609460000":46767.5074,"1451609470000":46767.3134,"1451609480000":46770.6176,"1451609490000":46777.5205,"1451609500000":46787.8841,"1451609510000":46801.58,"1451609520000":46818.3767,"1451609530000":46838.2544,"1451609540000":46861.0402,"1451609550000":46886.6184,"1451609560000":46914.7728,"1451609570000":46945.5368,"1451609580000":46978.9177,"1451609590000":47014.5863,"1451609600000":47052.5113,"1451609610000":47092.1493,"1451609620000":47133.1318,"1451609630000":47174.7864,"1451609640000":47216.5936,"1451609650000":47257.5531,"1451609660000":47296.3362,"1451609670000":47331.5161,"1451609680000":47363.4223,"1451609690000":47391.7117,"1451609700000":47415.1386,"1451609710000":47431.8804,"1451609720000":47439.7646,"1451609730000":47435.9606,"1451609740000":47418.3848,"1451609750000":47385.8761,"1451609760000":47341.3624,"1451609770000":47285.5518,"1451609780000":47222.1106,"1451609790000":47156.6958,"1451609800000":47094.5341,"1451609810000":47037.6319,"1451609820000":46986.794,"1451609830000":46942.0004,"1451609840000":46905.133,"1451609850000":46873.1318,"1451609860000":46845.1251,"1451609870000":46816.0974,"1451609880000":46783.8825,"1451609890000":46749.8147,"1451609900000":46712.0565,"1451609910000":46669.8923,"1451609920000":46622.6658,"1451609930000":46570.5326,"1451609940000":46513.0327,"1451609950000":46449.8151,"1451609960000":46381.079,"1451609970000":46306.4719,"1451609980000":46225.7465,"1451609990000":46138.8951},"Latitude":{"1451608080000":0.1976098,"1451608090000":0.8381574,"1451608100000":1.4787237,"1451608110000":2.1193083,"1451608120000":2.7599111,"1451608130000":3.4005317,"1451608140000":4.0411699,"1451608150000":4.6818255,"1451608160000":5.3224982,"1451608170000":5.9631878,"1451608180000":6.6038939,"1451608190000":7.2446163,"1451608200000":7.8853547,"1451608210000":8.5261088,"1451608220000":9.1668784,"1451608230000":9.8076631,"1451608240000":10.4484627,"1451608250000":11.0892769,"1451608260000":11.7301053,"1451608270000":12.3709477,"1451608280000":13.0118037,"1451608290000":13.652673,"1451608300000":14.2935554,"1451608310000":14.9344503,"1451608320000":15.5753577,"1451608330000":16.2162769,"1451608340000":16.8572078,"1451608350000":17.49815,"1451608360000":18.1391031,"1451608370000":18.7800668,"1451608380000":19.4210406,"1451608390000":20.0620242,"1451608400000":20.7030171,"1451608410000":21.3440191,"1451608420000":21.9850295,"1451608430000":22.6260481,"1451608440000":23.2670744,"1451608450000":23.908108,"1451608460000":24.5491483,"1451608470000":25.1901949,"1451608480000":25.8312474,"1451608490000":26.4723053,"1451608500000":27.113368,"1451608510000":27.754435,"1451608520000":28.3955059,"1451608530000":29.03658,"1451608540000":29.6776568,"1451608550000":30.3187357,"1451608560000":30.9598161,"1451608570000":31.6008974,"1451608580000":32.241979,"1451608590000":32.8830602,"1451608600000":33.5241403,"1451608610000":34.1652187,"1451608620000":34.8062945,"1451608630000":35.4473671,"1451608640000":36.0884356,"1451608650000":36.7294993,"1451608660000":37.3705573,"1451608670000":38.0116087,"1451608680000":38.6526526,"1451608690000":39.293688,"1451608700000":39.934714,"1451608710000":40.5757295,"1451608720000":41.2167334,"1451608730000":41.8577246,"1451608740000":42.4987019,"1451608750000":43.1396641,"1451608760000":43.7806099,"1451608770000":44.4215378,"1451608780000":45.0624465,"1451608790000":45.7033345,"1451608800000":46.3442002,"1451608810000":46.9850419,"1451608820000":47.6258579,"1451608830000":48.2666462,"1451608840000":48.907405,"1451608850000":49.5481322,"1451608860000":50.1888255,"1451608870000":50.8294827,"1451608880000":51.4701013,"1451608890000":52.1106786,"1451608900000":52.7512119,"1451608910000":53.3916982,"1451608920000":54.0321343,"1451608930000":54.6725168,"1451608940000":55.3128422,"1451608950000":55.9531064,"1451608960000":56.5933054,"1451608970000":57.2334346,"1451608980000":57.8734893,"1451608990000":58.5134642,"1451609000000":59.1533537,"1451609010000":59.7931517,"1451609020000":60.4328516,"1451609030000":61.0724463,"1451609040000":61.711928,"1451609050000":62.3512881,"1451609060000":62.9905175,"1451609070000":63.6296058,"1451609080000":64.2685419,"1451609090000":64.9073136,"1451609100000":65.5459073,"1451609110000":66.1843081,"1451609120000":66.8224992,"1451609130000":67.4604622,"1451609140000":68.0981766,"1451609150000":68.7356193,"1451609160000":69.3727644,"1451609170000":70.0095828,"1451609180000":70.6460415,"1451609190000":71.2821032,"1451609200000":71.9177254,"1451609210000":72.5528592,"1451609220000":73.1874487,"1451609230000":73.8214291,"1451609240000":74.4547252,"1451609250000":75.087249,"1451609260000":75.7188974,"1451609270000":76.349548,"1451609280000":76.9790551,"1451609290000":77.6072438,"1451609300000":78.2339022,"1451609310000":78.8587714,"1451609320000":79.4815316,"1451609330000":80.1017842,"1451609340000":80.7190256,"1451609350000":81.3326117,"1451609360000":81.9417067,"1451609370000":82.545209,"1451609380000":83.1416412,"1451609390000":83.7289849,"1451609400000":84.3044269,"1451609410000":84.8639645,"1451609420000":85.4017872,"1451609430000":85.9093203,"1451609440000":86.3738222,"1451609450000":86.7766352,"1451609460000":87.0920092,"1451609470000":87.2892515,"1451609480000":87.341932,"1451609490000":87.2417551,"1451609500000":87.0040147,"1451609510000":86.6579401,"1451609520000":86.2332742,"1451609530000":85.7535304,"1451609540000":85.235317,"1451609550000":84.6898852,"1451609560000":84.1248108,"1451609570000":83.5452511,"1451609580000":82.9547802,"1451609590000":82.3559267,"1451609600000":81.7505164,"1451609610000":81.1398936,"1451609620000":80.5250667,"1451609630000":79.9068042,"1451609640000":79.2857013,"1451609650000":78.6622255,"1451609660000":78.0367481,"1451609670000":77.4095679,"1451609680000":76.7809279,"1451609690000":76.1510272,"1451609700000":75.520031,"1451609710000":74.888077,"1451609720000":74.2552811,"1451609730000":73.6217413,"1451609740000":72.9875413,"1451609750000":72.3527528,"1451609760000":71.7174376,"1451609770000":71.0816494,"1451609780000":70.4454347,"1451609790000":69.8088346,"1451609800000":69.1718848,"1451609810000":68.534617,"1451609820000":67.8970594,"1451609830000":67.2592368,"1451609840000":66.6211714,"1451609850000":65.9828833,"1451609860000":65.3443902,"1451609870000":64.7057082,"1451609880000":64.066852,"1451609890000":63.4278345,"1451609900000":62.7886678,"1451609910000":62.1493627,"1451609920000":61.5099292,"1451609930000":60.8703761,"1451609940000":60.2307119,"1451609950000":59.5909441,"1451609960000":58.9510798,"1451609970000":58.3111252,"1451609980000":57.6710866,"1451609990000":57.0309692},"Longitude":{"1451608080000":-102.6818411,"1451608090000":-102.6939464,"1451608100000":-102.7060411,"1451608110000":-102.7181177,"1451608120000":-102.7301688,"1451608130000":-102.7421868,"1451608140000":-102.7541643,"1451608150000":-102.7660937,"1451608160000":-102.7779673,"1451608170000":-102.7897776,"1451608180000":-102.8015168,"1451608190000":-102.813177,"1451608200000":-102.8247504,"1451608210000":-102.8362291,"1451608220000":-102.8476049,"1451608230000":-102.8588697,"1451608240000":-102.8700152,"1451608250000":-102.8810329,"1451608260000":-102.8919143,"1451608270000":-102.9026508,"1451608280000":-102.9132335,"1451608290000":-102.9236533,"1451608300000":-102.933901,"1451608310000":-102.9439674,"1451608320000":-102.9538427,"1451608330000":-102.9635173,"1451608340000":-102.9729811,"1451608350000":-102.9822238,"1451608360000":-102.9912349,"1451608370000":-103.0000038,"1451608380000":-103.0085192,"1451608390000":-103.01677,"1451608400000":-103.0247443,"1451608410000":-103.0324303,"1451608420000":-103.0398156,"1451608430000":-103.0468874,"1451608440000":-103.0536327,"1451608450000":-103.060038,"1451608460000":-103.0660892,"1451608470000":-103.0717721,"1451608480000":-103.0770717,"1451608490000":-103.0819726,"1451608500000":-103.0864588,"1451608510000":-103.0905139,"1451608520000":-103.0941207,"1451608530000":-103.0972615,"1451608540000":-103.0999179,"1451608550000":-103.1020707,"1451608560000":-103.1037001,"1451608570000":-103.1047853,"1451608580000":-103.1053049,"1451608590000":-103.1052365,"1451608600000":-103.1045567,"1451608610000":-103.1032413,"1451608620000":-103.1012647,"1451608630000":-103.0986006,"1451608640000":-103.0952212,"1451608650000":-103.0910975,"1451608660000":-103.0861993,"1451608670000":-103.0804949,"1451608680000":-103.073951,"1451608690000":-103.0665328,"1451608700000":-103.0582037,"1451608710000":-103.0489254,"1451608720000":-103.0386576,"1451608730000":-103.0273578,"1451608740000":-103.0149814,"1451608750000":-103.0014815,"1451608760000":-102.9868084,"1451608770000":-102.97091,"1451608780000":-102.9537311,"1451608790000":-102.9352135,"1451608800000":-102.9152954,"1451608810000":-102.8939116,"1451608820000":-102.8709931,"1451608830000":-102.8464665,"1451608840000":-102.820254,"1451608850000":-102.7922731,"1451608860000":-102.7624358,"1451608870000":-102.7306486,"1451608880000":-102.6968118,"1451608890000":-102.6608191,"1451608900000":-102.6225567,"1451608910000":-102.5819034,"1451608920000":-102.5387289,"1451608930000":-102.4928941,"1451608940000":-102.4442494,"1451608950000":-102.3926339,"1451608960000":-102.337875,"1451608970000":-102.2797861,"1451608980000":-102.2181664,"1451608990000":-102.1527985,"1451609000000":-102.0834473,"1451609010000":-102.0098579,"1451609020000":-101.9317535,"1451609030000":-101.8488331,"1451609040000":-101.7607685,"1451609050000":-101.6672014,"1451609060000":-101.5677401,"1451609070000":-101.4619549,"1451609080000":-101.3493739,"1451609090000":-101.2294775,"1451609100000":-101.1016924,"1451609110000":-100.9653841,"1451609120000":-100.8198491,"1451609130000":-100.6643048,"1451609140000":-100.4978783,"1451609150000":-100.3195934,"1451609160000":-100.1283541,"1451609170000":-99.9229264,"1451609180000":-99.7019162,"1451609190000":-99.4637418,"1451609200000":-99.2066023,"1451609210000":-98.928438,"1451609220000":-98.6268831,"1451609230000":-98.2992066,"1451609240000":-97.9422405,"1451609250000":-97.5522891,"1451609260000":-97.125016,"1451609270000":-96.6553012,"1451609280000":-96.1370585,"1451609290000":-95.5629999,"1451609300000":-94.9243302,"1451609310000":-94.2103451,"1451609320000":-93.4078967,"1451609330000":-92.5006755,"1451609340000":-91.4682334,"1451609350000":-90.2846367,"1451609360000":-88.9165881,"1451609370000":-87.3207743,"1451609380000":-85.440085,"1451609390000":-83.1981871,"1451609400000":-80.4917588,"1451609410000":-77.1795803,"1451609420000":-73.0680795,"1451609430000":-67.8951244,"1451609440000":-61.3213981,"1451609450000":-52.9588384,"1451609460000":-42.5020115,"1451609470000":-30.0322849,"1451609480000":-16.3652148,"1451609490000":-2.9385079,"1451609500000":8.9464356,"1451609510000":18.7267995,"1451609520000":26.4852865,"1451609530000":32.5773643,"1451609540000":37.3833182,"1451609550000":41.2185659,"1451609560000":44.3220472,"1451609570000":46.8691128,"1451609580000":48.9876622,"1451609590000":50.7715119,"1451609600000":52.2902347,"1451609610000":53.5961042,"1451609620000":54.728936,"1451609630000":55.7194666,"1451609640000":56.5917301,"1451609650000":57.364747,"1451609660000":58.0537406,"1451609670000":58.6710224,"1451609680000":59.2266455,"1451609690000":59.7288925,"1451609700000":60.184644,"1451609710000":60.5996597,"1451609720000":60.9787952,"1451609730000":61.3261716,"1451609740000":61.6453074,"1451609750000":61.9392245,"1451609760000":62.2105316,"1451609770000":62.4614925,"1451609780000":62.6940808,"1451609790000":62.9100245,"1451609800000":63.110843,"1451609810000":63.2978776,"1451609820000":63.4723164,"1451609830000":63.6352158,"1451609840000":63.7875181,"1451609850000":63.9300663,"1451609860000":64.0636171,"1451609870000":64.1888512,"1451609880000":64.306383,"1451609890000":64.4167681,"1451609900000":64.5205107,"1451609910000":64.6180688,"1451609920000":64.7098595,"1451609930000":64.7962638,"1451609940000":64.8776301,"1451609950000":64.9542776,"1451609960000":65.0264996,"1451609970000":65.0945657,"1451609980000":65.1587245,"1451609990000":65.2192051},"Radius":{"1451608080000":6828390.6100000003,"1451608090000":6828288.8700000001,"1451608100000":6828186.3200000003,"1451608110000":6828082.9699999997,"1451608120000":6827978.8300000001,"1451608130000":6827873.9199999999,"1451608140000":6827768.2400000002,"1451608150000":6827661.8200000003,"1451608160000":6827554.6699999999,"1451608170000":6827446.7999999998,"1451608180000":6827338.2400000002,"1451608190000":6827228.9900000002,"1451608200000":6827119.0700000003,"1451608210000":6827008.5099999998,"1451608220000":6826897.3100000005,"1451608230000":6826785.5099999998,"1451608240000":6826673.1000000006,"1451608250000":6826560.1200000001,"1451608260000":6826446.5800000001,"1451608270000":6826332.5,"1451608280000":6826217.9100000001,"1451608290000":6826102.8100000005,"1451608300000":6825987.2400000002,"1451608310000":6825871.21,"1451608320000":6825754.7400000002,"1451608330000":6825637.8600000003,"1451608340000":6825520.5899999999,"1451608350000":6825402.9400000004,"1451608360000":6825284.9400000004,"1451608370000":6825166.6200000001,"1451608380000":6825047.9900000002,"1451608390000":6824929.0899999999,"1451608400000":6824809.9199999999,"1451608410000":6824690.5200000005,"1451608420000":6824570.9100000001,"1451608430000":6824451.1200000001,"1451608440000":6824331.1600000001,"1451608450000":6824211.0700000003,"1451608460000":6824090.8700000001,"1451608470000":6823970.5800000001,"1451608480000":6823850.2300000004,"1451608490000":6823729.8500000006,"1451608500000":6823609.46,"1451608510000":6823489.1000000006,"1451608520000":6823368.7800000003,"1451608530000":6823248.54,"1451608540000":6823128.3900000006,"1451608550000":6823008.3700000001,"1451608560000":6822888.5099999998,"1451608570000":6822768.8300000001,"1451608580000":6822649.3600000003,"1451608590000":6822530.1299999999,"1451608600000":6822411.1600000001,"1451608610000":6822292.4800000004,"1451608620000":6822174.1200000001,"1451608630000":6822056.1100000003,"1451608640000":6821938.4699999997,"1451608650000":6821821.2300000004,"1451608660000":6821704.4199999999,"1451608670000":6821588.0700000003,"1451608680000":6821472.2000000002,"1451608690000":6821356.8399999999,"1451608700000":6821242.0200000005,"1451608710000":6821127.7599999998,"1451608720000":6821014.1000000006,"1451608730000":6820901.0600000005,"1451608740000":6820788.6699999999,"1451608750000":6820676.96,"1451608760000":6820565.9500000002,"1451608770000":6820455.6699999999,"1451608780000":6820346.1500000004,"1451608790000":6820237.4100000001,"1451608800000":6820129.4900000002,"1451608810000":6820022.4100000001,"1451608820000":6819916.2000000002,"1451608830000":6819810.8799999999,"1451608840000":6819706.4800000004,"1451608850000":6819603.04,"1451608860000":6819500.5700000003,"1451608870000":6819399.1100000003,"1451608880000":6819298.6799999997,"1451608890000":6819199.3100000005,"1451608900000":6819101.0200000005,"1451608910000":6819003.8399999999,"1451608920000":6818907.8100000005,"1451608930000":6818812.9299999997,"1451608940000":6818719.2400000002,"1451608950000":6818626.7700000005,"1451608960000":6818535.54,"1451608970000":6818445.5800000001,"1451608980000":6818356.9000000004,"1451608990000":6818269.54,"1451609000000":6818183.5200000005,"1451609010000":6818098.8600000003,"1451609020000":6818015.5899999999,"1451609030000":6817933.7300000004,"1451609040000":6817853.2999999998,"1451609050000":6817774.3200000003,"1451609060000":6817696.8100000005,"1451609070000":6817620.8100000005,"1451609080000":6817546.3100000005,"1451609090000":6817473.3600000003,"1451609100000":6817401.96,"1451609110000":6817332.1400000006,"1451609120000":6817263.9000000004,"1451609130000":6817197.2800000003,"1451609140000":6817132.2999999998,"1451609150000":6817068.9500000002,"1451609160000":6817007.2700000005,"1451609170000":6816947.2700000005,"1451609180000":6816888.96,"1451609190000":6816832.3700000001,"1451609200000":6816777.4900000002,"1451609210000":6816724.3600000003,"1451609220000":6816672.9699999997,"1451609230000":6816623.3500000006,"1451609240000":6816575.5099999998,"1451609250000":6816529.4500000002,"1451609260000":6816485.2000000002,"1451609270000":6816442.75,"1451609280000":6816402.1299999999,"1451609290000":6816363.3399999999,"1451609300000":6816326.4000000004,"1451609310000":6816291.2999999998,"1451609320000":6816258.0700000003,"1451609330000":6816226.7000000002,"1451609340000":6816197.21,"1451609350000":6816169.6000000006,"1451609360000":6816143.8799999999,"1451609370000":6816120.0600000005,"1451609380000":6816098.1400000006,"1451609390000":6816078.1299999999,"1451609400000":6816060.0300000003,"1451609410000":6816043.8500000006,"1451609420000":6816029.5800000001,"1451609430000":6816017.2400000002,"1451609440000":6816006.8300000001,"1451609450000":6815998.3500000006,"1451609460000":6815991.7999999998,"1451609470000":6815987.1799999997,"1451609480000":6815984.4900000002,"1451609490000":6815983.7400000002,"1451609500000":6815984.9299999997,"1451609510000":6815988.04,"1451609520000":6815993.0899999999,"1451609530000":6816000.0700000003,"1451609540000":6816008.9800000004,"1451609550000":6816019.8200000003,"1451609560000":6816032.5800000001,"1451609570000":6816047.25,"1451609580000":6816063.8399999999,"1451609590000":6816082.3399999999,"1451609600000":6816102.75,"1451609610000":6816125.0600000005,"1451609620000":6816149.2599999998,"1451609630000":6816175.3500000006,"1451609640000":6816203.3200000003,"1451609650000":6816233.1600000001,"1451609660000":6816264.8700000001,"1451609670000":6816298.4400000004,"1451609680000":6816333.8600000003,"1451609690000":6816371.1100000003,"1451609700000":6816410.1900000004,"1451609710000":6816451.0899999999,"1451609720000":6816493.7999999998,"1451609730000":6816538.2999999998,"1451609740000":6816584.5800000001,"1451609750000":6816632.6200000001,"1451609760000":6816682.4299999997,"1451609770000":6816733.9699999997,"1451609780000":6816787.2400000002,"1451609790000":6816842.2199999997,"1451609800000":6816898.9000000004,"1451609810000":6816957.25,"1451609820000":6817017.2700000005,"1451609830000":6817078.9299999997,"1451609840000":6817142.2199999997,"1451609850000":6817207.1200000001,"1451609860000":6817273.6100000003,"1451609870000":6817341.6699999999,"1451609880000":6817411.29,"1451609890000":6817482.4400000004,"1451609900000":6817555.1100000003,"1451609910000":6817629.2700000005,"1451609920000":6817704.9000000004,"1451609930000":6817782.0,"1451609940000":6817860.5200000005,"1451609950000":6817940.4500000002,"1451609960000":6818021.7800000003,"1451609970000":6818104.4699999997,"1451609980000":6818188.5099999998,"1451609990000":6818273.8700000001},"Spacecraft":{"1451608080000":"A","1451608090000":"A","1451608100000":"A","1451608110000":"A","1451608120000":"A","1451608130000":"A","1451608140000":"A","1451608150000":"A","1451608160000":"A","1451608170000":"A","1451608180000":"A","1451608190000":"A","1451608200000":"A","1451608210000":"A","1451608220000":"A","1451608230000":"A","1451608240000":"A","1451608250000":"A","1451608260000":"A","1451608270000":"A","1451608280000":"A","1451608290000":"A","1451608300000":"A","1451608310000":"A","1451608320000":"A","1451608330000":"A","1451608340000":"A","1451608350000":"A","1451608360000":"A","1451608370000":"A","1451608380000":"A","1451608390000":"A","1451608400000":"A","1451608410000":"A","1451608420000":"A","1451608430000":"A","1451608440000":"A","1451608450000":"A","1451608460000":"A","1451608470000":"A","1451608480000":"A","1451608490000":"A","1451608500000":"A","1451608510000":"A","1451608520000":"A","1451608530000":"A","1451608540000":"A","1451608550000":"A","1451608560000":"A","1451608570000":"A","1451608580000":"A","1451608590000":"A","1451608600000":"A","1451608610000":"A","1451608620000":"A","1451608630000":"A","1451608640000":"A","1451608650000":"A","1451608660000":"A","1451608670000":"A","1451608680000":"A","1451608690000":"A","1451608700000":"A","1451608710000":"A","1451608720000":"A","1451608730000":"A","1451608740000":"A","1451608750000":"A","1451608760000":"A","1451608770000":"A","1451608780000":"A","1451608790000":"A","1451608800000":"A","1451608810000":"A","1451608820000":"A","1451608830000":"A","1451608840000":"A","1451608850000":"A","1451608860000":"A","1451608870000":"A","1451608880000":"A","1451608890000":"A","1451608900000":"A","1451608910000":"A","1451608920000":"A","1451608930000":"A","1451608940000":"A","1451608950000":"A","1451608960000":"A","1451608970000":"A","1451608980000":"A","1451608990000":"A","1451609000000":"A","1451609010000":"A","1451609020000":"A","1451609030000":"A","1451609040000":"A","1451609050000":"A","1451609060000":"A","1451609070000":"A","1451609080000":"A","1451609090000":"A","1451609100000":"A","1451609110000":"A","1451609120000":"A","1451609130000":"A","1451609140000":"A","1451609150000":"A","1451609160000":"A","1451609170000":"A","1451609180000":"A","1451609190000":"A","1451609200000":"A","1451609210000":"A","1451609220000":"A","1451609230000":"A","1451609240000":"A","1451609250000":"A","1451609260000":"A","1451609270000":"A","1451609280000":"A","1451609290000":"A","1451609300000":"A","1451609310000":"A","1451609320000":"A","1451609330000":"A","1451609340000":"A","1451609350000":"A","1451609360000":"A","1451609370000":"A","1451609380000":"A","1451609390000":"A","1451609400000":"A","1451609410000":"A","1451609420000":"A","1451609430000":"A","1451609440000":"A","1451609450000":"A","1451609460000":"A","1451609470000":"A","1451609480000":"A","1451609490000":"A","1451609500000":"A","1451609510000":"A","1451609520000":"A","1451609530000":"A","1451609540000":"A","1451609550000":"A","1451609560000":"A","1451609570000":"A","1451609580000":"A","1451609590000":"A","1451609600000":"A","1451609610000":"A","1451609620000":"A","1451609630000":"A","1451609640000":"A","1451609650000":"A","1451609660000":"A","1451609670000":"A","1451609680000":"A","1451609690000":"A","1451609700000":"A","1451609710000":"A","1451609720000":"A","1451609730000":"A","1451609740000":"A","1451609750000":"A","1451609760000":"A","1451609770000":"A","1451609780000":"A","1451609790000":"A","1451609800000":"A","1451609810000":"A","1451609820000":"A","1451609830000":"A","1451609840000":"A","1451609850000":"A","1451609860000":"A","1451609870000":"A","1451609880000":"A","1451609890000":"A","1451609900000":"A","1451609910000":"A","1451609920000":"A","1451609930000":"A","1451609940000":"A","1451609950000":"A","1451609960000":"A","1451609970000":"A","1451609980000":"A","1451609990000":"A"}}
diff --git a/tests/test_ClientRequest.py b/tests/test_ClientRequest.py
new file mode 100644
index 0000000..31843fe
--- /dev/null
+++ b/tests/test_ClientRequest.py
@@ -0,0 +1,19 @@
+import viresclient
+from viresclient import AeolusRequest, SwarmRequest
+from viresclient._client import ClientRequest
+
+
+def test_ClientRequest():
+    """Test that a ClientRequest gets set up correctly."""
+    request = ClientRequest("dummy_url")
+    assert isinstance(
+        request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service
+    )
+    request = SwarmRequest("dummy_url")
+    assert isinstance(
+        request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service
+    )
+    request = AeolusRequest("dummy_url")
+    assert isinstance(
+        request._wps_service, viresclient._wps.wps_vires.ViresWPS10Service
+    )
diff --git a/test/test_ReturnedData.py b/tests/test_ReturnedData.py
similarity index 84%
rename from test/test_ReturnedData.py
rename to tests/test_ReturnedData.py
index b670fa2..569607d 100644
--- a/test/test_ReturnedData.py
+++ b/tests/test_ReturnedData.py
@@ -1,10 +1,11 @@
-import pytest
 import os
+
 import pandas
+import pytest
 
-from viresclient._data_handling import ReturnedDataFile, ReturnedData
+from viresclient._data_handling import ReturnedData, ReturnedDataFile
 
-SUPPORTED_FILETYPES = ('csv', 'cdf', 'nc')
+SUPPORTED_FILETYPES = ("csv", "cdf", "nc")
 
 TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
 
@@ -21,7 +22,7 @@
 TEST_FILES = {
     "cdf": os.path.join(TEST_DATA_DIR, "test_data_01.cdf"),
     "csv": os.path.join(TEST_DATA_DIR, "test_data_01.csv"),
-    "json_from_cdf": os.path.join(TEST_DATA_DIR, "test_data_01_from_cdf.json")
+    "json_from_cdf": os.path.join(TEST_DATA_DIR, "test_data_01_from_cdf.json"),
 }
 
 
@@ -40,7 +41,7 @@ def test_ReturnedDataFile_setup():
     # The following should raise a TypeError:
     #  filetype must be csv/cdf/nc
     with pytest.raises(TypeError):
-        retdata = ReturnedDataFile(filetype='xyz')
+        retdata = ReturnedDataFile(filetype="xyz")
     with pytest.raises(TypeError):
         retdata = ReturnedDataFile(filetype=1)
 
@@ -60,7 +61,7 @@ def test_ReturnedData_setup():
     # The following should raise a TypeError:
     #  filetype must be csv/cdf/nc
     with pytest.raises(TypeError):
-        retdata = ReturnedData(filetype='xyz')
+        retdata = ReturnedData(filetype="xyz")
     with pytest.raises(TypeError):
         retdata = ReturnedData(filetype=1)
 
@@ -75,25 +76,19 @@ def test_ReturnedDataFile_saving(tmpfile):
 
         # Check that file name and extension checking is enforced
         retdata = ReturnedDataFile(filetype=filetype)
-        retdata._write_new_data(b'testtext')
+        retdata._write_new_data(b"testtext")
         with pytest.raises(TypeError):
             retdata.to_file(1)
         with pytest.raises(TypeError):
-            retdata.to_file(
-                str(tmpfile("testfile.xyz"))
-                )
+            retdata.to_file(str(tmpfile("testfile.xyz")))
 
         # Check that not overwriting and overwriting work right
-        testfile = str(tmpfile('testfile.{}'.format(filetype)))
+        testfile = str(tmpfile(f"testfile.{filetype}"))
         retdata.to_file(testfile)
         # with pytest.raises(FileExistsError):  # not in py27
         with pytest.raises(Exception):
-            retdata.to_file(
-                testfile, overwrite=False
-                )
-        retdata.to_file(
-            testfile, overwrite=True
-            )
+            retdata.to_file(testfile, overwrite=False)
+        retdata.to_file(testfile, overwrite=True)
 
 
 def test_ReturnedData_saving(tmpfile):
@@ -106,42 +101,30 @@ def test_ReturnedData_saving(tmpfile):
 
         # Check that file name and extension checking is enforced
         retdata = ReturnedData(filetype=filetype)
-        retdata.contents[0]._write_new_data(b'testtext')
+        retdata.contents[0]._write_new_data(b"testtext")
         with pytest.raises(TypeError):
             retdata.to_file(1)
         with pytest.raises(TypeError):
-            retdata.to_file(
-                str(tmpfile("testfile.xyz"))
-                )
+            retdata.to_file(str(tmpfile("testfile.xyz")))
         # Repeat for .to_files()
         with pytest.raises(TypeError):
             retdata.to_files([1])
         with pytest.raises(TypeError):
-            retdata.to_files(
-                [str(tmpfile("testfile.xyz"))]
-                )
+            retdata.to_files([str(tmpfile("testfile.xyz"))])
 
         # Check that not overwriting and overwriting work right
-        testfile = str(tmpfile('testfile.{}'.format(filetype)))
+        testfile = str(tmpfile(f"testfile.{filetype}"))
         retdata.to_file(testfile)
         # with pytest.raises(FileExistsError):  # not in py27
         with pytest.raises(Exception):
-            retdata.to_file(
-                testfile, overwrite=False
-                )
-        retdata.to_file(
-            testfile, overwrite=True
-            )
+            retdata.to_file(testfile, overwrite=False)
+        retdata.to_file(testfile, overwrite=True)
         # repeat for .to_files()
-        testfile = str(tmpfile('testfile2.{}'.format(filetype)))
+        testfile = str(tmpfile(f"testfile2.{filetype}"))
         retdata.to_files([testfile])
         with pytest.raises(Exception):
-            retdata.to_files(
-                [testfile], overwrite=False
-                )
-        retdata.to_files(
-            [testfile], overwrite=True
-            )
+            retdata.to_files([testfile], overwrite=False)
+        retdata.to_files([testfile], overwrite=True)
 
 
 def test_ReturnedDataFile_dataframe():