From 2faac54e21598c4c9337846924c6b62439675e87 Mon Sep 17 00:00:00 2001 From: "C. E. Brasseur" Date: Mon, 16 Oct 2023 11:30:44 +0100 Subject: [PATCH] Fixs for building docs etc --- astroquery/esa/neocc/__init__.py | 13 +-- astroquery/esa/neocc/core.py | 89 +++++++++---------- astroquery/esa/neocc/tabs.py | 1 - astroquery/esa/neocc/test/test_neocc.py | 38 ++++++-- .../esa/neocc/test/test_neocc_remote.py | 54 ++++++----- astroquery/solarsystem/neocc/__init__.py | 4 +- docs/esa/{ => neocc}/neocc.rst | 25 ++++-- docs/index.rst | 1 + 8 files changed, 134 insertions(+), 91 deletions(-) rename docs/esa/{ => neocc}/neocc.rst (96%) diff --git a/astroquery/esa/neocc/__init__.py b/astroquery/esa/neocc/__init__.py index a1ce009b7c..1b87669e17 100755 --- a/astroquery/esa/neocc/__init__.py +++ b/astroquery/esa/neocc/__init__.py @@ -18,11 +18,14 @@ class Conf(_config.ConfigNamespace): BASE_URL = 'https://' + os.getenv('NEOCC_PORTAL_IP', default='neo.ssa.esa.int') - API_URL = _config.ConfigItem(BASE_URL + '/PSDB-portlet/download?file=') + API_URL = _config.ConfigItem(BASE_URL + '/PSDB-portlet/download?file=', + "Main API URL") - EPHEM_URL = _config.ConfigItem(BASE_URL + '/PSDB-portlet/ephemerides?des=') + EPHEM_URL = _config.ConfigItem(BASE_URL + '/PSDB-portlet/ephemerides?des=', + "Ephermerides URL") - SUMMARY_URL = _config.ConfigItem(BASE_URL + '/search-for-asteroids?sum=1&des=') + SUMMARY_URL = _config.ConfigItem(BASE_URL + '/search-for-asteroids?sum=1&des=', + "Object summary URL") TIMEOUT = 60 @@ -31,6 +34,6 @@ class Conf(_config.ConfigNamespace): conf = Conf() -from .core import neocc, ESAneoccClass +from .core import neocc, NEOCCClass -__all__ = ['neocc', 'ESAneoccClass', 'Conf', 'conf'] +__all__ = ['neocc', 'NEOCCClass', 'Conf', 'conf'] diff --git a/astroquery/esa/neocc/core.py b/astroquery/esa/neocc/core.py index 4fcc4703cb..c5cba365b0 100755 --- a/astroquery/esa/neocc/core.py +++ b/astroquery/esa/neocc/core.py @@ -13,11 +13,11 @@ from astroquery.esa.neocc import lists, tabs -__all__ = ['neocc', 'ESAneoccClass'] +__all__ = ['neocc', 'NEOCCClass'] @async_to_sync -class ESAneoccClass(BaseQuery): +class NEOCCClass(BaseQuery): """ Class to init ESA NEOCC Python interface library """ @@ -130,7 +130,9 @@ def query_list(list_name): return neocc_list @staticmethod - def query_object(name, tab, **kwargs): + def query_object(name, tab, *, + orbital_elements=None, orbit_epoch=None, + observatory=None, start=None, stop=None, step=None, step_unit=None): """Get requested object data from ESA NEOCC. Parameters @@ -141,22 +143,27 @@ def query_object(name, tab, **kwargs): Name of the request tab. Valid names are: summary, orbit_properties, physical_properties, observations, ephemerides, close_approaches and impacts. - **kwargs : str - Tabs orbit_properties and ephemerides tabs required additional - arguments to work: - - * *orbit_properties*: the required additional arguments are: - - * *orbital_elements* : str (keplerian or equinoctial) - * *orbit_epoch* : str (present or middle) - - * *ephemerides*: the required additional arguments are: - - * *observatory* : str (observatory code, e.g. '500', 'J04', etc.) - * *start* : str (start date in YYYY-MM-DD HH:MM) - * *stop* : str (end date in YYYY-MM-DD HH:MM) - * *step* : str (time step, e.g. '2', '15', etc.) - * *step_unit* : str (e.g. 'days', 'minutes', etc.) + orbital_elements : str + Additional required argument for "orbit_properties" table. + Valid arguments are: keplerian, equinoctial + orbit_epoch : str + Additional required argument for "orbit_properties" table. + Valid arguments are: present, middle + observatory : str + Additional required argument for "ephemerides" table. + Observatory code, e.g. '500', 'J04', etc. + start : str + Additional required argument for "ephemerides" table. + Start date in YYYY-MM-DD HH:MM + stop : str + Additional required argument for "ephemerides" table. + End date in YYYY-MM-DD HH:MM + step : str + Additional required argument for "ephemerides" table. + Time step, e.g. '2', '15', etc. + step_unit : str + Additional required argument for "ephemerides" table. + Unit for time step e.g. 'days', 'minutes', etc. Returns ------- @@ -387,22 +394,14 @@ def query_object(name, tab, **kwargs): # Orbit properties elif tab == 'orbit_properties': - # Raise error if no elements are provided - if 'orbital_elements' not in kwargs: - raise KeyError('Please specify type of orbital_elements: ' - 'keplerian or equinoctial ' - '(e.g., orbital_elements="keplerian")') - - # Raise error if no epoch is provided - if 'orbit_epoch' not in kwargs: - raise KeyError('Please specify type of orbit_epoch: ' - 'present or middle ' - '(e.g., orbit_epoch="middle")') + + # Raise error if elements or epoch are not provided + if not all([orbital_elements, orbit_epoch]): + raise KeyError(("orbital_elements and orbit_epoch must be specified" + "for an orbit_properties query.")) # Get URL to obtain the data from NEOCC - url = tabs.get_object_url(name, tab, - orbital_elements=kwargs['orbital_elements'], - orbit_epoch=kwargs['orbit_epoch']) + url = tabs.get_object_url(name, tab, orbital_elements=orbital_elements, orbit_epoch=orbit_epoch) # Request data two times if the first attempt fails try: @@ -423,21 +422,13 @@ def query_object(name, tab, **kwargs): # Ephemerides elif tab == 'ephemerides': - # Create dictionary for kwargs - args_dict = {'observatory': 'observatory (e.g., observatory="500")', - 'start': 'start date (e.g., start="2021-05-17 00:00")', - 'stop': 'end date (e.g., stop="2021-05-18 00:00")', - 'step': 'time step (e.g., step="1")', - 'step_unit': 'step unit (e.g., step_unit="days")'} - - # Check if any kwargs is missing - for element in args_dict: - if element not in kwargs: - raise KeyError(f'Please specify {args_dict[element]} for ephemerides.') - - resp_str = tabs.get_ephemerides_data(name, observatory=kwargs['observatory'], - start=kwargs['start'], stop=kwargs['stop'], - step=kwargs['step'], step_unit=kwargs['step_unit']) + + if not all([observatory, start, stop, step, step_unit]): + raise KeyError(("Ephemerides queries require the following arguments:" + "observatory, start, stop, step, and step_unit")) + + resp_str = tabs.get_ephemerides_data(name, observatory=observatory, start=start, stop=stop, + step=step, step_unit=step_unit) neocc_obj = tabs.parse_ephemerides(resp_str) elif tab == 'summary': @@ -450,4 +441,4 @@ def query_object(name, tab, **kwargs): return neocc_obj -neocc = ESAneoccClass() +neocc = NEOCCClass() diff --git a/astroquery/esa/neocc/tabs.py b/astroquery/esa/neocc/tabs.py index ea7d3f1bab..682196f887 100755 --- a/astroquery/esa/neocc/tabs.py +++ b/astroquery/esa/neocc/tabs.py @@ -466,7 +466,6 @@ def parse_observations(resp_str, verbose=False): """ Parse the close approach response string into the close approach tables. - TODO: document properly. Parameters ---------- resp_str : str diff --git a/astroquery/esa/neocc/test/test_neocc.py b/astroquery/esa/neocc/test/test_neocc.py index 5ce44b4ff2..0f934e8afa 100755 --- a/astroquery/esa/neocc/test/test_neocc.py +++ b/astroquery/esa/neocc/test/test_neocc.py @@ -8,6 +8,7 @@ import os import re import pytest +import warnings import numpy as np import requests @@ -54,7 +55,6 @@ def get_mockreturn(name, timeout=TIMEOUT, verify=VERIFICATION): # Split name (the requested url) to obtain the name of the file location stored in \data fileloc = name.split(r'=')[1] - print(fileloc) # Exception for ephemerides if '&oc' in fileloc: @@ -63,6 +63,7 @@ def get_mockreturn(name, timeout=TIMEOUT, verify=VERIFICATION): filename = data_path(fileloc) with open(filename, 'rb') as FLE: content = FLE.read() + content = content.replace(b"\r", b"") # For windows tests return MockResponse(content) @@ -86,7 +87,12 @@ def test_bad_list_names(): def check_table_structure(data_table, table_len, table_cols, float_cols=[], int_cols=[], str_cols=[], time_cols=[]): """ - TODO + Given a data table, checks: + - Table length matches given table_len + - Table column names match the given table_cols list + - All the columns with indices given in float_cols are of float type + - Equivalent checks for int_cols, and string_cols + - Checks that columns with indices given in time_cols are `~astropy.time.Time` objects """ table_cols = np.array(table_cols) @@ -96,15 +102,20 @@ def check_table_structure(data_table, table_len, table_cols, float_cols=[], int_ assert all([x == y for x, y in zip(data_table.colnames, table_cols)]) - assert all([data_table[x].dtype == np.dtype('float64') for x in table_cols[float_cols]]) - assert all([data_table[x].dtype == np.dtype('int64') for x in table_cols[int_cols]]) - assert all([data_table[x].dtype.type == np.str_ for x in table_cols[str_cols]]) + # Ignore the FutureWarning that only comes up with the oldest dependencies + warnings.filterwarnings("ignore", category=FutureWarning, + message="Conversion of the second argument of issubdtype*") + assert all([np.issubdtype(data_table[x].dtype, float) for x in table_cols[float_cols]]) + assert all([np.issubdtype(data_table[x].dtype, int) for x in table_cols[int_cols]]) + assert all([np.issubdtype(data_table[x].dtype, str) for x in table_cols[str_cols]]) + assert all([isinstance(data_table[x], Time) for x in table_cols[time_cols]]) def check_table_values(data_table, true_value_dict): """ - TODO + Checks data_table rows against true values given in true_value_dict. + The format of true_value_dict is {: [true row data], ...} """ for row, values in true_value_dict.items(): @@ -178,8 +189,11 @@ def test_parse_nea(patch_get): assert re.match(r'\w{3} \w{3} \d{2} \d{2}:\d{2}:\d{2} \w{3} \d{4}', monthly_update["NEA"][0]) +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') def test_parse_risk(patch_get): """Check data: risk_list, risk_list_special + + Ignore ERFA 'dubious year' warnings because they are expected. """ # Risk and risk special lists risk_list = neocc.neocc.query_list("risk_list") @@ -293,8 +307,11 @@ def test_parse_pri(patch_get): check_table_values(faint_list, faint_dict) +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') def test_parse_encounter(patch_get): """Check data: encounter_list + + Ignore ERFA 'dubious year' warnings because they are expected. """ encounter_list = neocc.neocc.query_list("close_encounter") @@ -377,8 +394,11 @@ def check_tab_result_basic(results, num_tabs): assert len(results) == num_tabs +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') def test_tabs_impacts(patch_get): """Check data: asteroid impacts tab + + Ignore ERFA 'dubious year' warnings because they are expected. """ # 433 Eros has no tab "impacts" @@ -418,8 +438,11 @@ def test_tabs_impacts(patch_get): check_table_values(impact_table, impact_dict) +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') def test_tabs_close_approach(patch_get): """Check data: asteroid close approaches tab + + Ignore ERFA 'dubious year' warnings because they are expected. """ # Check opbject with no close approaches @@ -503,8 +526,11 @@ def test_tabs_physical_properties(patch_get): check_table_values(phys_props, phys_dict) +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') def test_tabs_observations(patch_get): """Check data: asteroid observations tab + + Ignore ERFA 'dubious year' warnings because they are expected. """ with pytest.raises(ValueError): diff --git a/astroquery/esa/neocc/test/test_neocc_remote.py b/astroquery/esa/neocc/test/test_neocc_remote.py index a0563b270b..76de3c103d 100644 --- a/astroquery/esa/neocc/test/test_neocc_remote.py +++ b/astroquery/esa/neocc/test/test_neocc_remote.py @@ -10,12 +10,12 @@ * Date: 21-08-2022 """ - import os import re import random import pytest import requests +import warnings import numpy as np @@ -31,9 +31,12 @@ VERIFICATION = neocc.conf.SSL_CERT_VERIFICATION +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') @pytest.mark.remote_data class TestLists: """Class which contains the unitary tests for lists module. + + Ignore ERFA 'dubious year' warnings because they are expected. """ # Dictionary for lists lists_dict = { @@ -52,6 +55,10 @@ class TestLists: "neo_catalogue_middle": 'neo_km.cat' } + # Ignore the FutureWarning that only comes up with the oldest dependencies + warnings.filterwarnings("ignore", category=FutureWarning, + message="Conversion of the second argument of issubdtype*") + def test_get_list_url(self): """Test for checking the URL termination for requested lists. Check invalid list name raise KeyError. @@ -158,7 +165,7 @@ def test_parse_risk(self): data_list = requests.get(API_URL + self.lists_dict[url], timeout=TIMEOUT, verify=VERIFICATION).content - # Decode the data using UTF-8 + # Decode te data using UTF-8 data_list_d = data_list.decode('utf-8') # Parse using parse_risk @@ -175,15 +182,15 @@ def test_parse_risk(self): # Assert columns data types # Floats float_cols = ['Diameter in m', 'IP max', 'PS max', 'Vel in km/s', 'IP cum', 'PS cum'] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) # int64 int_cols = ['TS', 'First Year', 'Last Year'] - assert all([new_list[x].dtype == np.dtype('int64') for x in int_cols]) + assert all([np.issubdtype(new_list[x].dtype, int) for x in int_cols]) # String str_cols = ['Object Name', '*=Y'] - assert all([new_list[x].dtype.type == np.str_ for x in str_cols]) + assert all([np.issubdtype(new_list[x].dtype, str) for x in str_cols]) # Datetime assert isinstance(new_list['Date/Time'], Time) @@ -196,15 +203,15 @@ def test_parse_risk(self): # Assert columns data types # Floats float_cols = ['IP max', 'PS max', 'Vel in km/s'] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) - # int64 + # ints int_cols = ['TS', 'Diameter in m'] - assert all([new_list[x].dtype == np.dtype('int64') for x in int_cols]) + assert all([np.issubdtype(new_list[x].dtype, int) for x in int_cols]) # String str_cols = ['Object Name', '*=Y'] - assert all([new_list[x].dtype.type == np.str_ for x in str_cols]) + assert all([np.issubdtype(new_list[x].dtype, str) for x in str_cols]) def test_parse_clo(self): """Check data: close_approaches_upcoming, @@ -241,14 +248,14 @@ def test_parse_clo(self): # Floats float_cols = ['Miss Distance in au', 'Miss Distance in LD', 'Diameter in m', 'H', 'Max Bright', 'Rel. vel in km/s'] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) # int64 - assert new_list['Miss Distance in km'].dtype == np.dtype('int64') + assert np.issubdtype(new_list['Miss Distance in km'].dtype, int) # Object str_cols = ['Object Name', '*=Yes'] - assert all([new_list[x].dtype.type == np.str_ for x in str_cols]) + assert all([np.issubdtype(new_list[x].dtype, str) for x in str_cols]) # Datetime assert isinstance(new_list['Date'], Time) @@ -284,14 +291,14 @@ def test_parse_pri(self): # Assert columns data types # Floats float_cols = ['R.A. in arcsec', 'Decl. in deg', 'V in mag'] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) # int64 int_cols = ['Priority', 'Elong. in deg', 'Sky uncert.'] - assert all([new_list[x].dtype == np.dtype('int64') for x in int_cols]) + assert all([np.issubdtype(new_list[x].dtype, int) for x in int_cols]) # Object - assert new_list["Object"].dtype.type == np.str_ + assert np.issubdtype(new_list["Object"].dtype, str) # Datetime assert isinstance(new_list['End of Visibility'], Time) @@ -328,11 +335,11 @@ def test_parse_encounter(self): # Assert columns data types # Floats float_cols = encounter_columns[3:] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) # Str str_cols = ['Name/desig', 'Planet'] - assert all([new_list[x].dtype.type == np.str_ for x in str_cols]) + assert all([np.issubdtype(new_list[x].dtype, str) for x in str_cols]) # Datetime assert isinstance(new_list['Date'], Time) @@ -397,15 +404,16 @@ def test_parse_neo_catalogue(self): float_cols = ['Epoch (MJD)', 'a', 'e', 'i', 'long. node', 'arg. peric.', 'mean anomaly', 'absolute magnitude', 'slope param.'] - assert all([new_list[x].dtype == np.dtype('float64') for x in float_cols]) + assert all([np.issubdtype(new_list[x].dtype, float) for x in float_cols]) # Object - assert new_list['Name'].dtype.type == np.str_ + assert np.issubdtype(new_list['Name'].dtype, str) # Int - assert new_list['non-grav param.'].dtype == np.dtype('int64') + assert np.issubdtype(new_list['non-grav param.'].dtype, int) +@pytest.mark.filterwarnings('ignore:ERFA function *:erfa.core.ErfaWarning') @pytest.mark.remote_data class TestTabs: """Class which contains the unitary tests for tabs module. @@ -413,6 +421,10 @@ class TestTabs: path_nea = os.path.join(DATA_DIR, 'allnea.csv') nea_list = Table.read(path_nea, format="ascii.csv") + # Ignore the FutureWarning that only comes up with the oldest dependencies + warnings.filterwarnings("ignore", category=FutureWarning, + message="Conversion of the second argument of issubdtype*") + def test_get_object_url(self): """Test for checking the URL termination for requested object tab. Check invalid list name raise KeyError. @@ -517,7 +529,7 @@ def test_tabs_summary(self): assert all([x == y for x, y in zip(summary.colnames, summary_cols)]) # Assert dtype - assert all([summary[x].dtype.type == np.str_ for x in summary_cols]) + assert all([np.issubdtype(summary[x].dtype, str) for x in summary_cols]) # Check specific asteroids that will remain inmutable ast_summ1 = neocc.neocc.query_object(name='433', tab='summary')[0] diff --git a/astroquery/solarsystem/neocc/__init__.py b/astroquery/solarsystem/neocc/__init__.py index de270f123d..bc7e3da36e 100644 --- a/astroquery/solarsystem/neocc/__init__.py +++ b/astroquery/solarsystem/neocc/__init__.py @@ -7,4 +7,6 @@ a collection of data services provided by NEOCC """ -# from ...esa.neocc import neocc, ESAneoccClass +from astroquery.esa.neocc import neocc, NEOCCClass + +__all__ = ["neocc", "NEOCCClass"] diff --git a/docs/esa/neocc.rst b/docs/esa/neocc/neocc.rst similarity index 96% rename from docs/esa/neocc.rst rename to docs/esa/neocc/neocc.rst index 13546746b8..83501b9a9f 100644 --- a/docs/esa/neocc.rst +++ b/docs/esa/neocc/neocc.rst @@ -1,9 +1,9 @@ .. _astroquery.esa.neocc: -************************************************************************************************ -ESA NEOCC Portal Python Interface Library (`astroquery.esa.neocc`/`astroquery.solarsystem.neocc`) -************************************************************************************************ +*********************************************************************************************** +ESA NEOCC Portal Python Interface Library (`astroquery.esa.neocc`/astroquery.solarsystem.neocc) +*********************************************************************************************** The ESA NEO Coordination Centre (NEOCC) is the operational centre of ESA’s Planetary Defence Office PDO) within the Space Safety Programme (S2P). Its aim is to coordinate and contribute to the @@ -27,6 +27,7 @@ Getting ESA NEOCC's products -------------------------------- 1. Direct download of list files -------------------------------- + This function allows the user to download the requested list data from ESA NEOCC. Different lists that can be requested are: @@ -49,8 +50,9 @@ These lists are referenced in ``_. -------------------------------- Examples -------------------------------- + **NEA list:** The output -of this list is a `astropy.table.Table` which contains the list of all NEAs +of this list is a `~astropy.table.Table` which contains the list of all NEAs currently considered in the NEOCC system. .. doctest-remote-data:: @@ -82,7 +84,7 @@ be used as input for *query_object* method. >>> print(list_data["NEA"][4]) 1221 Amor -**Close approaches:** The output of this list is a `astropy.table.Table` which +**Close approaches:** The output of this list is a `~astropy.table.Table` which contains information about asteroid close approaches. .. doctest-remote-data:: @@ -131,6 +133,7 @@ These properties are referenced in ``_. -------------------------------- Examples -------------------------------- + **Impacts, Physical Properties and Observations**: This example tries to summarize how to access the data of this tabs and how to use it. Note that this classes only require as inputs the name of @@ -221,7 +224,7 @@ in the 'meta' property, including information about the table columns. **Observations:** In this example we query for Observations tables, a query that -returns a list containing 3-5 `astropy.table.Table`s depending if there are +returns a list containing 3-5 `~astropy.table.Table` objects depending if there are "Roving observer" or satellite observations. @@ -274,7 +277,7 @@ which results in a single data table. **Orbit Properties:** In order to access the orbital properties information, it is necessary to provide two additional inputs to -*query_object* method: `orbital_elements` and `orbit_epoch`. +*query_object* method: "orbital_elements" and "orbit_epoch". This query returns a list of three tables, the orbital properties, and the covariance and corotation matrices. @@ -300,7 +303,7 @@ and corotation matrices. **Ephemerides:** In order to access ephemerides information, it is necessary to provide five additional inputs to *query_object* -method: `observatory`, `start`, `stop`, `step` and `step_unit`. +method: "observatory", "start", "stop", "step" and "step_unit". .. doctest-remote-data:: @@ -325,3 +328,9 @@ method: `observatory`, `start`, `stop`, `step` and `step_unit`. 2019-05-22T01:30:00.000 58625.0625 7 34 4.357 ... 0.001 0.001 155.2 2019-05-23T01:30:00.000 58626.0625 7 37 36.303 ... 0.001 0.001 158.7 + +Reference/API +============= + +.. automodapi:: astroquery.esa.neocc + :no-inheritance-diagram: diff --git a/docs/index.rst b/docs/index.rst index c754417320..e500200e16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -245,6 +245,7 @@ The following modules have been completed using a common API: esa/iso/iso.rst esa/jwst/jwst.rst esa/xmm_newton/xmm_newton.rst + esa/neocc/neocc.rst esasky/esasky.rst eso/eso.rst image_cutouts/first/first.rst