Repository: astropy/pyvo

Issue:
Bug: DALQueryError caused by interpolated `np.float64`
This might be a bit of a duplicate of https://github.com/astropy/pyvo/issues/568 or at the very least related.

I was following [Markus's pyvo-course](https://codeberg.org/msdemlei/pyvo-course) and hit a `DALQueryError`. 

Stripping down Markus's code to a MRE:
```py
from pyvo import registry
from astropy.coordinates import SkyCoord

result = registry.search(
    registry.Spatial((SkyCoord.from_name("M42"), 2))
)
```
Output:
```
...
  File "/home/lilith/.local/share/hatch/env/virtual/vo/nmg7WNbb/vo/lib/python3.12/site-packages/pyvo/registry/regtap.py", line 271, in execute
    return RegistryResults(self.execute_votable(), url=self.queryurl)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lilith/.local/share/hatch/env/virtual/vo/nmg7WNbb/vo/lib/python3.12/site-packages/pyvo/dal/query.py", line 336, in __init__
    raise DALQueryError(self._status[1], self._status[0], url)
pyvo.dal.exceptions.DALQueryError: Field query: Could not parse your query: Expected ")", found ','  (at char 781), (line:13, col:78)
```
Looking at the query it generates by replacing `search` with `get_RegTAP_query`:
```sql
SELECT
ivoid, res_type, short_name, res_title, content_level, res_description, reference_url, creator_seq, created, updated, rights, content_type, source_format, source_value, region_of_regard, waveband,
  ivo_string_agg(COALESCE(access_url, ''), ':::py VO sep:::') AS access_urls,
  ivo_string_agg(COALESCE(standard_id, ''), ':::py VO sep:::') AS standard_ids,
  ivo_string_agg(COALESCE(intf_type, ''), ':::py VO sep:::') AS intf_types,
  ivo_string_agg(COALESCE(intf_role, ''), ':::py VO sep:::') AS intf_roles,
  ivo_string_agg(COALESCE(cap_description, ''), ':::py VO sep:::') AS cap_descriptions
FROM
rr.resource
NATURAL LEFT OUTER JOIN rr.capability
NATURAL LEFT OUTER JOIN rr.interface
WHERE
(ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_spatial WHERE 1 = CONTAINS(MOC(6, CIRCLE(np.float64(83.8201), np.float64(-5.3876), 2)), coverage)))
GROUP BY
ivoid, res_type, short_name, res_title, content_level, res_description, reference_url, creator_seq, created, updated, rights, content_type, source_format, source_value, region_of_regard, waveband
```

I am brand new to `pyvo` but having `np.float64` looks pretty odd, and I found some related issues (e.g. https://github.com/astropy/pyvo/issues/479). 

My guess is that these lines are the culprit:
https://github.com/astropy/pyvo/blob/99e0fd93b3c388e18cbef55084992c6dd09095c3/pyvo/registry/rtcons.py#L108-L109

But I'm not sure what the preferred approach to fixing this is, since it seems to be something discussed in many repositories.

Posting this just to let you know! Thanks :)

---

```
$ python3 --version
Python 3.12.7
$ pip freeze | grep -e pyvo== -e astropy== -e numpy==
astropy==7.0.0
numpy==2.1.3
pyvo==1.6
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index ce227a9..fef5c8d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -17,6 +17,8 @@ Bug Fixes
 
 - Fix propagating some previously swallowed exceptions. [#614]
 
+- Fix string literal generation for SQL query when using numpy >=2.0. [#624]
+
 
 1.6 (2024-11-01)
 ================
diff --git a/pyvo/dal/query.py b/pyvo/dal/query.py
index beedd97..2bb187a 100644
--- a/pyvo/dal/query.py
+++ b/pyvo/dal/query.py
@@ -695,7 +695,7 @@ class Record(Mapping):
         return len(self._mapping)
 
     def __repr__(self):
-        return repr(tuple(self.values()))
+        return repr(tuple(f'{val}' for val in self.values()))
 
     def get(self, key, default=None, decode=False):
         """
diff --git a/pyvo/registry/rtcons.py b/pyvo/registry/rtcons.py
index 262e94d..0f37db6 100644
--- a/pyvo/registry/rtcons.py
+++ b/pyvo/registry/rtcons.py
@@ -102,11 +102,8 @@ def make_sql_literal(value):
     elif isinstance(value, bytes):
         return "'{}'".format(value.decode("ascii").replace("'", "''"))
 
-    elif isinstance(value, (int, numpy.integer)):
-        return "{:d}".format(value)
-
-    elif isinstance(value, (float, numpy.floating)):
-        return repr(value)
+    elif isinstance(value, (int, numpy.integer, float, numpy.floating)):
+        return f'{value}'
 
     elif isinstance(value, datetime.datetime):
         return "'{}'".format(value.isoformat())


Test patch:
diff --git a/conftest.py b/conftest.py
index b81c77d..2c83886 100644
--- a/conftest.py
+++ b/conftest.py
@@ -40,11 +40,6 @@ from astropy.utils.iers import conf as iers_conf
 iers_conf.auto_download = False
 
 
-# Keep this until we require numpy to be >=2.0
-if minversion(np, "2.0.0.dev0+git20230726"):
-    np.set_printoptions(legacy="1.25")
-
-
 def pytest_configure(config):
     """Configure Pytest with Astropy.
 
diff --git a/pyvo/dal/tests/test_query.py b/pyvo/dal/tests/test_query.py
index 177d4d5..bed62bf 100644
--- a/pyvo/dal/tests/test_query.py
+++ b/pyvo/dal/tests/test_query.py
@@ -461,7 +461,7 @@ class TestRecord:
         record = DALResults.from_result_url(
             'http://example.com/query/basic')[0]
         truth = 'Illuminatus'
-        assert repr(record) == repr((23, truth))
+        assert repr(record) == repr(('23', truth))
 
     def test_get(self):
         record = DALResults.from_result_url(
diff --git a/pyvo/registry/tests/test_rtcons.py b/pyvo/registry/tests/test_rtcons.py
index 86467d0..79e78a2 100644
--- a/pyvo/registry/tests/test_rtcons.py
+++ b/pyvo/registry/tests/test_rtcons.py
@@ -11,7 +11,7 @@ from astropy import units as u
 from astropy.coordinates import SkyCoord
 from astropy.utils.exceptions import AstropyDeprecationWarning
 
-import numpy
+import numpy as np
 import pytest
 
 from pyvo import registry
@@ -21,6 +21,10 @@ from pyvo.dal import query as dalq
 from .commonfixtures import messenger_vocabulary, FAKE_GAVO, FAKE_PLAIN  # noqa: F401
 
 
+# We should make sure non-legacy numpy works as expected for string literal generation
+np.set_printoptions(legacy=False)
+
+
 def _build_regtap_query_with_fake(
         *args,
         service=FAKE_GAVO,
@@ -52,7 +56,7 @@ class TestSQLLiterals:
                 "bytes": b"keep this ascii for now",
                 "anInt": 210,
                 "aFloat": 5e7,
-                "numpyStuff": numpy.float64(23.7),
+                "numpyStuff": np.float64(23.7),
                 "timestamp": datetime.datetime(2021, 6, 30, 9, 1), }
 
         return _WithFillers()._get_sql_literals()