-
-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mivot writer #627
Open
lmichel
wants to merge
47
commits into
astropy:main
Choose a base branch
from
lmichel:mivot-writer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Mivot writer #627
Changes from 43 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
89d8dae
add a clean ns option for the pretty printing
lmichel 5fddfcd
layout changed after a read/write cycle
lmichel 89c6fe3
local copy of the MIVOT XML schema
lmichel cf7fc92
annotation builder: new class
lmichel db49366
instance builder: new class
lmichel 4803139
test suite for the annotations builders
lmichel 34aacff
API for writing MIVOT annotations
lmichel 1b6197b
test suite for the MIVOT writer
lmichel f367116
Doc updated with the split into 2 documents, one for the reader and one
lmichel 7a9cff4
Changelog updated
lmichel c8b8549
code style
lmichel 3ad6f43
style corrections
lmichel 79c01b8
add PR number in change log
lmichel 9e20fd3
removed
lmichel 55cc826
add PR number
lmichel a622b32
resolve conflicts
lmichel be59549
add writer to api index
lmichel c68dfaa
Merge branch 'main' into mivot-writer
lmichel f9917ed
fix CI
lmichel c7cfe7b
Merge branch 'mivot-writer' of github.com:lmichel/pyvo into mivot-writer
lmichel 7e0c918
fix use of mapping exception
lmichel 4b40de5
add package init
lmichel c0068aa
fix call to MivotInstance dict
lmichel e9e273e
typo
lmichel 28ee3ed
test MivotViewer.to_dict() output
lmichel a86c8f2
Fix CI errors
lmichel d74d094
add missing doctring for clean_ns parameter of pretty_string()
lmichel 150acba
merge after rebase
lmichel 9462516
fix test output in rest file
lmichel 9277c03
flake8
lmichel b2efc78
Fix CI errors
lmichel 2dee869
Merge branch 'mivot-writer' of github.com:lmichel/pyvo into mivot-writer
lmichel d36398b
restructuring documentation : toc updated, exposed API
lmichel 1b5f460
Fix code blocks in docstring that were tagged as sphinx reference (one
lmichel 514b48b
remove narrative doc from source files
lmichel b79706c
replace abbreviation with explicit name (clean_ns)
lmichel 4932477
add explicit definition of the exposed namespaces (__all__)
lmichel b8021e9
add license
lmichel 0303a85
Make optional parameters mandatory keywords.
lmichel 3d9dec5
import viewer explicitely because pyvo.mivot.viewer is no longer the
lmichel bff20a5
XML typography more homogeneous, parameter model_url of add_model made
lmichel bab58f2
fix CI errors
lmichel d470071
DOC: fix headings
bsipocz c39489b
mv #627 to 1.7 section
lmichel 3e9c6a4
Merge branch 'main' into mivot-writer
lmichel f030f35
#627 review
lmichel d7c73e8
Merge branch 'mivot-writer' of [email protected]:lmichel/pyvo.git into m…
lmichel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,8 +1,8 @@ | ||||||
******************** | ||||||
MIVOT (`pyvo.mivot`) | ||||||
******************** | ||||||
********************** | ||||||
MIVOT (``pyvo.mivot``) | ||||||
********************** | ||||||
|
||||||
This module contains the new feature of annotations in VOTable. | ||||||
This module contains the new feature handling model annotations in VOTable. | ||||||
Astropy version >= 6.0 is required. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds better IMO.
Suggested change
|
||||||
|
||||||
Introduction | ||||||
|
@@ -28,7 +28,7 @@ Introduction | |||||
|
||||||
|
||||||
Implementation Scope | ||||||
-------------------- | ||||||
==================== | ||||||
This implementation is totally model-agnostic. | ||||||
|
||||||
- It does not operate any validation against specific data models. | ||||||
|
@@ -42,8 +42,8 @@ Some of the examples have been provided by a special end-point of the Vizier con | |||||
(https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch) that maps query results to this model. | ||||||
|
||||||
.. image:: _images/mangoEpochPosition.png | ||||||
:width: 500 | ||||||
:alt: EpochPropagation class used to validate this api. | ||||||
:width: 500 | ||||||
:alt: EpochPropagation class used to validate this api. | ||||||
|
||||||
It is to be noted that the Vizier service does not annotate errors at the time of writing (Q1 2024) | ||||||
|
||||||
|
@@ -56,191 +56,23 @@ which allows to get (and set) Mivot blocks from/into VOTables as an XML element | |||||
epoch propagation use case: | ||||||
|
||||||
- ``JOIN`` features are not supported. | ||||||
- ``TEMPLATES`` with more than one ``INSANCE`` not supported. | ||||||
|
||||||
Integrated Readout | ||||||
------------------ | ||||||
The ``ModelViewer`` module manages access to data mapped to a model through dynamically | ||||||
generated objects (``MivotInstance``class). | ||||||
The example below shows how a VOTable, resulting from a cone-search query which data are mapped | ||||||
to the ``EpochPosition`` class, can be consumed. | ||||||
|
||||||
.. doctest-remote-data:: | ||||||
>>> import astropy.units as u | ||||||
>>> from astropy.coordinates import SkyCoord | ||||||
>>> from pyvo.dal.scs import SCSService | ||||||
>>> from pyvo.utils.prototype import activate_features | ||||||
>>> from pyvo.mivot.version_checker import check_astropy_version | ||||||
>>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||||||
>>> activate_features("MIVOT") | ||||||
>>> if check_astropy_version() is False: | ||||||
... pytest.skip("MIVOT test skipped because of the astropy version.") | ||||||
>>> scs_srv = SCSService("https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch/I/239/hip_main") | ||||||
>>> m_viewer = MivotViewer( | ||||||
... scs_srv.search( | ||||||
... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'), | ||||||
... radius=0.05 | ||||||
... ) | ||||||
... ) | ||||||
>>> mivot_instance = m_viewer.dm_instance | ||||||
>>> print(mivot_instance.dmtype) | ||||||
mango:EpochPosition | ||||||
>>> print(mivot_instance.coordSys.spaceRefFrame.value) | ||||||
ICRS | ||||||
>>> while m_viewer.next(): | ||||||
... print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}") | ||||||
position: 59.94033461 52.26722684 | ||||||
|
||||||
|
||||||
In this example, the data readout is totally managed by the ``MivotViewer`` instance. | ||||||
The ``astropy.io.votable`` API is encapsulated in this module. | ||||||
|
||||||
Model leaves (class attributes) are complex types that provide additional information: | ||||||
|
||||||
- ``value``: attribute value | ||||||
- ``dmtype``: attribute type such as defined in the Mivot annotations | ||||||
- ``unit``: attribute unit such as defined in the Mivot annotations | ||||||
- ``ref``: identifier of the table column mapped on the attribute | ||||||
|
||||||
The model view on a data row can also be passed as a Python dictionary | ||||||
using the ``to_dict()`` method of ``MivotInstance``. | ||||||
|
||||||
.. code-block:: python | ||||||
:caption: Working with a model view as a dictionary | ||||||
(the JSON layout has been squashed for display purpose) | ||||||
|
||||||
from pyvo.mivot import MivotViewer | ||||||
from pyvo.mivot.utils.dict_utils import DictUtils | ||||||
|
||||||
m_viewer = MivotViewer(path_to_votable) | ||||||
mivot_instance = m_viewer.dm_instance | ||||||
mivot_object_dict = mivot_object.to_dict() | ||||||
|
||||||
DictUtils.print_pretty_json(mivot_object_dict) | ||||||
{ | ||||||
"dmtype": "mango:EpochPosition", | ||||||
"longitude": {"value": 359.94372764, "unit": "deg"}, | ||||||
"latitude": {"value": -0.28005255, "unit": "deg"}, | ||||||
"pmLongitude": {"value": -5.14, "unit": "mas/yr"}, | ||||||
"pmLatitude": {"value": -25.43, "unit": "mas/yr"}, | ||||||
"epoch": {"value": 1991.25, "unit": "year"}, | ||||||
"coordSys": { | ||||||
"dmtype": "coords:SpaceSys", | ||||||
"dmid": "ICRS", | ||||||
"dmrole": "coords:Coordinate.coordSys", | ||||||
"spaceRefFrame": {"value": "ICRS"}, | ||||||
}, | ||||||
} | ||||||
|
||||||
- It is recommended to use a copy of the | ||||||
dictionary as it will be rebuilt each time the ``to_dict()`` method is invoked. | ||||||
- The default representation of ``MivotInstance`` instances is made with a pretty | ||||||
string serialization of this dictionary (method ``__repr__()``). | ||||||
- An extended version of the object dictionary e.g. with information about where | ||||||
the values were picked from from, is available using the method ``to_hk_dict()``. | ||||||
|
||||||
Per-Row Readout | ||||||
--------------- | ||||||
|
||||||
The annotation schema can also be applied to table rows read outside of the ``MivotViewer`` | ||||||
with the `astropy.io.votable` API: | ||||||
|
||||||
.. code-block:: python | ||||||
:caption: Accessing the model view of Astropy table rows | ||||||
|
||||||
votable = parse(path_to_votable) | ||||||
table = votable.resources[0].tables[0] | ||||||
# init the viewer | ||||||
mivot_viewer = MivotViewer(votable, resource_number=0) | ||||||
mivot_object = mivot_viewer.dm_instance | ||||||
# and feed it with the table row | ||||||
read = [] | ||||||
for rec in table.array: | ||||||
mivot_object.update(rec) | ||||||
read.append(mivot_object.longitude.value) | ||||||
# show that the model retrieve the correct data values | ||||||
assert rec["RAICRS"] == mivot_object.longitude.value | ||||||
assert rec["DEICRS"] == mivot_object.latitude.value | ||||||
|
||||||
In this case, it is up to the user to ensure that the read data rows are those mapped by the Mivot annotations. | ||||||
|
||||||
Get a SkyCoord Instance Directly From the Annotations | ||||||
----------------------------------------------------- | ||||||
|
||||||
Once you get a ``MivotInstance`` representing the last row read, you can use it to create an ``astropy.SkyCoord`` object. | ||||||
|
||||||
.. code-block:: python | ||||||
:caption: Accessing the model view of Astropy table rows | ||||||
|
||||||
from pyvo.mivot import MivotViewer | ||||||
|
||||||
m_viewer = MivotViewer(path_to_votable) | ||||||
mivot_instance = m_viewer.dm_instance | ||||||
print(mivot_instance.get_SkyCoord()) | ||||||
<SkyCoord (ICRS): (ra, dec) in deg(52.26722684, 59.94033461) | ||||||
(pm_ra_cosdec, pm_dec) in mas / yr(-0.82, -1.85)> | ||||||
|
||||||
This feature works under the condition that the annotations contain a valid instance of ``mango:EPochPosition``, otherwise | ||||||
a ``NoMatchingDMTypeError`` is thrown. | ||||||
Although not a standard at the time of writing, the class structure supported by this implementation must match the figure above. | ||||||
|
||||||
|
||||||
For XML Hackers | ||||||
--------------- | ||||||
|
||||||
The model instances can also be serialized as XML elements that can be parsed with XPath queries. | ||||||
|
||||||
.. code-block:: python | ||||||
:caption: Accessing the XML view of the mapped model instances | ||||||
|
||||||
with MivotViewer(path_to_votable) as mivot_viewer: | ||||||
while mivot_viewer.next(): | ||||||
xml_view = mivot_viewer.xml_view | ||||||
# do whatever you want with this XML element | ||||||
|
||||||
It to be noted that ``mivot_viewer.xml_view`` is a shortcut | ||||||
for ``mivot_viewer.xml_view.view`` where ``mivot_viewer.xml_view`` | ||||||
is is an instance of ``pyvo.mivot.viewer.XmlViewer``. | ||||||
This object provides many functions facilitating the XML parsing. | ||||||
|
||||||
Class Generation in a Nutshell | ||||||
------------------------------ | ||||||
|
||||||
MIVOT reconstructs model structures with 3 elements: | ||||||
|
||||||
- ``INSTANCE`` for the objects | ||||||
- ``ATTRIBUTE`` for the attributes | ||||||
- ``COLLECTION`` for the elements with a cardinality greater than 1 | ||||||
|
||||||
The role played by each of these elements in the model hierarchy is defined | ||||||
by its ``@dmrole`` XML attribute. Types of both ``INSTANCE`` and ``ATTRIBUTE`` are defined by | ||||||
their ``@dmtype`` XML attributes. | ||||||
|
||||||
``MivotInstance`` classes are built by following MIVOT annotation structure: | ||||||
|
||||||
- ``INSTANCE`` are represented by Python classes | ||||||
- ``ATTRIBUTE`` are represented by Python class fields | ||||||
- ``COLLECTION`` are represented by Python lists ([]) | ||||||
|
||||||
``@dmrole`` and ``@dmtype`` cannot be used as Python keywords as such, because they are built from VO-DML | ||||||
identifiers, which have the following structure: ``model:a.b``. | ||||||
|
||||||
- Only the last part of the path is kept for attribute names. | ||||||
- For class names, forbidden characters (``:`` or ``.``) are replaced with ``_``. | ||||||
- Original ``@dmtype`` are kept as attributes of generated Python objects. | ||||||
- The structure of the ``MivotInstance`` objects can be inferred from the mapped model in 2 different ways: | ||||||
- ``TEMPLATES`` with more than one ``INSTANCE`` not supported. | ||||||
|
||||||
|
||||||
Using the MIVOT package | ||||||
======================= | ||||||
|
||||||
The ``pyvo.mivot`` module can be used to either read or build annotations. | ||||||
|
||||||
.. toctree:: | ||||||
:maxdepth: 2 | ||||||
|
||||||
viewer | ||||||
writer | ||||||
|
||||||
- 1. From the MIVOT instance property ``MivotInstance.to_dict()`` a shown above. | ||||||
This is a pure Python dictionary but its access can be slow because it is generated | ||||||
on the fly each time the property is invoked. | ||||||
- 2. From the internal class dictionary ``MivotInstance.__dict__`` | ||||||
(see the Python `data model <https://docs.python.org/3/reference/datamodel.html>`_). | ||||||
|
||||||
Reference/API | ||||||
============= | ||||||
|
||||||
.. automodapi:: pyvo.mivot | ||||||
.. automodapi:: pyvo.mivot.viewer | ||||||
.. automodapi:: pyvo.mivot.seekers | ||||||
bsipocz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
.. automodapi:: pyvo.mivot.features | ||||||
.. automodapi:: pyvo.mivot.utils | ||||||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering what happened here since this is not your change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand the issue, this relates to 1.6.