From b681451b59fbc5466bdaeb2fbfb41896af078e2a Mon Sep 17 00:00:00 2001 From: Bruno Bord Date: Sun, 22 Sep 2019 19:00:44 +0200 Subject: [PATCH] Handling file expiration * Generate a catalog of the expiration dates for files, * Detect when a file has expired and raise a ``UserWarning`` --- CHANGELOG.md | 2 ++ README.md | 12 ++++++++++++ download.py | 24 +++++++++++++++++++----- skyfield_data/__init__.py | 2 ++ skyfield_data/expiration_data.py | 3 +++ skyfield_data/expirations.py | 26 ++++++++++++++++++++++++++ tests/test_expiration_date.py | 32 ++++++++++++++++++++++++++++++++ tox.ini | 2 +- 8 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 skyfield_data/expiration_data.py create mode 100644 skyfield_data/expirations.py create mode 100644 tests/test_expiration_date.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fe0365a..0c88744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * Dropped compatibility with Python 3.3 and 3.4, as skyfield did. * Added basic tests for the ``get_skyfield_data_path`` function using `tox`. * Added automated tests through Travis CI. +* Generate a catalog of the expiration dates for files, +* Detect when a file has expired and raise a ``UserWarning`` ## 0.0.2 (2019-08-23) diff --git a/README.md b/README.md index e30fb9f..a1b64b9 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,18 @@ If you want to make sure that the data files would **never** be downloaded, you load = Loader(get_skyfield_data_path(), expire=False) ``` +Whenever a file contained in the catalog has expired, you're going to receive a warning when loading the `skyfield-data` path: + +```python +>>> from skyfield_data import get_skyfield_data_path +>>> from skyfield.api import Loader +>>> load = Loader(get_skyfield_data_path()) +/home/[redacted]/skyfield_data/expirations.py:25: RuntimeWarning: The file de421.bsp has expired. Please upgrade your version of `skyfield-data` or expect computation errors + RuntimeWarning +``` + +By default, the loading isn't blocked, but it's strongly recommended to upgrade to a more recent version, to make sure you're not going to make wrong astronomical computations. + ## Developers We're providing a ``Makefile`` with basic targets to play around with the toolkit. use ``make help`` to get more details. diff --git a/download.py b/download.py index 6e268e5..e2eb010 100644 --- a/download.py +++ b/download.py @@ -2,17 +2,17 @@ import argparse from datetime import date from datetime import datetime, timedelta -from os.path import join, exists +from os.path import join, exists, abspath, dirname import shutil from urllib.request import urlopen from jplephem.spk import DAF, SPK -from skyfield_data import get_skyfield_data_path - JPL = "ftp://ssd.jpl.nasa.gov/pub/eph/planets/bsp" USNO = "http://maia.usno.navy.mil/ser7" IERS = "https://hpiers.obspm.fr/iers/bul/bulc" +__DATA_PATH = abspath(join(dirname(__file__), "skyfield_data", "data")) + def calendar_date(jd_integer): """Convert Julian Day `jd_integer` into a Gregorian (year, month, day).""" @@ -188,14 +188,21 @@ def main(args): "expiration_func": leap_seconds_expiration }, } + # For expiration date content + target_expiration = abspath( + join(__DATA_PATH, '..', 'expiration_data.py') + ) + expiration_dates = {} for filename, params in items.items(): server = params["server"] url = "{}/{}".format(server, filename) - target = join(get_skyfield_data_path(), filename) + target = join(__DATA_PATH, filename) + + expiration_date = get_expiration_date(target, params) + expiration_dates[filename] = expiration_date if args.check_only: - expiration_date = get_expiration_date(target, params) print( "File: {} => expiration: {}".format( filename, expiration_date or "`unknown`" @@ -214,6 +221,13 @@ def main(args): download(url, target) else: print("Skipping {} ({})".format(filename, reason)) + # Generating the expiration date file. + expiration_template = """import datetime + +EXPIRATIONS = {} +""" + with open(target_expiration, 'w') as fd: + fd.write(expiration_template.format(expiration_dates)) print("\nDone\n") diff --git a/skyfield_data/__init__.py b/skyfield_data/__init__.py index 7d13c8b..cc8242b 100644 --- a/skyfield_data/__init__.py +++ b/skyfield_data/__init__.py @@ -1,4 +1,5 @@ from os.path import dirname, join, abspath +from .expirations import check_expirations __DATA_PATH = abspath(join(dirname(__file__), "data")) @@ -14,6 +15,7 @@ def get_skyfield_data_path(): load = Loader(get_skyfield_data_path()) planets = load('de421.bsp') """ + check_expirations() return __DATA_PATH diff --git a/skyfield_data/expiration_data.py b/skyfield_data/expiration_data.py new file mode 100644 index 0000000..e669b1f --- /dev/null +++ b/skyfield_data/expiration_data.py @@ -0,0 +1,3 @@ +import datetime + +EXPIRATIONS = {'de421.bsp': datetime.date(2053, 10, 8), 'deltat.data': datetime.date(2020, 6, 1), 'deltat.preds': datetime.date(2021, 1, 1), 'Leap_Second.dat': datetime.date(2020, 7, 28)} diff --git a/skyfield_data/expirations.py b/skyfield_data/expirations.py new file mode 100644 index 0000000..949e30e --- /dev/null +++ b/skyfield_data/expirations.py @@ -0,0 +1,26 @@ +import warnings +from datetime import date +from os.path import dirname, join, abspath +from os import listdir +from .expiration_data import EXPIRATIONS + + +__DATA_PATH = abspath(join(dirname(__file__), "data")) + + +def get_all(): + return EXPIRATIONS + + +def check_expirations(): + expirations = get_all() + files = listdir(__DATA_PATH) + for filename in files: + expiration_date = expirations.get(filename) + if expiration_date and date.today() >= expiration_date: + warnings.warn( + ("The file {} has expired." + " Please upgrade your version of `skyfield-data` or expect" + " computation errors").format(filename), + RuntimeWarning + ) diff --git a/tests/test_expiration_date.py b/tests/test_expiration_date.py new file mode 100644 index 0000000..4646da3 --- /dev/null +++ b/tests/test_expiration_date.py @@ -0,0 +1,32 @@ +import mock +from datetime import date +from datetime import timedelta +from skyfield_data import get_skyfield_data_path + + +@mock.patch('skyfield_data.expirations.get_all') +def test_no_expiration(mocked_exp): + mocked_exp.return_value = {} + with mock.patch('warnings.warn') as mocked_warn: + get_skyfield_data_path() + assert mocked_warn.call_count == 0 + + +@mock.patch('skyfield_data.expirations.get_all') +def test_expiration_deltat_distant_future(mocked_exp): + mocked_exp.return_value = { + 'deltat.data': date.today() + timedelta(days=10000) + } + with mock.patch('warnings.warn') as mocked_warn: + get_skyfield_data_path() + assert mocked_warn.call_count == 0 + + +@mock.patch('skyfield_data.expirations.get_all') +def test_expiration_deltat_yesterday(mocked_exp): + mocked_exp.return_value = { + 'deltat.data': date.today() - timedelta(days=1) + } + with mock.patch('warnings.warn') as mocked_warn: + get_skyfield_data_path() + assert mocked_warn.call_count == 1 diff --git a/tox.ini b/tox.ini index 16ffc24..331152f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py27,py35,py36,py37 [testenv] commands = - pytest -s + pytest -s {posargs} deps = .[tests]