pyvo
Advanced tools
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>myjobid</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <status>initial</status> | ||
| <status>processing</status> | ||
| <status>completed</status> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>myjobid</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <!-- Empty jobInfo element --> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>myjobid</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <tap:progress xmlns:tap="http://example-tap.org">50</tap:progress> | ||
| <custom:progress xmlns:custom="http://example-custom.org">75</custom:progress> | ||
| <uniqueElement>no collision</uniqueElement> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>jobid123</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <queryInfo> | ||
| <metrics> | ||
| <execution_time>1500</execution_time> | ||
| <rows_returned>100</rows_returned> | ||
| </metrics> | ||
| </queryInfo> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>test123</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <tapQueryInfo> | ||
| <pct_complete>100</pct_complete> | ||
| <chunks_processed>1</chunks_processed> | ||
| <total_chunks>1</total_chunks> | ||
| </tapQueryInfo> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> | ||
| <uws:jobId>myid123</uws:jobId> | ||
| <uws:ownerId/> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:creationTime>2025-06-04T00:00:00Z</uws:creationTime> | ||
| <uws:startTime>2025-06-04T00:05:00Z</uws:startTime> | ||
| <uws:endTime>2025-06-04T02:00:00Z</uws:endTime> | ||
| <uws:executionDuration>7200</uws:executionDuration> | ||
| <uws:destruction>2025-06-04T00:00:00Z</uws:destruction> | ||
| <uws:parameters> | ||
| <uws:parameter id="query">SELECT * FROM table</uws:parameter> | ||
| </uws:parameters> | ||
| <uws:results> | ||
| <uws:result id="result" xlink:href="http://example.com/result"/> | ||
| </uws:results> | ||
| <uws:jobInfo> | ||
| <integer_value>100</integer_value> | ||
| <float_value>3.14</float_value> | ||
| <string_value>pyvo</string_value> | ||
| <empty_value></empty_value> | ||
| </uws:jobInfo> | ||
| </uws:job> |
| <?xml version="1.0" encoding="utf-8"?> | ||
| <VOTABLE version="1.4" | ||
| xmlns="http://www.ivoa.net/xml/VOTable/v1.3" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/votable-1.4.xsd"> | ||
| <RESOURCE name="simbad_cone-search_result" type="results"> | ||
| <INFO name="standardID" value="ivo://ivoa.net/std/conesearch">IVOID of the service specification</INFO> | ||
| <INFO name="service_protocol" value="Simple Cone Search 1.03-extended">Used access protocol and version</INFO> | ||
| <INFO name="publisher" value="CDS/SIMBAD">Data publisher</INFO> | ||
| <INFO name="request" value="https://simbad.cds.unistra.fr/cone/?RA=269.4521&DEC=4.693365&SR=0.01&VERB=2&MAXREC=1&ORDER_BY=distance&ORDER_DIR=ASC&RESPONSEFORMAT=application%2Fx-votable%2Bxml%3Bcontent%3Dmivot%3Bserialization%3DTABLEDATA%3Bcharset%3DUTF-8">HTTP request URL</INFO> | ||
| <INFO name="request_date" value="2025-09-23T15:01:26.078545Z">Query execution date</INFO> | ||
| <INFO name="contact" value="cds-question@unistra.fr">Publisher email address</INFO> | ||
| <INFO name="QUERY_STATUS" value="OK">Successful query</INFO> | ||
| <COOSYS ID="SIMBAD-COOSYS" system="ICRS" epoch="J2000" equinox="2000"/> | ||
| <RESOURCE type="meta"> | ||
| <VODML xmlns="http://www.ivoa.net/xml/mivot"> | ||
| <REPORT status="OK"/> | ||
| <MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml"/> | ||
| <MODEL name="coords" url="https://ivoa.net/xml/VODML/Coords-v1.0.vo-dml.xml"/> | ||
| <MODEL name="mango" url="https://raw.githubusercontent.com/ivoa-std/MANGO/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml"/> | ||
| <GLOBALS> | ||
| <INSTANCE dmtype="coords:SpaceSys" dmid="_spaceframe_ICRS_2000_BARYCENTER"> | ||
| <INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame"> | ||
| <ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="ICRS"/> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| </GLOBALS> | ||
| <TEMPLATES> | ||
| <INSTANCE dmtype="mango:MangoObject" dmid="main_id"> | ||
| <ATTRIBUTE dmtype="ivoa:string" dmrole="mango:MangoObject.identifier" ref="main_id"/> | ||
| <COLLECTION dmrole="mango:MangoObject.propertyDock"> | ||
| <INSTANCE dmtype="mango:EpochPosition"> | ||
| <ATTRIBUTE dmtype="ivoa:string" dmrole="mango:Property.description" value="6 parameters position"/> | ||
| <INSTANCE dmtype="mango:VocabularyTerm" dmrole="mango:Property.semantics"> | ||
| <ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.uri" value="https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location"/> | ||
| <ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.label" value="Astronomical location"/> | ||
| </INSTANCE> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.longitude" unit="deg" ref="ra"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.latitude" unit="deg" ref="dec"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLongitude" unit="mas / yr" ref="pmra"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLatitude" unit="mas / yr" ref="pmdec"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.parallax" unit="mas" ref="plx_value"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.radialVelocity" unit="km / s" ref="rvz_radvel"/> | ||
| <INSTANCE dmrole="mango:EpochPosition.obsDate" dmtype="mango:DateTime"> | ||
| <ATTRIBUTE dmrole="mango:DateTime.representation" dmtype="ivoa:string" value="year"/> | ||
| <ATTRIBUTE dmrole="mango:DateTime.dateTime" dmtype="ivoa:datetime" value="2000" unit="y"/> | ||
| </INSTANCE> | ||
| <INSTANCE dmtype="mango:EpochPositionErrors" dmrole="mango:EpochPosition.errors"> | ||
| <INSTANCE dmtype="mango:error.PErrorEllipse" dmrole="mango:EpochPositionErrors.position"> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.semiMajorAxis" unit="mas" ref="coo_err_maja"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.semiMinorAxis" unit="mas" ref="coo_err_mina"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.angle" unit="deg" ref="coo_err_angle"/> | ||
| </INSTANCE> | ||
| <INSTANCE dmtype="mango:error.PErrorEllipse" dmrole="mango:EpochPositionErrors.properMotion"> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.semiMajorAxis" unit="mas / yr" ref="pm_err_maja"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.semiMinorAxis" unit="mas / yr" ref="pm_err_mina"/> | ||
| <ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorEllipse.angle" unit="deg" ref="pm_err_angle"/> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| <REFERENCE dmrole="mango:EpochPosition.spaceSys" dmref="_spaceframe_ICRS_2000_BARYCENTER"/> | ||
| </INSTANCE> | ||
| </COLLECTION> | ||
| </INSTANCE> | ||
| </TEMPLATES> | ||
| </VODML> | ||
| </RESOURCE> | ||
| <TABLE> | ||
| <FIELD datatype="float" name="distance" ucd="pos.angDistance" unit="deg"> | ||
| <DESCRIPTION>Distance (in degrees) between this object and the cone-search's target.</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD ID="main_id" arraysize="*" datatype="char" name="main_id" ucd="meta.id;meta.main"> | ||
| <DESCRIPTION>Main identifier for an object</DESCRIPTION> | ||
| <LINK href="https://simbad.cds.unistra.fr/simbad/sim-id?Ident=${main_id}&NbIdent=1"/> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="ra" ref="SIMBAD-COOSYS" ucd="pos.eq.ra;meta.main" unit="deg"> | ||
| <DESCRIPTION>Right ascension</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="dec" ref="SIMBAD-COOSYS" ucd="pos.eq.dec;meta.main" unit="deg"> | ||
| <DESCRIPTION>Declination</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD arraysize="*" datatype="char" name="otype" ucd="src.class"> | ||
| <DESCRIPTION>Object type</DESCRIPTION> | ||
| <LINK href="https://simbad.cds.unistra.fr/guide/otypes.htx#${otype}"/> | ||
| </FIELD> | ||
| <FIELD arraysize="*" datatype="char" name="coo"/> | ||
| <FIELD datatype="double" name="coo_err_maja" ucd="phys.angSize.smajAxis;pos.errorEllipse;pos.eq" unit="mas"> | ||
| <DESCRIPTION>Coordinate error major axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="coo_err_mina" ucd="phys.angSize.sminAxis;pos.errorEllipse;pos.eq" unit="mas"> | ||
| <DESCRIPTION>Coordinate error minor axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="short" name="coo_err_angle" ucd="pos.posAng;pos.errorEllipse;pos.eq" unit="deg"> | ||
| <DESCRIPTION>Coordinate error angle</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="pmra" ucd="pos.pm;pos.eq.ra" unit="mas.yr-1"> | ||
| <DESCRIPTION>Proper motion in RA</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="pmdec" ucd="pos.pm;pos.eq.dec" unit="mas.yr-1"> | ||
| <DESCRIPTION>Proper motion in DEC</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="pm_err_maja" ucd="phys.angSize.smajAxis;pos.errorEllipse;pos.pm" unit="mas.yr-1"> | ||
| <DESCRIPTION>Proper motion error major axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="pm_err_mina" ucd="phys.angSize.sminAxis;pos.errorEllipse;pos.pm" unit="mas.yr-1"> | ||
| <DESCRIPTION>Proper motion error minor axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="short" name="pm_err_angle" ucd="pos.posAng;pos.errorEllipse;pos.pm" unit="deg"> | ||
| <DESCRIPTION>Proper motion error angle</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="plx_value" ucd="pos.parallax.trig" unit="mas"> | ||
| <DESCRIPTION>Parallax</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="rvz_radvel" ucd="spect.dopplerVeloc.opt" unit="km.s-1"> | ||
| <DESCRIPTION>Radial Velocity</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="galdim_majaxis" ucd="phys.angSize.smajAxis" unit="arcmin"> | ||
| <DESCRIPTION>Angular size major axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="double" name="galdim_minaxis" ucd="phys.angSize.sminAxis" unit="arcmin"> | ||
| <DESCRIPTION>Angular size minor axis</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="short" name="galdim_angle" ucd="pos.posAng" unit="deg"> | ||
| <DESCRIPTION>Galaxy ellipse angle</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD arraysize="*" datatype="char" name="sp_type" ucd="src.spType"> | ||
| <DESCRIPTION>MK spectral type</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD arraysize="*" datatype="char" name="morph_type" ucd="src.morph.type"> | ||
| <DESCRIPTION>Morphological type</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="int" name="biblist"/> | ||
| <DATA> | ||
| <TABLEDATA> | ||
| <TR> | ||
| <TD>1.0409523503457668E-5</TD> | ||
| <TD>NAME Barnard's Star c</TD> | ||
| <TD>269.45207695861900</TD> | ||
| <TD>4.69336496657667</TD> | ||
| <TD>Planet</TD> | ||
| <TD> 17 57 48.4984700683 +04 41 36.113879675</TD> | ||
| <TD>0.0262</TD> | ||
| <TD>0.0290</TD> | ||
| <TD>90</TD> | ||
| <TD>-801.551</TD> | ||
| <TD>10362.394</TD> | ||
| <TD>0.032</TD> | ||
| <TD>0.036</TD> | ||
| <TD>90</TD> | ||
| <TD>546.9759</TD> | ||
| <TD></TD> | ||
| <TD></TD> | ||
| <TD></TD> | ||
| <TD></TD> | ||
| <TD></TD> | ||
| <TD></TD> | ||
| <TD>1</TD> | ||
| </TR> | ||
| </TABLEDATA> | ||
| </DATA> | ||
| </TABLE> | ||
| <INFO name="QUERY_STATUS" value="OVERFLOW">Truncated result</INFO> | ||
| </RESOURCE> | ||
| </VOTABLE> |
| <?xml version="1.0" encoding="utf-8"?> | ||
| <VOTABLE version="1.3" xmlns="http://www.ivoa.net/xml/VOTable/v1.3" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/v1.3"> | ||
| <RESOURCE type="results"> | ||
| <INFO name="QUERY_STATUS" value="OK" /> | ||
| <INFO name="QUERY" | ||
| value='SELECT TOP 1 "public".mergedentry.SC_EP_1_FLUX,"public".mergedentry.SC_EP_2_FLUX,"public".mergedentry.SC_EP_3_FLUX | ||
| FROM "public".mergedentry | ||
| ' /> | ||
| <!-- Here starts the mapping block This bloc maps all data contained in the VOTable on the MANGO MODEL_INSTANCE. The MODEL_INSTANCEInstanceInVot | ||
| syntax is detailed here https://github.com/ivoa-std/MODEL_INSTANCEinstanceinvot with a lot of snippet here https://github.com/ivoa/MODEL_INSTANCEinstanceinvot-code --> | ||
| <RESOURCE type="meta"> | ||
| <VODML xmlns="http://www.ivoa.net/xml/mivot"> | ||
| <REPORT status="OK">Automatically annotated by XTAPDB</REPORT> | ||
| <MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" /> | ||
| <MODEL name="coords" url="https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml" /> | ||
| <MODEL name="meas" url="https://www.ivoa.net/xml/Meas/20200908/Meas-v1.0.vo-dml.xml" /> | ||
| <MODEL name="phot" url="https://ivoa.net/xml/VODML/Phot-v1.vodml.xml" /> | ||
| <MODEL name="mango" url="https://github.com/ivoa-std/MANGO/raw/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml" /> | ||
| <!-- The GLOBALS block contains all objects with a scope covering all data. This is typically the case for the coordinate | ||
| Systems --> | ||
| <GLOBALS> | ||
| <INSTANCE dmid="CoordSystem_XMM_EB1_id" dmtype="Phot:PhotCal"> | ||
| <ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB1" /> | ||
| <!-- Magnitude System --> | ||
| <INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem"> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="XMM" /> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.referenceSpectrum" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| </INSTANCE> | ||
| <!-- Filter --> | ||
| <REFERENCE dmref="CoordSystem_XMM_FILTER_EB1_id" dmrole="Phot:PhotCal.photometryFilter" /> | ||
| </INSTANCE> | ||
| <INSTANCE dmid="CoordSystem_XMM_EB2_id" dmtype="Phot:PhotCal"> | ||
| <ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB2" /> | ||
| <!-- Magnitude System --> | ||
| <INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem"> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="XMM" /> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.referenceSpectrum" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| </INSTANCE> | ||
| <!-- Filter --> | ||
| <REFERENCE dmref="CoordSystem_XMM_FILTER_EB2_id" dmrole="Phot:PhotCal.photometryFilter" /> | ||
| </INSTANCE> | ||
| <INSTANCE dmid="CoordSystem_XMM_EB3_id" dmtype="Phot:PhotCal"> | ||
| <ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB3" /> | ||
| <!-- Magnitude System --> | ||
| <INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem"> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="XMM" /> | ||
| <ATTRIBUTE dmrole="Phot:MagnitudeSystem.referenceSpectrum" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| </INSTANCE> | ||
| <!-- Filter --> | ||
| <REFERENCE dmref="CoordSystem_XMM_FILTER_EB3_id" dmrole="Phot:PhotCal.photometryFilter" /> | ||
| </INSTANCE> | ||
| <INSTANCE dmid="CoordSystem_XMM_FILTER_EB1_id" dmtype="Phot:PhotometryFilter"> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB1" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="XMM EPIC EB1" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="Soft" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="EB1" /> | ||
| <!-- Spectral Location --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation"> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl.effective" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="0.35" /> | ||
| </INSTANCE> | ||
| <!-- Band width --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.bandwidth" dmtype="Phot:Bandwidth"> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth;stat.fwhm" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="0.3" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="0.2" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="0.5" /> | ||
| </INSTANCE> | ||
| <!-- Transmission Curve --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve"> | ||
| <INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access"> | ||
| <ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| <ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="text/html" /> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| <INSTANCE dmid="CoordSystem_XMM_FILTER_EB2_id" dmtype="Phot:PhotometryFilter"> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB2" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="XMM EPIC EB2" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="Soft" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="EB2" /> | ||
| <!-- Spectral Location --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation"> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl.effective" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="0.75" /> | ||
| </INSTANCE> | ||
| <!-- Band width --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.bandwidth" dmtype="Phot:Bandwidth"> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth;stat.fwhm" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="0.5" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="0.5" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="1.0" /> | ||
| </INSTANCE> | ||
| <!-- Transmission Curve --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve"> | ||
| <INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access"> | ||
| <ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| <ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="text/html" /> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| <INSTANCE dmid="CoordSystem_XMM_FILTER_EB3_id" dmtype="Phot:PhotometryFilter"> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="XMM/EPIC/EB3" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="XMM EPIC EB3" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="Medium" /> | ||
| <ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="EB3" /> | ||
| <!-- Spectral Location --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation"> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl.effective" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="1.5" /> | ||
| </INSTANCE> | ||
| <!-- Band width --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.bandwidth" dmtype="Phot:Bandwidth"> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth;stat.fwhm" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="keV" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="1.0" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="1.0" /> | ||
| <ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="2.0" /> | ||
| </INSTANCE> | ||
| <!-- Transmission Curve --> | ||
| <INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve"> | ||
| <INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access"> | ||
| <ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" | ||
| value="https://xmm-tools.cosmos.esa.int/external/xmm_user_support/documentation/sas_usg/USG/SASUSG.html" /> | ||
| <ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="text/html" /> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| </INSTANCE> | ||
| </GLOBALS> | ||
| <!-- This block maps the data of the table identified as "Results" --> | ||
| <TEMPLATES tableref="Results"> | ||
| <INSTANCE dmrole="" dmtype="mango:Brightness"> | ||
| <ATTRIBUTE dmrole="mango:Brightness.value" dmtype="ivoa:RealQuantity" ref="SC_EP_1_FLUX" /> | ||
| <REFERENCE dmref="CoordSystem_XMM_EB1_id" dmrole="mango:Brightness.photCal" /> | ||
| </INSTANCE> | ||
| <INSTANCE dmrole="" dmtype="mango:Brightness"> | ||
| <ATTRIBUTE dmrole="mango:Brightness.value" dmtype="ivoa:RealQuantity" ref="SC_EP_2_FLUX" /> | ||
| <REFERENCE dmref="CoordSystem_XMM_EB2_id" dmrole="mango:Brightness.photCal" /> | ||
| </INSTANCE> | ||
| <INSTANCE dmrole="" dmtype="mango:Brightness"> | ||
| <ATTRIBUTE dmrole="mango:Brightness.value" dmtype="ivoa:RealQuantity" ref="SC_EP_3_FLUX" /> | ||
| <REFERENCE dmref="CoordSystem_XMM_EB3_id" dmrole="mango:Brightness.photCal" /> | ||
| </INSTANCE> | ||
| </TEMPLATES> | ||
| </VODML> | ||
| </RESOURCE> | ||
| <TABLE name="Results"> | ||
| <FIELD datatype="float" name="SC_EP_1_FLUX" ucd="phot.flux" unit="erg/cm**2/s"> | ||
| <DESCRIPTION>the flux of the energy band number 1 of the ep camera in sc</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="float" name="SC_EP_2_FLUX" ucd="phot.flux" unit="erg/cm**2/s"> | ||
| <DESCRIPTION>the flux of the energy band number 2 of the ep camera in sc</DESCRIPTION> | ||
| </FIELD> | ||
| <FIELD datatype="float" name="SC_EP_3_FLUX" ucd="phot.flux" unit="erg/cm**2/s"> | ||
| <DESCRIPTION>the flux of the energy band number 3 of the ep camera in sc</DESCRIPTION> | ||
| </FIELD> | ||
| <DATA> | ||
| <TABLEDATA> | ||
| <TR> | ||
| <TD>0.0</TD> | ||
| <TD>0.1</TD> | ||
| <TD>0.2</TD> | ||
| </TR> | ||
| <TR> | ||
| <TD>1.0</TD> | ||
| <TD>2.1</TD> | ||
| <TD>3.2</TD> | ||
| </TR> | ||
| </TABLEDATA> | ||
| </DATA> | ||
| </TABLE> | ||
| </RESOURCE> | ||
| </VOTABLE> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!--W3C Schema for VOTable = Virtual Observatory Tabular Format | ||
| .Version 1.0 : 15-Apr-2002 | ||
| .Version 1.09: 23-Jan-2004 Version 1.09 | ||
| .Version 1.09: 30-Jan-2004 Version 1.091 | ||
| .Version 1.09: 22-Mar-2004 Version 1.092 | ||
| .Version 1.094: 02-Jun-2004 GROUP does not contain FIELD | ||
| .Version 1.1 : 10-Jun-2004 remove the complexContent | ||
| --> | ||
| <xs:schema | ||
| xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" | ||
| targetNamespace="http://www.ivoa.net/xml/VOTable/v1.1" | ||
| xmlns="http://www.ivoa.net/xml/VOTable/v1.1" | ||
| > | ||
| <!-- Here we define some interesting new datatypes: | ||
| - anyTEXT may have embedded XHTML (conforming HTML) | ||
| - astroYear is an epoch in Besselian or Julian year, e.g. J2000 | ||
| - arrayDEF specifies an array size e.g. 12x23x* | ||
| - dataType defines the acceptable datatypes | ||
| - ucdType defines the acceptable UCDs (UCD1+) | ||
| - precType defines the acceptable precisions | ||
| - yesno defines just the 2 alternatives | ||
| --> | ||
| <xs:complexType name="anyTEXT" mixed="true"> | ||
| <xs:sequence> | ||
| <xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <xs:simpleType name="astroYear"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[JB]?[0-9]+([.][0-9]*)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="ucdType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[A-Za-z0-9_.;\-]*"/><!-- UCD1 use also / + % --> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="arrayDEF"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="([0-9]+x)*[0-9]*[*]?(s\W)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="encodingType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="gzip"/> | ||
| <xs:enumeration value="base64"/> | ||
| <xs:enumeration value="dynamic"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="dataType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="boolean"/> | ||
| <xs:enumeration value="bit"/> | ||
| <xs:enumeration value="unsignedByte"/> | ||
| <xs:enumeration value="short"/> | ||
| <xs:enumeration value="int"/> | ||
| <xs:enumeration value="long"/> | ||
| <xs:enumeration value="char"/> | ||
| <xs:enumeration value="unicodeChar"/> | ||
| <xs:enumeration value="float"/> | ||
| <xs:enumeration value="double"/> | ||
| <xs:enumeration value="floatComplex"/> | ||
| <xs:enumeration value="doubleComplex"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="precType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[EF]?[1-9][0-9]*"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="yesno"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="yes"/> | ||
| <xs:enumeration value="no"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <!-- VOTable is the root element --> | ||
| <xs:element name="VOTABLE"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:element ref="DEFINITIONS" minOccurs="0"/><!-- Deprecated --> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element ref="COOSYS" /> | ||
| <xs:element ref="PARAM" /> | ||
| <xs:element ref="INFO" /> | ||
| </xs:choice> | ||
| <xs:element ref="RESOURCE" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="version"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="1.1"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- RESOURCES can contain DESCRIPTION, (INFO|PARAM|COSYS), LINK, TABLEs --> | ||
| <xs:element name="RESOURCE"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element ref="INFO" /> | ||
| <xs:element ref="COOSYS" /> | ||
| <xs:element ref="PARAM" /> | ||
| </xs:choice> | ||
| <xs:element ref="LINK" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element ref="TABLE" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element ref="RESOURCE" minOccurs="0" maxOccurs="unbounded"/> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE types --> | ||
| <xs:any namespace="##other" processContents="lax" | ||
| minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="type" default="results"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="results"/> | ||
| <xs:enumeration value="meta"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE attributes --> | ||
| <xs:anyAttribute namespace="##other" processContents="lax"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" /> | ||
| <xs:element name="DEFINITIONS"> | ||
| <xs:annotation> | ||
| <xs:documentation>Deprecated in Version 1.1</xs:documentation> | ||
| </xs:annotation> | ||
| <xs:complexType> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element ref="COOSYS" /> | ||
| <xs:element ref="PARAM" /> | ||
| </xs:choice> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- INFO is a name-value pair --> | ||
| <xs:element name="INFO"> | ||
| <xs:complexType><xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:extension> | ||
| </xs:simpleContent></xs:complexType> | ||
| </xs:element> | ||
| <!-- A PARAM is similar to a FIELD, but it also has a "value" attribute --> | ||
| <xs:element name="PARAM"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:element ref="VALUES" minOccurs="0"/> | ||
| <xs:element ref="LINK" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="datatype" type="dataType" use="required"/> | ||
| <xs:attribute name="precision" type="precType"/> | ||
| <xs:attribute name="width" type="xs:positiveInteger"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="arraysize" type="arrayDEF"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- A TABLE is a sequence of FIELD/PARAMs and LINKS and DESCRIPTION, | ||
| possibly followed by a DATA section | ||
| --> | ||
| <xs:element name="TABLE"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element ref="FIELD" /> | ||
| <xs:element ref="PARAM" /> | ||
| <xs:element ref="GROUP" /> | ||
| </xs:choice> | ||
| <xs:element ref="LINK" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element ref="DATA" minOccurs="0"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="nrows" type="xs:nonNegativeInteger"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- FIELD is the definition of what is in a column of the table --> | ||
| <xs:element name="FIELD"> | ||
| <xs:complexType> | ||
| <xs:sequence> <!-- minOccurs="0" maxOccurs="unbounded" --> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:element ref="VALUES" minOccurs="0"/> <!-- maxOccurs="2" --> | ||
| <xs:element ref="LINK" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="datatype" type="dataType" use="required"/> | ||
| <xs:attribute name="precision" type="precType"/> | ||
| <xs:attribute name="width" type="xs:positiveInteger"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="arraysize" type="xs:string"/> | ||
| <xs:attribute name="type"> | ||
| <!-- type is not in the Version 1.1, but is kept for | ||
| backward compatibility purposes | ||
| --> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="hidden"/> | ||
| <xs:enumeration value="no_query"/> | ||
| <xs:enumeration value="trigger"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- GROUP groups columns; may include descriptions, fields/params/groups --> | ||
| <xs:element name="GROUP"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="DESCRIPTION" minOccurs="0"/> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element ref="FIELDref"/> | ||
| <xs:element ref="PARAMref"/> | ||
| <xs:element ref="PARAM"/> | ||
| <xs:element ref="GROUP"/> | ||
| </xs:choice> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- FIELDref and PARAMref are references to FIELD or PARAM defined | ||
| in the parent TABLE or RESOURCE --> | ||
| <xs:element name="FIELDref"> | ||
| <xs:complexType> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <!-- utype and maybe ucd could well be added there, | ||
| will be if necessary --> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="PARAMref"> | ||
| <xs:complexType> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <!-- utype and maybe ucd could well be added there, | ||
| will be if necessary --> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- VALUES expresses the values that can be taken by the data | ||
| in a column or by a parameter | ||
| --> | ||
| <xs:element name="VALUES"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="MIN" minOccurs="0"/> | ||
| <xs:element ref="MAX" minOccurs="0"/> | ||
| <xs:element ref="OPTION" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="type" default="legal"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="legal"/> | ||
| <xs:enumeration value="actual"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="null" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <!-- xs:attribute name="invalid" type="yesno" default="no"/ --> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="MIN"> | ||
| <xs:complexType> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="MAX"> | ||
| <xs:complexType> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="OPTION"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="OPTION" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- The LINK is a URL (href) or some other kind of reference (gref) --> | ||
| <xs:element name="LINK"> | ||
| <xs:complexType mixed="true"> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="content-role"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="query"/> | ||
| <xs:enumeration value="hints"/> | ||
| <xs:enumeration value="doc"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="content-type" type="xs:token"/> | ||
| <xs:attribute name="title" type="xs:string"/> | ||
| <xs:attribute name="value" type="xs:string"/> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="gref" type="xs:token"/><!-- Deprecated in V1.1 --> | ||
| <xs:attribute name="action" type="xs:anyURI"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- DATA is the actual table data, in one of three formats --> | ||
| <xs:element name="DATA"> | ||
| <xs:complexType> | ||
| <xs:choice> | ||
| <xs:element ref="TABLEDATA"/> | ||
| <xs:element ref="BINARY"/> | ||
| <xs:element ref="FITS"/> | ||
| </xs:choice> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- Pure XML data --> | ||
| <xs:element name="TABLEDATA"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="TR" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="TD"> | ||
| <xs:complexType><xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <!-- xs:attribute name="ref" type="xs:IDREF"/ --> | ||
| <xs:attribute name="encoding" type="encodingType"/> | ||
| </xs:extension> | ||
| </xs:simpleContent></xs:complexType> | ||
| </xs:element> | ||
| <xs:element name="TR"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="TD" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- FITS file, perhaps with specification of which extension to seek to --> | ||
| <xs:element name="FITS"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="STREAM"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="extnum" type="xs:positiveInteger"/> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- BINARY data format --> | ||
| <xs:element name="BINARY"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element ref="STREAM"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- STREAM can be local or remote, encoded or not --> | ||
| <xs:element name="STREAM"> | ||
| <xs:complexType> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="type" default="locator"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="locator"/> | ||
| <xs:enumeration value="other"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="actuate" default="onRequest"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="onLoad"/> | ||
| <xs:enumeration value="onRequest"/> | ||
| <xs:enumeration value="other"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="encoding" type="encodingType" default="none"/> | ||
| <xs:attribute name="expires" type="xs:dateTime"/> | ||
| <xs:attribute name="rights" type="xs:token"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| <!-- Expresses the coordinate system we are using --> | ||
| <xs:element name="COOSYS"> | ||
| <xs:complexType><xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID" use="required"/> | ||
| <xs:attribute name="equinox" type="astroYear"/> | ||
| <xs:attribute name="epoch" type="astroYear"/> | ||
| <xs:attribute name="system" default="eq_FK5"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="eq_FK4"/> | ||
| <xs:enumeration value="eq_FK5"/> | ||
| <xs:enumeration value="ICRS"/> | ||
| <xs:enumeration value="ecl_FK4"/> | ||
| <xs:enumeration value="ecl_FK5"/> | ||
| <xs:enumeration value="galactic"/> | ||
| <xs:enumeration value="supergalactic"/> | ||
| <xs:enumeration value="xy"/> | ||
| <xs:enumeration value="barycentric"/> | ||
| <xs:enumeration value="geo_app"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:extension></xs:simpleContent></xs:complexType> | ||
| </xs:element> | ||
| </xs:schema> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!--W3C Schema for VOTable = Virtual Observatory Tabular Format | ||
| .Version 1.0 : 15-Apr-2002 | ||
| .Version 1.09: 23-Jan-2004 Version 1.09 | ||
| .Version 1.09: 30-Jan-2004 Version 1.091 | ||
| .Version 1.09: 22-Mar-2004 Version 1.092 | ||
| .Version 1.094: 02-Jun-2004 GROUP does not contain FIELD | ||
| .Version 1.1 : 10-Jun-2004 remove the complexContent | ||
| .Version 1.11: GL: 23-May-2006 remove most root elements, use name= type= iso ref= structure | ||
| .Version 1.11: GL: 29-Aug-2006 review and added comments (prefixed by GL) | ||
| before sending to Francois Ochsenbein | ||
| .Version 1.12: FO: Preliminary Version 1.2 | ||
| .Version 1.18: FO: Tested (jax) version 1.2 | ||
| .Version 1.19: FO: Completed INFO attributes | ||
| .Version 1.20: FO: Added xtype; content-role is less restrictive (May2009) | ||
| .Version 1.20a: FO: PR-20090710 Cosmetics. | ||
| .Version 1.20b: FO: INFO does not accept sub-elements (2009-09-29) | ||
| .Version 1.20c: FO: elementFormDefault="qualified" to stay compatible with 1.1 | ||
| --> | ||
| <xs:schema | ||
| xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" | ||
| xmlns="http://www.ivoa.net/xml/VOTable/v1.2" | ||
| targetNamespace="http://www.ivoa.net/xml/VOTable/v1.2" | ||
| > | ||
| <xs:annotation><xs:documentation> | ||
| VOTable1.2 is meant to serialize tabular documents in the | ||
| context of Virtual Observatory applications. This schema | ||
| corresponds to the VOTable document available from | ||
| http://www.ivoa.net/Documents/latest/VOT.html | ||
| </xs:documentation></xs:annotation> | ||
| <!-- Here we define some interesting new datatypes: | ||
| - anyTEXT may have embedded XHTML (conforming HTML) | ||
| - astroYear is an epoch in Besselian or Julian year, e.g. J2000 | ||
| - arrayDEF specifies an array size e.g. 12x23x* | ||
| - dataType defines the acceptable datatypes | ||
| - ucdType defines the acceptable UCDs (UCD1+) | ||
| - precType defines the acceptable precisions | ||
| - yesno defines just the 2 alternatives | ||
| --> | ||
| <xs:complexType name="anyTEXT" mixed="true"> | ||
| <xs:sequence> | ||
| <xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <xs:simpleType name="astroYear"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[JB]?[0-9]+([.][0-9]*)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="ucdType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:annotation><xs:documentation> | ||
| Accept UCD1+ | ||
| Accept also old UCD1 (but not / + %) including SIAP convention (with :) | ||
| </xs:documentation></xs:annotation> | ||
| <xs:pattern value="[A-Za-z0-9_.:;\-]*"/><!-- UCD1 use also / + % --> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="arrayDEF"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="([0-9]+x)*[0-9]*[*]?(s\W)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="encodingType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="gzip"/> | ||
| <xs:enumeration value="base64"/> | ||
| <xs:enumeration value="dynamic"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="dataType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="boolean"/> | ||
| <xs:enumeration value="bit"/> | ||
| <xs:enumeration value="unsignedByte"/> | ||
| <xs:enumeration value="short"/> | ||
| <xs:enumeration value="int"/> | ||
| <xs:enumeration value="long"/> | ||
| <xs:enumeration value="char"/> | ||
| <xs:enumeration value="unicodeChar"/> | ||
| <xs:enumeration value="float"/> | ||
| <xs:enumeration value="double"/> | ||
| <xs:enumeration value="floatComplex"/> | ||
| <xs:enumeration value="doubleComplex"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="precType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[EF]?[1-9][0-9]*"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="yesno"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="yes"/> | ||
| <xs:enumeration value="no"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:complexType name="Min"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="Max"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="Option"> | ||
| <xs:sequence> | ||
| <xs:element name="OPTION" type="Option" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:complexType> | ||
| <!-- VALUES expresses the values that can be taken by the data | ||
| in a column or by a parameter | ||
| --> | ||
| <xs:complexType name="Values"> | ||
| <xs:sequence> | ||
| <xs:element name="MIN" type="Min" minOccurs="0"/> | ||
| <xs:element name="MAX" type="Max" minOccurs="0"/> | ||
| <xs:element name="OPTION" type="Option" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="type" default="legal"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="legal"/> | ||
| <xs:enumeration value="actual"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="null" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <!-- xs:attribute name="invalid" type="yesno" default="no"/ --> | ||
| </xs:complexType> | ||
| <!-- The LINK is a URL (href) or some other kind of reference (gref) --> | ||
| <xs:complexType name="Link"> | ||
| <xs:annotation><xs:documentation> | ||
| content-role was previsouly restricted as: <![CDATA[ | ||
| <xs:attribute name="content-role"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="query"/> | ||
| <xs:enumeration value="hints"/> | ||
| <xs:enumeration value="doc"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute>]]>; is now a name token. | ||
| </xs:documentation></xs:annotation> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="content-role" type="xs:NMTOKEN"/> | ||
| <xs:attribute name="content-type" type="xs:NMTOKEN"/> | ||
| <xs:attribute name="title" type="xs:string"/> | ||
| <xs:attribute name="value" type="xs:string"/> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="gref" type="xs:token"/><!-- Deprecated in V1.1 --> | ||
| <xs:attribute name="action" type="xs:anyURI"/> | ||
| </xs:complexType> | ||
| <!-- INFO is defined in Version 1.2 as a PARAM of String type | ||
| <xs:complexType name="Info"> | ||
| <xs:complexContent> | ||
| <xs:restriction base="Param"> | ||
| <xs:attribute name="unit" fixed=""/> | ||
| <xs:attribute name="datatype" fixed="char"/> | ||
| <xs:attribute name="arraysize" fixed="*"/> | ||
| </xs:restriction> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| -or- as a full definition: | ||
| <xs:complexType name="Info"> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="VALUES" type="Values" minOccurs="0"/> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| --> | ||
| <!-- No sub-element is accepted in INFO for backward compatibility --> | ||
| <xs:complexType name="Info"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <!-- Expresses the coordinate system we are using --><!-- Deprecated V1.2 --> | ||
| <xs:complexType name="CoordinateSystem"> | ||
| <xs:annotation><xs:documentation> | ||
| Deprecated in Version 1.2 | ||
| </xs:documentation></xs:annotation> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID" use="required"/> | ||
| <xs:attribute name="equinox" type="astroYear"/> | ||
| <xs:attribute name="epoch" type="astroYear"/> | ||
| <xs:attribute name="system" default="eq_FK5"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="eq_FK4"/> | ||
| <xs:enumeration value="eq_FK5"/> | ||
| <xs:enumeration value="ICRS"/> | ||
| <xs:enumeration value="ecl_FK4"/> | ||
| <xs:enumeration value="ecl_FK5"/> | ||
| <xs:enumeration value="galactic"/> | ||
| <xs:enumeration value="supergalactic"/> | ||
| <xs:enumeration value="xy"/> | ||
| <xs:enumeration value="barycentric"/> | ||
| <xs:enumeration value="geo_app"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <xs:complexType name="Definitions"> | ||
| <xs:annotation><xs:documentation> | ||
| Deprecated in Version 1.1 | ||
| </xs:documentation></xs:annotation> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/><!-- Deprecated in V1.2 --> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| </xs:choice> | ||
| </xs:complexType> | ||
| <!-- FIELD is the definition of what is in a column of the table --> | ||
| <xs:complexType name="Field"> | ||
| <xs:sequence> <!-- minOccurs="0" maxOccurs="unbounded" --> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="VALUES" type="Values" minOccurs="0"/> <!-- maxOccurs="2" --> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="datatype" type="dataType" use="required"/> | ||
| <xs:attribute name="precision" type="precType"/> | ||
| <xs:attribute name="width" type="xs:positiveInteger"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="arraysize" type="xs:string"/> | ||
| <!-- GL: is the next deprecated element remaining | ||
| (is not in PARAM, but will in new model be inherited) | ||
| --> | ||
| <xs:attribute name="type"> | ||
| <!-- type is not in the Version 1.1, but is kept for | ||
| backward compatibility purposes | ||
| --> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="hidden"/> | ||
| <xs:enumeration value="no_query"/> | ||
| <xs:enumeration value="trigger"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| <!-- A PARAM is similar to a FIELD, but it also has a "value" attribute --> | ||
| <!-- GL: implemented here as a subtype as suggested we do in Kyoto. --> | ||
| <xs:complexType name="Param"> | ||
| <xs:complexContent> | ||
| <xs:extension base="Field"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:extension> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| <!-- GROUP groups columns; may include descriptions, fields/params/groups --> | ||
| <xs:complexType name="Group"> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <!-- GL I guess I can understand the next choice element as one may (?) | ||
| really want to group fields and params and groups in a particular order. | ||
| --> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="FIELDref" type="FieldRef"/> | ||
| <xs:element name="PARAMref" type="ParamRef"/> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| <xs:element name="GROUP" type="Group"/> | ||
| <!-- GL a GroupRef could remove recursion --> | ||
| </xs:choice> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <!-- FIELDref and PARAMref are references to FIELD or PARAM defined | ||
| in the parent TABLE or RESOURCE --> | ||
| <!-- GL This can not be enforced in XML Schema, so why not IDREF in <Group> ? | ||
| In particular if the UCD and utype attributes will NOT be added --> | ||
| <xs:complexType name="FieldRef"> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="ParamRef"> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <!-- DATA is the actual table data, in one of three formats --> | ||
| <!-- | ||
| GL in Kyoto we discussed the option of having the specific Data items | ||
| be subtypes of Data: | ||
| --> | ||
| <!-- | ||
| <xs:complexType name="Data" abstract="true"/> | ||
| <xs:complexType name="TableData"> | ||
| <xs:complexContent> | ||
| <xs:extension base="Data"> | ||
| ... etc | ||
| </xs:extension> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| --> | ||
| <xs:complexType name="Data"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:choice> | ||
| <xs:element name="TABLEDATA" type="TableData"/> | ||
| <xs:element name="BINARY" type="Binary"/> | ||
| <xs:element name="FITS" type="FITS"/> | ||
| </xs:choice> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <!-- Pure XML data --> | ||
| <xs:complexType name="TableData"> | ||
| <xs:sequence> | ||
| <xs:element name="TR" type="Tr" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <xs:complexType name="Td"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <!-- xs:attribute name="ref" type="xs:IDREF"/ --> | ||
| <xs:annotation><xs:documentation> | ||
| The 'encoding' attribute is added here to avoid | ||
| problems of code generators which do not properly | ||
| interpret the TR/TD structures. | ||
| 'encoding' was chosen because it appears in | ||
| appendix A.5 | ||
| </xs:documentation></xs:annotation> | ||
| <xs:attribute name="encoding" type="encodingType"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <xs:complexType name="Tr"> | ||
| <xs:annotation><xs:documentation> | ||
| The ID attribute is added here to the TR tag to avoid | ||
| problems of code generators which do not properly | ||
| interpret the TR/TD structures | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="TD" type="Td" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| </xs:complexType> | ||
| <!-- FITS file, perhaps with specification of which extension to seek to --> | ||
| <xs:complexType name="FITS"> | ||
| <xs:sequence> | ||
| <xs:element name="STREAM" type="Stream"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="extnum" type="xs:positiveInteger"/> | ||
| </xs:complexType> | ||
| <!-- BINARY data format --> | ||
| <xs:complexType name="Binary"> | ||
| <xs:sequence> | ||
| <xs:element name="STREAM" type="Stream"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <!-- STREAM can be local or remote, encoded or not --> | ||
| <xs:complexType name="Stream"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="type" default="locator"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="locator"/> | ||
| <xs:enumeration value="other"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="actuate" default="onRequest"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="onLoad"/> | ||
| <xs:enumeration value="onRequest"/> | ||
| <xs:enumeration value="other"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="encoding" type="encodingType" default="none"/> | ||
| <xs:attribute name="expires" type="xs:dateTime"/> | ||
| <xs:attribute name="rights" type="xs:token"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <!-- A TABLE is a sequence of FIELD/PARAMs and LINKS and DESCRIPTION, | ||
| possibly followed by a DATA section | ||
| --> | ||
| <xs:complexType name="Table"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <!-- GL: why a choice iso for example --> | ||
| <!-- | ||
| <xs:element name="PARAM" type="Param" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element name="FIELD" type="Field" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element name="GROUP" type="Group" minOccurs="0" maxOccurs="unbounded"/> | ||
| --> | ||
| <!-- | ||
| This could also enforce groups to be defined after the fields and params | ||
| to which they must have a reference, which is somewhat more logical | ||
| --> | ||
| <!-- Added Version 1.2: --> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| <!-- An empty table without any FIELD/PARAM should not be acceptable --> | ||
| <xs:choice minOccurs="1" maxOccurs="unbounded"> | ||
| <xs:element name="FIELD" type="Field"/> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| <xs:element name="GROUP" type="Group"/> | ||
| </xs:choice> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| <!-- This would allow several DATA parts in a table (future extension?) | ||
| <xs:sequence minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="DATA" type="Data"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| --> | ||
| <xs:element name="DATA" type="Data" minOccurs="0"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="nrows" type="xs:nonNegativeInteger"/> | ||
| </xs:complexType> | ||
| <!-- RESOURCES can contain DESCRIPTION, (INFO|PARAM|COSYS), LINK, TABLEs --> | ||
| <xs:complexType name="Resource"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics in several places | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/><!-- Deprecated in V1.2 --> | ||
| <xs:element name="GROUP" type="Group" /> | ||
| <xs:element name="PARAM" type="Param" /> | ||
| </xs:choice> | ||
| <xs:sequence minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:choice> | ||
| <xs:element name="TABLE" type="Table" /> | ||
| <xs:element name="RESOURCE" type="Resource" /> | ||
| </xs:choice> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE types --> | ||
| <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="type" default="results"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="results"/> | ||
| <xs:enumeration value="meta"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE attributes --> | ||
| <xs:anyAttribute namespace="##other" processContents="lax"/> | ||
| </xs:complexType> | ||
| <!-- VOTable is the root element --> | ||
| <xs:element name="VOTABLE"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="DEFINITIONS" type="Definitions" minOccurs="0"/><!-- Deprecated --> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/><!-- Deprecated in V1.2 --> | ||
| <xs:element name="GROUP" type="Group" /> | ||
| <xs:element name="PARAM" type="Param" /> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:choice> | ||
| <xs:element name="RESOURCE" type="Resource" minOccurs="1" maxOccurs="unbounded"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="version"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="1.2"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| </xs:schema> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!--W3C Schema for VOTable = Virtual Observatory Tabular Format | ||
| .Version 1.0 : 15-Apr-2002 | ||
| .Version 1.09: 23-Jan-2004 Version 1.09 | ||
| .Version 1.09: 30-Jan-2004 Version 1.091 | ||
| .Version 1.09: 22-Mar-2004 Version 1.092 | ||
| .Version 1.094: 02-Jun-2004 GROUP does not contain FIELD | ||
| .Version 1.1 : 10-Jun-2004 remove the complexContent | ||
| .Version 1.11: GL: 23-May-2006 remove most root elements, use name= type= iso ref= structure | ||
| .Version 1.11: GL: 29-Aug-2006 review and added comments (prefixed by GL) | ||
| before sending to Francois Ochsenbein | ||
| .Version 1.12: FO: Preliminary Version 1.2 | ||
| .Version 1.18: FO: Tested (jax) version 1.2 | ||
| .Version 1.19: FO: Completed INFO attributes | ||
| .Version 1.20: FO: Added xtype; content-role is less restrictive (May2009) | ||
| .Version 1.20a: FO: PR-20090710 Cosmetics. | ||
| .Version 1.20b: FO: INFO does not accept sub-elements (2009-09-29) | ||
| .Version 1.20c: FO: elementFormDefault="qualified" to stay compatible with 1.1 | ||
| .Version 1.3: MT: Added BINARY2 element | ||
| .Version 1.3: MT: Further relaxed LINK content-role type to token | ||
| .Version 1.3-Erratum-2 MT: Made slight change to precType pattern | ||
| .Version 1.4pre1: MD: merged 1.3-Erratrum 2, added TIMESYS. | ||
| .Version 1.4wd-a: TD: updates for initial draft of v1.4. | ||
| .Version 1.4: TD: Change version to 1.4 | ||
| .Version 1.5pre1: MD: adding refposition attribute and FIELDref/PARAMref | ||
| in COOSYS, which no longer is derived from xs:string, either. | ||
| .Version 1.5pre2: MD: vocabularising system attribute in COOSYS | ||
| .Version 1.5pre3: TD: Undo the FIELDref/PARAMref addition to COOSYS | ||
| .Version 1.5: TD: Update version to 1.5 for REC | ||
| --> | ||
| <xs:schema | ||
| xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" | ||
| xmlns="http://www.ivoa.net/xml/VOTable/v1.3" | ||
| targetNamespace="http://www.ivoa.net/xml/VOTable/v1.3" | ||
| version="1.5" | ||
| > | ||
| <xs:annotation><xs:documentation> | ||
| VOTable is meant to serialize tabular documents in the | ||
| context of Virtual Observatory applications. This schema | ||
| corresponds to the VOTable document available from | ||
| http://www.ivoa.net/Documents/latest/VOT.html | ||
| </xs:documentation></xs:annotation> | ||
| <!-- Here we define some interesting new datatypes: | ||
| - anyTEXT may have embedded XHTML (conforming HTML) | ||
| - astroYear is an epoch in Besselian or Julian year, e.g. J2000 | ||
| - arrayDEF specifies an array size e.g. 12x23x* | ||
| - dataType defines the acceptable datatypes | ||
| - ucdType defines the acceptable UCDs (UCD1+) | ||
| - precType defines the acceptable precisions | ||
| - yesno defines just the 2 alternatives | ||
| --> | ||
| <xs:complexType name="anyTEXT" mixed="true"> | ||
| <xs:sequence> | ||
| <xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <xs:simpleType name="astroYear"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[JB]?[0-9]+([.][0-9]*)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="ucdType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:annotation><xs:documentation> | ||
| Accept UCD1+ | ||
| Accept also old UCD1 (but not / + %) including SIAP convention (with :) | ||
| </xs:documentation></xs:annotation> | ||
| <xs:pattern value="[A-Za-z0-9_.:;\-]*"/><!-- UCD1 use also / + % --> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="arrayDEF"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="([0-9]+x)*[0-9]*[*]?(s\W)?"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="encodingType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="gzip"/> | ||
| <xs:enumeration value="base64"/> | ||
| <xs:enumeration value="dynamic"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="dataType"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="boolean"/> | ||
| <xs:enumeration value="bit"/> | ||
| <xs:enumeration value="unsignedByte"/> | ||
| <xs:enumeration value="short"/> | ||
| <xs:enumeration value="int"/> | ||
| <xs:enumeration value="long"/> | ||
| <xs:enumeration value="char"/> | ||
| <xs:enumeration value="unicodeChar"/> | ||
| <xs:enumeration value="float"/> | ||
| <xs:enumeration value="double"/> | ||
| <xs:enumeration value="floatComplex"/> | ||
| <xs:enumeration value="doubleComplex"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="precType"> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[EF]?[0-9][0-9]*"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:simpleType name="yesno"> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="yes"/> | ||
| <xs:enumeration value="no"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:complexType name="Min"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="Max"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="inclusive" type="yesno" default="yes"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="Option"> | ||
| <xs:sequence> | ||
| <xs:element name="OPTION" type="Option" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:complexType> | ||
| <!-- VALUES expresses the values that can be taken by the data | ||
| in a column or by a parameter | ||
| --> | ||
| <xs:complexType name="Values"> | ||
| <xs:sequence> | ||
| <xs:element name="MIN" type="Min" minOccurs="0"/> | ||
| <xs:element name="MAX" type="Max" minOccurs="0"/> | ||
| <xs:element name="OPTION" type="Option" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="type" default="legal"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="legal"/> | ||
| <xs:enumeration value="actual"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="null" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <!-- xs:attribute name="invalid" type="yesno" default="no"/ --> | ||
| </xs:complexType> | ||
| <!-- The LINK is a URL (href) or some other kind of reference (gref) --> | ||
| <xs:complexType name="Link"> | ||
| <xs:annotation><xs:documentation> | ||
| content-role was previsouly restricted as: <![CDATA[ | ||
| <xs:attribute name="content-role"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="query"/> | ||
| <xs:enumeration value="hints"/> | ||
| <xs:enumeration value="doc"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute>]]>; is now a token. | ||
| </xs:documentation></xs:annotation> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="content-role" type="xs:token"/> | ||
| <xs:attribute name="content-type" type="xs:token"/> | ||
| <xs:attribute name="title" type="xs:string"/> | ||
| <xs:attribute name="value" type="xs:string"/> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="gref" type="xs:token"/><!-- Deprecated in V1.1 --> | ||
| <xs:attribute name="action" type="xs:anyURI"/> | ||
| </xs:complexType> | ||
| <!-- INFO is defined in Version 1.2 as a PARAM of String type | ||
| <xs:complexType name="Info"> | ||
| <xs:complexContent> | ||
| <xs:restriction base="Param"> | ||
| <xs:attribute name="unit" fixed=""/> | ||
| <xs:attribute name="datatype" fixed="char"/> | ||
| <xs:attribute name="arraysize" fixed="*"/> | ||
| </xs:restriction> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| -or- as a full definition: | ||
| <xs:complexType name="Info"> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="VALUES" type="Values" minOccurs="0"/> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| --> | ||
| <!-- No sub-element is accepted in INFO for backward compatibility --> | ||
| <xs:complexType name="Info"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <!-- Expresses the coordinate system we are using --> | ||
| <xs:complexType name="CoordinateSystem"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID" use="required"/> | ||
| <xs:attribute name="equinox" type="astroYear"/> | ||
| <xs:attribute name="epoch" type="astroYear"/> | ||
| <xs:attribute name="system" default="FK5"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| Values for this attribute must be taken from the IVOA | ||
| refframe vocabulary, http://www.ivoa.net/rdf/refframe | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| </xs:attribute> | ||
| <xs:attribute name="refposition" type="xs:token"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| The reference position SHOULD be taken from the IVOA | ||
| refposition vocabulary (http://www.ivoa.net/rdf/refposition). | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| </xs:attribute> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <xs:simpleType name="Timeorigin"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| This is a time origin of a time coordinate, given as a | ||
| Julian Date for the the time scale and reference point | ||
| defined. It is usually given as a floating point | ||
| literal; for convenience, the magic strings “MJD-origin” | ||
| (standing for 2400000.5) and “JD-origin” (standing for 0) | ||
| are also allowed. | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| <xs:restriction base="xs:token"> | ||
| <xs:pattern value="[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?|(JD|MJD)-origin"> | ||
| </xs:pattern> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| <xs:complexType name="TimeSystem"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="ID" type="xs:ID" use="required"/> | ||
| <xs:attribute name="timeorigin" type="Timeorigin"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| The time origin is the offset or the time coordinate to Julian | ||
| Date. The timeorigin attribute MUST be given unless the time's | ||
| representation contains a year of a calendar era, in which case it | ||
| MUST NOT be present. | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| </xs:attribute> | ||
| <xs:attribute name="timescale" use="required" type="xs:token"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| This is the time scale used. Values SHOULD be | ||
| taken from the IVOA timescale vocabulary (http://www.ivoa.net/rdf/timescale). | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| </xs:attribute> | ||
| <xs:attribute name="refposition" use="required" type="xs:token"> | ||
| <xs:annotation> | ||
| <xs:documentation> | ||
| The reference position SHOULD be taken from the IVOA | ||
| refposition vocabulary (http://www.ivoa.net/rdf/refposition). | ||
| </xs:documentation> | ||
| </xs:annotation> | ||
| </xs:attribute> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <xs:complexType name="Definitions"> | ||
| <xs:annotation><xs:documentation> | ||
| Deprecated in Version 1.1 | ||
| </xs:documentation></xs:annotation> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/> | ||
| <xs:element name="TIMESYS" type="TimeSystem"/> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| </xs:choice> | ||
| </xs:complexType> | ||
| <!-- FIELD is the definition of what is in a column of the table --> | ||
| <xs:complexType name="Field"> | ||
| <xs:sequence> <!-- minOccurs="0" maxOccurs="unbounded" --> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="VALUES" type="Values" minOccurs="0"/> <!-- maxOccurs="2" --> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="unit" type="xs:token"/> | ||
| <xs:attribute name="datatype" type="dataType" use="required"/> | ||
| <xs:attribute name="precision" type="precType"/> | ||
| <xs:attribute name="width" type="xs:positiveInteger"/> | ||
| <xs:attribute name="xtype" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="name" type="xs:token" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="arraysize" type="xs:string"/> | ||
| <!-- GL: is the next deprecated element remaining | ||
| (is not in PARAM, but will in new model be inherited) | ||
| --> | ||
| <xs:attribute name="type"> | ||
| <!-- type is not in the Version 1.1, but is kept for | ||
| backward compatibility purposes | ||
| --> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="hidden"/> | ||
| <xs:enumeration value="no_query"/> | ||
| <xs:enumeration value="trigger"/> | ||
| <xs:enumeration value="location"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| <!-- A PARAM is similar to a FIELD, but it also has a "value" attribute --> | ||
| <!-- GL: implemented here as a subtype as suggested we do in Kyoto. --> | ||
| <xs:complexType name="Param"> | ||
| <xs:complexContent> | ||
| <xs:extension base="Field"> | ||
| <xs:attribute name="value" type="xs:string" use="required"/> | ||
| </xs:extension> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| <!-- GROUP groups columns; may include descriptions, fields/params/groups --> | ||
| <xs:complexType name="Group"> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <!-- GL I guess I can understand the next choice element as one may (?) | ||
| really want to group fields and params and groups in a particular order. | ||
| --> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="FIELDref" type="FieldRef"/> | ||
| <xs:element name="PARAMref" type="ParamRef"/> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| <xs:element name="GROUP" type="Group"/> | ||
| <!-- GL a GroupRef could remove recursion --> | ||
| </xs:choice> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <!-- FIELDref and PARAMref are references to FIELD or PARAM defined | ||
| in the parent TABLE or RESOURCE --> | ||
| <!-- GL This can not be enforced in XML Schema, so why not IDREF in <Group> ? | ||
| In particular if the UCD and utype attributes will NOT be added --> | ||
| <xs:complexType name="FieldRef"> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <xs:complexType name="ParamRef"> | ||
| <xs:attribute name="ref" type="xs:IDREF" use="required"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| </xs:complexType> | ||
| <!-- DATA is the actual table data, in one of three formats --> | ||
| <!-- | ||
| GL in Kyoto we discussed the option of having the specific Data items | ||
| be subtypes of Data: | ||
| --> | ||
| <!-- | ||
| <xs:complexType name="Data" abstract="true"/> | ||
| <xs:complexType name="TableData"> | ||
| <xs:complexContent> | ||
| <xs:extension base="Data"> | ||
| ... etc | ||
| </xs:extension> | ||
| </xs:complexContent> | ||
| </xs:complexType> | ||
| --> | ||
| <xs:complexType name="Data"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:choice> | ||
| <xs:element name="TABLEDATA" type="TableData"/> | ||
| <xs:element name="BINARY" type="Binary"/> | ||
| <xs:element name="BINARY2" type="Binary2"/> | ||
| <xs:element name="FITS" type="FITS"/> | ||
| </xs:choice> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <!-- Pure XML data --> | ||
| <xs:complexType name="TableData"> | ||
| <xs:sequence> | ||
| <xs:element name="TR" type="Tr" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <xs:complexType name="Td"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <!-- xs:attribute name="ref" type="xs:IDREF"/ --> | ||
| <xs:annotation><xs:documentation> | ||
| The 'encoding' attribute is added here to avoid | ||
| problems of code generators which do not properly | ||
| interpret the TR/TD structures. | ||
| 'encoding' was chosen because it appears in | ||
| appendix A.5 | ||
| </xs:documentation></xs:annotation> | ||
| <xs:attribute name="encoding" type="encodingType"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <xs:complexType name="Tr"> | ||
| <xs:annotation><xs:documentation> | ||
| The ID attribute is added here to the TR tag to avoid | ||
| problems of code generators which do not properly | ||
| interpret the TR/TD structures | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="TD" type="Td" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| </xs:complexType> | ||
| <!-- FITS file, perhaps with specification of which extension to seek to --> | ||
| <xs:complexType name="FITS"> | ||
| <xs:sequence> | ||
| <xs:element name="STREAM" type="Stream"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="extnum" type="xs:positiveInteger"/> | ||
| </xs:complexType> | ||
| <!-- BINARY data format --> | ||
| <xs:complexType name="Binary"> | ||
| <xs:sequence> | ||
| <xs:element name="STREAM" type="Stream"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <!-- BINARY2 data format --> | ||
| <xs:complexType name="Binary2"> | ||
| <xs:sequence> | ||
| <xs:element name="STREAM" type="Stream"/> | ||
| </xs:sequence> | ||
| </xs:complexType> | ||
| <!-- STREAM can be local or remote, encoded or not --> | ||
| <xs:complexType name="Stream"> | ||
| <xs:simpleContent> | ||
| <xs:extension base="xs:string"> | ||
| <xs:attribute name="type" default="locator"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="locator"/> | ||
| <xs:enumeration value="other"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="href" type="xs:anyURI"/> | ||
| <xs:attribute name="actuate" default="onRequest"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="onLoad"/> | ||
| <xs:enumeration value="onRequest"/> | ||
| <xs:enumeration value="other"/> | ||
| <xs:enumeration value="none"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <xs:attribute name="encoding" type="encodingType" default="none"/> | ||
| <xs:attribute name="expires" type="xs:dateTime"/> | ||
| <xs:attribute name="rights" type="xs:token"/> | ||
| </xs:extension> | ||
| </xs:simpleContent> | ||
| </xs:complexType> | ||
| <!-- A TABLE is a sequence of FIELD/PARAMs and LINKS and DESCRIPTION, | ||
| possibly followed by a DATA section | ||
| --> | ||
| <xs:complexType name="Table"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <!-- GL: why a choice iso for example --> | ||
| <!-- | ||
| <xs:element name="PARAM" type="Param" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element name="FIELD" type="Field" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:element name="GROUP" type="Group" minOccurs="0" maxOccurs="unbounded"/> | ||
| --> | ||
| <!-- | ||
| This could also enforce groups to be defined after the fields and params | ||
| to which they must have a reference, which is somewhat more logical | ||
| --> | ||
| <!-- Added Version 1.2: --> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| <!-- An empty table without any FIELD/PARAM should not be acceptable --> | ||
| <xs:choice minOccurs="1" maxOccurs="unbounded"> | ||
| <xs:element name="FIELD" type="Field"/> | ||
| <xs:element name="PARAM" type="Param"/> | ||
| <xs:element name="GROUP" type="Group"/> | ||
| </xs:choice> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| <!-- This would allow several DATA parts in a table (future extension?) | ||
| <xs:sequence minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="DATA" type="Data"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| --> | ||
| <xs:element name="DATA" type="Data" minOccurs="0"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ref" type="xs:IDREF"/> | ||
| <xs:attribute name="ucd" type="ucdType"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="nrows" type="xs:nonNegativeInteger"/> | ||
| </xs:complexType> | ||
| <!-- RESOURCES can contain DESCRIPTION, (INFO|PARAM|COSYS), LINK, TABLEs --> | ||
| <xs:complexType name="Resource"> | ||
| <xs:annotation><xs:documentation> | ||
| Added in Version 1.2: INFO for diagnostics in several places | ||
| </xs:documentation></xs:annotation> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/> | ||
| <xs:element name="TIMESYS" type="TimeSystem"/> | ||
| <xs:element name="GROUP" type="Group" /> | ||
| <xs:element name="PARAM" type="Param" /> | ||
| </xs:choice> | ||
| <xs:sequence minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="LINK" type="Link" minOccurs="0" maxOccurs="unbounded"/> | ||
| <xs:choice> | ||
| <xs:element name="TABLE" type="Table" /> | ||
| <xs:element name="RESOURCE" type="Resource" /> | ||
| </xs:choice> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE types --> | ||
| <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="name" type="xs:token"/> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="utype" type="xs:string"/> | ||
| <xs:attribute name="type" default="results"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="results"/> | ||
| <xs:enumeration value="meta"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| <!-- Suggested Doug Tody, to include new RESOURCE attributes --> | ||
| <xs:anyAttribute namespace="##other" processContents="lax"/> | ||
| </xs:complexType> | ||
| <!-- VOTable is the root element --> | ||
| <xs:element name="VOTABLE"> | ||
| <xs:complexType> | ||
| <xs:sequence> | ||
| <xs:element name="DESCRIPTION" type="anyTEXT" minOccurs="0"/> | ||
| <xs:element name="DEFINITIONS" type="Definitions" minOccurs="0"/><!-- Deprecated --> | ||
| <xs:choice minOccurs="0" maxOccurs="unbounded"> | ||
| <xs:element name="COOSYS" type="CoordinateSystem"/> | ||
| <xs:element name="TIMESYS" type="TimeSystem"/> | ||
| <xs:element name="GROUP" type="Group" /> | ||
| <xs:element name="PARAM" type="Param" /> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:choice> | ||
| <xs:element name="RESOURCE" type="Resource" minOccurs="1" maxOccurs="unbounded"/> | ||
| <xs:element name="INFO" type="Info" minOccurs="0" maxOccurs="unbounded"/> | ||
| </xs:sequence> | ||
| <xs:attribute name="ID" type="xs:ID"/> | ||
| <xs:attribute name="version"> | ||
| <xs:simpleType> | ||
| <xs:restriction base="xs:NMTOKEN"> | ||
| <xs:enumeration value="1.3"/> | ||
| <xs:enumeration value="1.4"/> | ||
| <xs:enumeration value="1.5"/> | ||
| </xs:restriction> | ||
| </xs:simpleType> | ||
| </xs:attribute> | ||
| </xs:complexType> | ||
| </xs:element> | ||
| </xs:schema> |
+30
-0
@@ -0,1 +1,31 @@ | ||
| 1.8 (2025-11-13) | ||
| ================ | ||
| Enhancements and Fixes | ||
| ---------------------- | ||
| - No longer show a warning when an overflow status is encountered with a maxrec | ||
| set by the user [#690] | ||
| - Add support for the jobInfo element for UWS jobs [#679] | ||
| - Fix DALOverflowWarning error message to only indicate the cause as limits from | ||
| user or server. [#689] | ||
| - Add retry option to AsyncTAPJob.fetch_result for transient failures [#696] | ||
| - Improve the ``MivotViewer`` API: adding support of multiple mapped objects | ||
| per row and a partial redesign of the public API. Also, improved the gateway | ||
| between annotations and ``SkyCoord`` objects. [#698] | ||
| - Adding "hats" and "hips" servicetype shorthands for registry searches for | ||
| HATS and HIPS resources. [#706] | ||
| Deprecations and Removals | ||
| ------------------------- | ||
| - Upgrade of the ``MivotViewer`` API including the removal of the | ||
| ``xml_viewer`` module. [#698] | ||
| 1.7.1 (2025-10-16) | ||
@@ -2,0 +32,0 @@ ================== |
+14
-4
@@ -351,10 +351,20 @@ .. _pyvo-data-access: | ||
| To retrieve more rows than that (often conservative) default limit, you | ||
| must override maxrec in the call to ``search``. A warning can be expected if | ||
| you reach the ``maxrec`` limit: | ||
| must override maxrec in the call to ``search``. PyVO will only warn about | ||
| truncation when it's unexpected. If you request 5 records and get 5 records, | ||
| no warning is issued: | ||
| .. doctest-remote-data:: | ||
| >>> tap_results = tap_service.search("SELECT * FROM arihip.main", maxrec=5) # doctest: +SHOW_WARNINGS | ||
| DALOverflowWarning: Partial result set. Potential causes MAXREC, async storage space, etc. | ||
| >>> tap_results = tap_service.search("SELECT * FROM arihip.main", maxrec=5) | ||
| >>> len(tap_results) | ||
| 5 | ||
| However, if results are truncated by server limits without you specifying | ||
| maxrec, you'll receive a helpful warning: | ||
| .. doctest-remote-data:: | ||
| >>> tap_results = tap_service.search("SELECT * FROM arihip.main") # doctest: +SHOW_WARNINGS | ||
| DALOverflowWarning: Results truncated due to server limits. Consider setting a maxrec value. | ||
| Services will not let you raise maxrec beyond the hard match limit: | ||
@@ -361,0 +371,0 @@ |
+143
-0
@@ -106,2 +106,15 @@ ****************************************** | ||
| What is JobInfo? | ||
| ================ | ||
| The :class:`~pyvo.io.uws.tree.JobInfo` element is an extensible container | ||
| that allows UWS implementations to include arbitrary, service-specific information | ||
| about a job. This for example can be used to provide: | ||
| * **Implementation-specific metadata** about job execution like progress info | ||
| * **Service-specific configuration** or diagnostic information | ||
| * **Extended error details** or debugging information | ||
| JobInfo exposes this information in a possibly hierarchical dictionary. | ||
| Working with UWS Jobs | ||
@@ -140,2 +153,11 @@ ===================== | ||
| ... </results> | ||
| ... <jobInfo> | ||
| ... <rowsReturned>1234</rowsReturned> | ||
| ... <executionTime>133.333</executionTime> | ||
| ... <cpuTime>98.765</cpuTime> | ||
| ... <queuePosition>0</queuePosition> | ||
| ... <estimatedDuration>120</estimatedDuration> | ||
| ... <nodeId>compute-node-03</nodeId> | ||
| ... <serviceVersion>1.1</serviceVersion> | ||
| ... </jobInfo> | ||
| ... </job>''' | ||
@@ -211,2 +233,106 @@ >>> | ||
| Working with JobInfo | ||
| ==================== | ||
| JobInfo provides access to service-specific metadata about job execution. | ||
| Basic JobInfo Access | ||
| -------------------- | ||
| >>> if job.jobinfo: | ||
| ... # RECOMMENDED: Dict-like access for expected elements | ||
| ... try: | ||
| ... rows_returned = job.jobinfo['rowsReturned'] | ||
| ... execution_time = job.jobinfo['executionTime'] | ||
| ... print(f"Query returned {rows_returned.value:,} rows in {execution_time.value:.1f} seconds") | ||
| ... except KeyError as e: | ||
| ... print(f"Missing expected element: {e}") | ||
| ... | ||
| ... # Use .get() with defaults for optional elements | ||
| ... queue_pos = job.jobinfo.get('queuePosition', None) | ||
| ... node_id = job.jobinfo.get('nodeId', None) | ||
| ... | ||
| ... if queue_pos: | ||
| ... print(f"Final queue position: {queue_pos.value}") | ||
| ... | ||
| ... if node_id: | ||
| ... print(f"Executed on: {node_id.text}") | ||
| Query returned 1,234 rows in 133.3 seconds | ||
| Final queue position: 0 | ||
| Executed on: compute-node-03 | ||
| .. note:: | ||
| **API Usage Recommendation**: When accessing jobInfo elements, prefer dict-like | ||
| access (``job.jobinfo['element']``) for expected elements, as this provides | ||
| clearer error handling with KeyError exceptions. Use ``.get()`` with default | ||
| values for optional elements. See the jobInfo examples for detailed patterns. | ||
| Understanding .value vs .text | ||
| ----------------------------- | ||
| JobInfo elements provide two ways to access their content: | ||
| >>> if job.jobinfo: | ||
| ... duration_elem = job.jobinfo['estimatedDuration'] | ||
| ... | ||
| ... # .text returns the raw string content | ||
| ... print(f"Raw text: '{duration_elem.text}'") | ||
| ... | ||
| ... # .value attempts automatic type conversion (int, float, or string) | ||
| ... print(f"Converted value: {duration_elem.value}") | ||
| ... print(f"Type: {type(duration_elem.value)}") | ||
| ... | ||
| ... # For elements with string content that shouldn't be converted | ||
| ... node_elem = job.jobinfo['nodeId'] | ||
| ... print(f"Node ID text: {node_elem.text}") | ||
| ... print(f"Node ID value: {node_elem.value}") | ||
| Raw text: '120' | ||
| Converted value: 120 | ||
| Type: <class 'int'> | ||
| Node ID text: compute-node-03 | ||
| Node ID value: compute-node-03 | ||
| Best Practices for JobInfo Access | ||
| --------------------------------- | ||
| >>> def safe_jobinfo_access(job): | ||
| ... """Demonstrates safe jobInfo access patterns""" | ||
| ... if not job.jobinfo: | ||
| ... print("No jobInfo available") | ||
| ... return | ||
| ... | ||
| ... # Pattern 1: Expected elements with error handling | ||
| ... try: | ||
| ... rows = job.jobinfo['rowsReturned'] | ||
| ... exec_time = job.jobinfo['executionTime'] | ||
| ... print(f"Processed {rows.value:,} rows in {exec_time.value:.1f} seconds") | ||
| ... except KeyError as e: | ||
| ... print(f"Required statistics missing: {e}") | ||
| ... | ||
| ... # Pattern 2: Optional elements with defaults | ||
| ... queue_pos = job.jobinfo.get('queuePosition', 'unknown') | ||
| ... priority = job.jobinfo.get('priority', 'normal') | ||
| ... | ||
| ... # Pattern 3: Check existence before accessing | ||
| ... if 'nodeId' in job.jobinfo: | ||
| ... node = job.jobinfo['nodeId'] | ||
| ... print(f"Executed on node: {node.text}") | ||
| ... | ||
| ... # Pattern 4: Iterate through all available elements | ||
| ... print("All jobInfo elements:") | ||
| ... for key in job.jobinfo.keys(): | ||
| ... element = job.jobinfo[key] | ||
| ... print(f" {key}: {element.text}") | ||
| >>> safe_jobinfo_access(job) | ||
| Processed 1,234 rows in 133.3 seconds | ||
| Executed on node: compute-node-03 | ||
| All jobInfo elements: | ||
| rowsReturned: 1234 | ||
| executionTime: 133.333 | ||
| cpuTime: 98.765 | ||
| queuePosition: 0 | ||
| estimatedDuration: 120 | ||
| nodeId: compute-node-03 | ||
| serviceVersion: 1.1 | ||
| Job Status Monitoring | ||
@@ -226,2 +352,9 @@ ===================== | ||
| ... | ||
| ... # Access jobInfo safely | ||
| ... if job.jobinfo: | ||
| ... try: | ||
| ... rows = job.jobinfo['rowsReturned'] | ||
| ... print(f"Total rows returned: {rows.value:,}") | ||
| ... except KeyError: | ||
| ... print("Row count not available") | ||
| ... elif job.phase == 'ERROR' and job.errorsummary and job.errorsummary.message: | ||
@@ -233,2 +366,3 @@ ... print(f"Job failed: {job.errorsummary.message.content}") | ||
| Total job runtime: 2.3 minutes | ||
| Total rows returned: 1,234 | ||
@@ -336,2 +470,11 @@ Working with Job Lists | ||
| <jobInfo> | ||
| <rowsReturned>1234</rowsReturned> | ||
| <executionTime>133.333</executionTime> | ||
| <cpuTime>98.765</cpuTime> | ||
| <queuePosition>0</queuePosition> | ||
| <estimatedDuration>120</estimatedDuration> | ||
| <nodeId>compute-node-03</nodeId> | ||
| <serviceVersion>1.1</serviceVersion> | ||
| </jobInfo> | ||
| </job> |
+138
-109
@@ -0,1 +1,3 @@ | ||
| .. _mivot-examples: | ||
| ************************************************************ | ||
@@ -5,3 +7,3 @@ MIVOT (``pyvo.mivot``): How to use annotated data - Examples | ||
| Photometric properties readout | ||
| Photometric Properties Readout | ||
| ============================== | ||
@@ -26,30 +28,28 @@ | ||
| .. code-block:: python | ||
| .. doctest-skip:: | ||
| import pytest | ||
| from pyvo.utils import activate_features | ||
| from pyvo.dal import TAPService | ||
| from pyvo.mivot.utils.xml_utils import XmlUtils | ||
| from pyvo.mivot.utils.dict_utils import DictUtils | ||
| from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||
| >>> from pyvo.utils import activate_features | ||
| >>> from pyvo.dal import TAPService | ||
| >>> from pyvo.mivot.utils.xml_utils import XmlUtils | ||
| >>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||
| >>> | ||
| >>> # Enable MIVOT-specific features in the pyvo library | ||
| >>> activate_features("MIVOT") | ||
| >>> | ||
| >>> service = TAPService('https://xcatdb.unistra.fr/xtapdb') | ||
| >>> result = service.run_sync( | ||
| ... """ | ||
| ... SELECT TOP 5 * FROM "public".mergedentry | ||
| ... """, | ||
| ... format="application/x-votable+xml;content=mivot" | ||
| ... ) | ||
| >>> | ||
| >>> # The MIVOT viewer generates the model view of the data | ||
| >>> m_viewer = MivotViewer(result, resolve_ref=True) | ||
| >>> | ||
| >>> # Print out the Mivot annotations read out of the VOtable | ||
| >>> # This statement is just for a pedagogic purpose (access to a private attribute) | ||
| >>> XmlUtils.pretty_print(m_viewer._mapping_block) | ||
| # Enable MIVOT-specific features in the pyvo library | ||
| activate_features("MIVOT") | ||
| service = TAPService('https://xcatdb.unistra.fr/xtapdb') | ||
| result = service.run_sync( | ||
| """ | ||
| SELECT TOP 5 * FROM "public".mergedentry | ||
| """, | ||
| format="application/x-votable+xml;content=mivot" | ||
| ) | ||
| # The MIVOT viewer generates the model view of the data | ||
| m_viewer = MivotViewer(result, resolve_ref=True) | ||
| # Print out the Mivot annotations read out of the VOtable | ||
| # This statement is just for a pedagogic purpose (access to a private attribute) | ||
| XmlUtils.pretty_print(m_viewer._mapping_block) | ||
| In this first step we just queried the service and we built the object that will process the Mivot annotations. | ||
@@ -68,44 +68,41 @@ The Mivot block printing output is too long to be listed here. However, the screenshot below shows its shallow structure. | ||
| At instantiation time, the viewer reads the first data row, which must exist, | ||
| in order to construct a Python object that reflects the mapped model. | ||
| in order to construct the Python objects that reflect the mapped models and | ||
| to make the data available through them. | ||
| .. code-block:: python | ||
| .. doctest-skip:: | ||
| # Build a Python object matching the TEMPLATES content and | ||
| # which leaves are set with the values of the first row | ||
| mango_object = m_viewer.dm_instance | ||
| >>> # Discover the Python objects matching the TEMPLATES content | ||
| >>> for dm_instance in m_viewer.dm_instances; | ||
| >>> print(dm_instance) | ||
| <MivotInstance: dmtype="mango:MangoObject"> | ||
| # Print out the content of the Python object | ||
| # This statement is just for a pedagogic purpose | ||
| DictUtils.print_pretty_json(mango_object.to_dict()) | ||
| The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row. | ||
| You can explore the structure of this object by using the printed dictionary or standard object paths as shown below. | ||
| You can explore the structure of this object by using standard object paths as shown below. | ||
| Now, we can iterate through the table data and retrieve an updated Mivot instance for each row. | ||
| .. code-block:: python | ||
| .. doctest-skip:: | ||
| while m_viewer.next_row_view(): | ||
| if mango_object.dmtype == "mango:MangoObject": | ||
| print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}") | ||
| for mango_property in mango_object.propertyDock: | ||
| if mango_property.dmtype == "mango:Brightness": | ||
| if mango_property.value.value: | ||
| mag_value = mango_property.value.value | ||
| mag_error = mango_property.error.sigma.value | ||
| phot_cal = mango_property.photCal | ||
| spectral_location = phot_cal.photometryFilter.spectralLocation | ||
| mag_filter = phot_cal.identifier.value | ||
| spectral_location = phot_cal.photometryFilter.spectralLocation | ||
| mag_wl = spectral_location.value.value | ||
| sunit = spectral_location.unitexpression.value | ||
| print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}") | ||
| Read source 4XMM J054329.3-682106 mango:MangoObject | ||
| >>> mango_object = m_viewer.dm_instances[0] | ||
| >>> while m_viewer.next_row_view(): | ||
| >>> if mango_object.dmtype == "mango:MangoObject": | ||
| >>> print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}") | ||
| >>> for mango_property in mango_object.propertyDock: | ||
| >>> if mango_property.dmtype == "mango:Brightness": | ||
| >>> if mango_property.value.value: | ||
| >>> mag_value = mango_property.value.value | ||
| >>> mag_error = mango_property.error.sigma.value | ||
| >>> phot_cal = mango_property.photCal | ||
| >>> spectral_location = phot_cal.photometryFilter.spectralLocation | ||
| >>> mag_filter = phot_cal.identifier.value | ||
| >>> spectral_location = phot_cal.photometryFilter.spectralLocation | ||
| >>> mag_wl = spectral_location.value.value | ||
| >>> sunit = spectral_location.unitexpression.value | ||
| >>> print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}") | ||
| Read source 4XMM J054329.3-682106 mango:MangoObject | ||
| flux at 0.35 keV (filter XMM/EPIC/EB1) is 8.35e-14 +/- 3.15e-14 | ||
| flux at 0.75 keV (filter XMM/EPIC/EB2) is 3.26e-15 +/- 5.45e-15 | ||
| flux at 6.1 keV (filter XMM/EPIC/EB8) is 8.68e-14 +/- 6.64e-14 | ||
| ... | ||
| ... | ||
| ... | ||
| ... | ||
@@ -119,7 +116,7 @@ The same code can easily be connected with matplotlib to plot SEDs as shown below (code not provided). | ||
| It is to noted that the current table row keeps available through the Mivot viewer. | ||
| It is to be noted that the current table row keeps available through the Mivot viewer. | ||
| .. code-block:: python | ||
| .. code-block:: python | ||
| row = m_viewer.table_row | ||
| row = m_viewer.table_row | ||
@@ -136,3 +133,3 @@ | ||
| EpochPosition property readout | ||
| EpochPosition Property Readout | ||
| ============================== | ||
@@ -146,3 +143,3 @@ | ||
| .. warning:: | ||
| At the time of writing, Vizier only mapped positions and proper motions (when available), | ||
| At the time of writing (Q1 2025), Vizier only mapped positions and proper motions (when available), | ||
| and the definitive epoch class had not been adopted. | ||
@@ -153,48 +150,54 @@ Therefore, this implementation may differ a little bit from the standard model. | ||
| but rather lists them in the Mivot *TEMPLATES*. | ||
| The annotation reader must support both designs. | ||
| The annotation reader supports both designs. | ||
| In the first step below, we run a standard cone search query by using the standard PyVO API. | ||
| Once the query is finished, we can get a reference to the object that will process the Mivot annotations. | ||
| .. code-block:: python | ||
| .. doctest-skip:: | ||
| import pytest | ||
| import astropy.units as u | ||
| from astropy.coordinates import SkyCoord | ||
| from pyvo.dal.scs import SCSService | ||
| >>> import astropy.units as u | ||
| >>> from astropy.coordinates import SkyCoord | ||
| >>> from pyvo.dal.scs import SCSService | ||
| >>> from pyvo.utils import activate_features | ||
| >>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||
| >>> from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder | ||
| >>> | ||
| >>> # Enable MIVOT-specific features in the pyvo library | ||
| >>> activate_features("MIVOT") | ||
| >>> | ||
| >>> scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main") | ||
| >>> | ||
| >>> query_result = scs_srv.search( | ||
| ... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'), | ||
| ... radius=0.5) | ||
| >>> | ||
| >>> # The MIVOT viewer generates the model view of the data | ||
| >>> m_viewer = MivotViewer(query_result, resolve_ref=True) | ||
| from pyvo.utils import activate_features | ||
| from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||
| from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder | ||
| from pyvo.mivot.utils.dict_utils import DictUtils | ||
| We can now discover which data model classes the data is mapped to. | ||
| # Enable MIVOT-specific features in the pyvo library | ||
| activate_features("MIVOT") | ||
| .. doctest-skip:: | ||
| scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main") | ||
| >>> # Get a set of Python objects matching the TEMPLATES content and | ||
| >>> # which leaves are set with the values of the first row | ||
| >>> for dm_instance in m_viewer.dm_instances; | ||
| >>> print(dm_instance) | ||
| <MivotInstance: dmtype="mango:EpochPosition"> | ||
| query_result = scs_srv.search( | ||
| pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'), | ||
| radius=0.5) | ||
| The first instance can be accessed by the ``m_viewer.dm_instance`` getter. | ||
| This is a simple shorcut aiming at simplifying the code. | ||
| # The MIVOt viewer generates the model view of the data | ||
| m_viewer = MivotViewer(query_result, resolve_ref=True) | ||
| .. doctest-skip:: | ||
| Once the query is finished, we can get a reference to the object that will process the Mivot annotations. | ||
| >>> dm_instance = m_viewer.dm_instance | ||
| >>> print(dm_instance.dmtype) | ||
| mango:EpochPosition | ||
| .. code-block:: python | ||
| We can also provide a complete instance representation that includes all fields in the entire hierarchy. | ||
| # Build a Python object matching the TEMPLATES content and | ||
| # which leaves are set with the values of the first row | ||
| mango_property = m_viewer.dm_instance | ||
| .. doctest-skip:: | ||
| # Print out the content of the Python object | ||
| # This statement is just for a pedagogic purpose | ||
| DictUtils.print_pretty_json(mango_property.to_dict()) | ||
| The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row. | ||
| You can explore the structure of this object by using standard object paths or by browsing the dictionary shown below. | ||
| .. code-block:: json | ||
| { | ||
| >>> # Print out the json serialization of the Python object | ||
| >>> print(repr(dm_instance)) | ||
| { | ||
| "dmtype": "mango:EpochPosition", | ||
@@ -241,19 +244,19 @@ "longitude": { | ||
| "value": "ICRS" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| The reader can transform ``EpochPosition`` instances into ``SkyCoord`` instances. | ||
| These can then be used for further scientific processing. | ||
| The reader can transform ``EpochPosition`` instances into ``SkyCoord`` instances. | ||
| These can then be used for further scientific processing. | ||
| .. doctest-skip:: | ||
| .. code-block:: python | ||
| >>> while m_viewer.next_row_view(): | ||
| >>> mango_property = m_viewer.dm_instance | ||
| >>> if mango_property.dmtype == "mango:EpochPosition": | ||
| >>> scb = SkyCoordBuilder(mango_property) | ||
| >>> # do whatever process with the SkyCoord object | ||
| >>> print(scb.build_sky_coord()) | ||
| while m_viewer.next_row_view(): | ||
| if mango_property.dmtype == "mango:EpochPosition": | ||
| scb = SkyCoordBuilder(mango_property.to_dict()) | ||
| # do whatever process with the SkyCoord object | ||
| print(scb.build_sky_coord()) | ||
| .. important:: | ||
@@ -266,3 +269,29 @@ Similar to the previous example, this code can be used with any VOTable with data mapped to MANGO. | ||
| Homework | ||
| ======== | ||
| The next section provides some tips to use the API documented in the :ref:`annoter page <mivot-annoter>`. | ||
| Simbad has released (Q3 2025) an annotated version of its Cone Search. | ||
| It's a good case to exercise this API. | ||
| .. code-block:: python | ||
| SERVER = "https://simbad.cds.unistra.fr/cone?" | ||
| VERB = 2 | ||
| RA = 269.452076* u.degree | ||
| DEC = 4.6933649* u.degree | ||
| SR = 0.1* u.degree | ||
| MAXREC = 100 | ||
| RESPONSEFORMAT = "mivot" | ||
| scs_srv = SCSService(SERVER) | ||
| query_result = scs_srv.search( | ||
| pos=SkyCoord(ra=RA, dec=DEC, frame='icrs'), | ||
| radius=SR, | ||
| verbosity=VERB, | ||
| RESPONSEFORMAT=RESPONSEFORMAT, | ||
| MAXREC=MAXREC) | ||
| *The next section provides some tips to use the API documented in the* :ref:`annoter page <mivot-annoter>`. |
+151
-97
@@ -26,2 +26,6 @@ ****************************************************** | ||
| .. attention:: | ||
| The module based on XPath queries and allowing to browse the XML | ||
| annotations (``viewer.XmlViewer``) has been removed from version 1.8 | ||
| Integrated Readout | ||
@@ -32,7 +36,19 @@ ------------------ | ||
| The example below shows how a VOTable result of a cone-search query can be parsed and data | ||
| mapped to the ``EpochPosition`` class. | ||
| ``MivotInstance`` is a generic class that does not refer to any specific model. | ||
| The mapped class of a particular instance is stored in its ``dmtype`` attribute. | ||
| These objects can be used as standard Python objects, with their fields representing | ||
| elements of the model instances. | ||
| .. doctest-remote-data:: | ||
| The first step is to instanciate a viewer that will provide the API for browsing annotations. | ||
| The viewer can be built from a VOTable file path, a parsed VOtable (``VOTableFile`` object), | ||
| or a ``DALResults`` instance. | ||
| .. attention:: | ||
| The code below only works with ``astropy 6+`` | ||
| .. doctest-skip:: | ||
| >>> import astropy.units as u | ||
@@ -42,111 +58,148 @@ >>> from astropy.coordinates import SkyCoord | ||
| >>> 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://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/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 | ||
| ... ), | ||
| ... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, | ||
| ... frame='icrs'), | ||
| ... radius=0.1 | ||
| ... ), | ||
| ... resolve_ref=True | ||
| ... ) | ||
| >>> mivot_instance = m_viewer.dm_instance | ||
| >>> print(mivot_instance.dmtype) | ||
| mango:EpochPosition | ||
| >>> print(mivot_instance.spaceSys.frame.spaceRefFrame.value) | ||
| ICRS | ||
| >>> while m_viewer.next_row_view(): | ||
| ... print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}") | ||
| position: 59.94033461 52.26722684 | ||
| In this example, the query result is mapped to the ``mango:EpochPosition`` class, | ||
| but users do not need to know this in advance, since the API provides tools | ||
| to discover the mapped models. | ||
| In this example, the data readout is totally managed by the ``MivotViewer`` instance. | ||
| The ``astropy.io.votable`` API is encapsulated in this module. | ||
| .. doctest-skip:: | ||
| Model leaves (class attributes) are complex types that provide additional information: | ||
| >>> if m_viewer.get_models().get("mango"): | ||
| >>> print("data is mapped to the MANGO data model") | ||
| data is mapped to the MANGO data model | ||
| - ``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 | ||
| We can also check which datamodel classes the data is mapped to. | ||
| The model view on a data row can also be passed as a Python dictionary | ||
| using the ``dict`` property of ``MivotInstance``. | ||
| .. doctest-skip:: | ||
| .. code-block:: python | ||
| :caption: Working with a model view as a dictionary | ||
| (the JSON layout has been squashed for display purpose) | ||
| >>> mivot_instances = m_viewer.dm_instances | ||
| >>> print(f"data is mapped to {len(mivot_instances)} model class(es)") | ||
| data is mapped to 1 model class(es) | ||
| from pyvo.mivot.utils.dict_utils import DictUtils | ||
| .. doctest-skip:: | ||
| mivot_instance = m_viewer.dm_instance | ||
| mivot_object_dict = mivot_object.dict | ||
| >>> mivot_instance = m_viewer.dm_instances[0] | ||
| >>> print(f"data is mapped to the {mivot_instance.dmtype} class") | ||
| data is mapped to the mango:EpochPosition class | ||
| DictUtils.print_pretty_json(mivot_object_dict) | ||
| { | ||
| "dmtype": "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"}, | ||
| "Coordinate_coordSys": { | ||
| "dmtype": "SpaceSys", | ||
| "dmid": "SpaceFrame_ICRS", | ||
| "dmrole": "coordSys", | ||
| "spaceRefFrame": {"value": "ICRS"}, | ||
| }, | ||
| At this point, we know that the data has been mapped to the ``MANGO`` model, | ||
| and that the data rows can be interpreted as instances of the ``mango:EpochPosition``. | ||
| .. doctest-skip:: | ||
| >>> print(mivot_instance.spaceSys.frame.spaceRefFrame.value) | ||
| ICRS | ||
| .. doctest-skip:: | ||
| >>> while m_viewer.next_row_view(): | ||
| >>> print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}") | ||
| position: 59.94033461 52.26722684 | ||
| .... | ||
| .. important:: | ||
| Coordinate systems are usually mapped in the GLOBALS MIVOT block. | ||
| This allows them to be referenced from any other MIVOT element. | ||
| The viewer resolves such references when the constructor flag ``resolve_ref`` is set to ``True``. | ||
| In this case the coordinate system instances are copied into their host elements. | ||
| The code below shows how to access GLOBALS instances (one in this example) independently of the mapped data. | ||
| .. doctest-skip:: | ||
| >>> for globals_instance in m_viewer.dm_globals_instances: | ||
| >>> print(globals_instance) | ||
| <MivotInstance: dmtype="coords:SpaceSys"> | ||
| We can also provide a complete instance representation that includes all fields in the entire hierarchy. | ||
| .. doctest-skip:: | ||
| >>> print(repr(globals_instance)) | ||
| { | ||
| "dmtype": "coords:SpaceSys", | ||
| "dmid": "SpaceFrame_ICRS", | ||
| "frame": { | ||
| "dmrole": "coords:PhysicalCoordSys.frame", | ||
| "dmtype": "coords:SpaceFrame", | ||
| "spaceRefFrame": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "ICRS" | ||
| } | ||
| } | ||
| } | ||
| - It is recommended to use a copy of the | ||
| dictionary as it will be rebuilt each time the ``dict`` property is invoked. | ||
| - The default representation of ``MivotInstance`` instances is made with a pretty | ||
| string serialization of this dictionary. | ||
| As you can see from the previous examples, model leaves (class attributes) are complex types. | ||
| This is because they contain additional metadata as well as values: | ||
| - ``value`` : attribute value | ||
| - ``dmtype`` : attribute type such as defined in the Mivot annotations | ||
| - ``unit`` (if any) : attribute unit such as defined in the Mivot annotations | ||
| Per-Row Readout | ||
| --------------- | ||
| The annotation schema can also be applied to table rows read outside of the ``MivotViewer`` | ||
| with the `astropy.io.votable` API: | ||
| This annotation schema can also be applied to table rows read using the `astropy.io.votable` API | ||
| outside of the ``MivotViewer`` context. | ||
| .. code-block:: python | ||
| :caption: Accessing the model view of Astropy table rows | ||
| .. doctest-skip:: | ||
| 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 | ||
| >>> votable = parse(path_to_votable) | ||
| >>> table = votable.resources[0].tables[0] | ||
| >>> # init the viewer on the first resource of the votable (default) | ||
| >>> mivot_viewer = MivotViewer(votable) | ||
| >>> mivot_object = mivot_viewer.dm_instance | ||
| >>> # and feed it with the numpy table row | ||
| >>> for rec in table.array: | ||
| >>> # apply the mapping to current row | ||
| >>> mivot_object.update(rec) | ||
| >>> # show that the model retrieve the correct values | ||
| >>> # ... or do whatever you want | ||
| >>> 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. | ||
| Mivot/Mango as a Direct Gateway from Data to Astropy SkyCoord | ||
| ------------------------------------------------------------- | ||
| For XML Hackers | ||
| --------------- | ||
| A simple way to get the most out of annotations is to use them | ||
| to directly create Astropy objects, without having to parse the metadata, | ||
| whether it comes from the annotation or the VOTable. | ||
| The model instances can also be serialized as XML elements that can be parsed with XPath queries. | ||
| .. doctest-skip:: | ||
| .. code-block:: python | ||
| :caption: Accessing the XML view of the mapped model instances | ||
| >>> from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder | ||
| >>> | ||
| >>> m_viewer.rewind() | ||
| >>> while m_viewer.next_row_view(): | ||
| >>> sky_coord_builder = SkyCoordBuilder(mivot_instance) | ||
| >>> sky_coord = sky_coord_builder.build_sky_coord() | ||
| >>> print(sky_coord) | ||
| <SkyCoord (ICRS): (ra, dec, distance) in (deg, deg, pc) | ||
| (52.26722684, 59.94033461, 1315.7894902) | ||
| (pm_ra_cosdec, pm_dec) in mas / yr | ||
| (-0.82, -1.85)> | ||
| with MivotViewer(path_to_votable) as mivot_viewer: | ||
| while mivot_viewer.next_row_view(): | ||
| xml_view = mivot_viewer.xml_view | ||
| # do whatever you want with this XML element | ||
| In the above example, we assume that the mapped model can be used as a ``SkyCoord`` precursor. | ||
| If this is not the case, an error is raised. | ||
| It is 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. | ||
| .. important:: | ||
| In the current implementation, the only functioning gateway connects | ||
| ``Mango:EpochPosition`` objects with the ``SkyCoord`` class. | ||
| The ultimate objective is to generalize this mechanism to any property modeled by Mango, | ||
| and eventually to other IVOA models, thereby realizing the full potential | ||
| of a comprehensive and interoperable mapping framework. | ||
| Class Generation in a Nutshell | ||
@@ -177,24 +230,25 @@ ------------------------------ | ||
| - 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: | ||
| - If the end-user is unaware of the class mapped by the actual ``MivotInstance``, | ||
| if can can explore it by using its class dictionary ``MivotInstance.__dict__`` | ||
| (see the Python `data model <https://docs.python.org/3/reference/datamodel.html>`_). | ||
| - 1. From the MIVOT instance property ``MivotInstance.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>`_). | ||
| .. doctest-skip:: | ||
| .. code-block:: python | ||
| :caption: Exploring the MivotInstance structure with the internal dictionaries | ||
| >>> mivot_instance = mivot_viewer.dm_instance | ||
| >>> print(mivot_instance.__dict__.keys()) | ||
| dict_keys(['dmtype', 'longitude', 'latitude', 'pmLongitude', 'pmLatitude', 'epoch', 'Coordinate_coordSys']) | ||
| mivot_instance = mivot_viewer.dm_instance | ||
| .. doctest-skip:: | ||
| print(mivot_instance.__dict__.keys()) | ||
| dict_keys(['dmtype', 'longitude', 'latitude', 'pmLongitude', 'pmLatitude', 'epoch', 'Coordinate_coordSys']) | ||
| >>> print(mivot_instance.Coordinate_coordSys.__dict__.keys()) | ||
| dict_keys(['dmtype', 'dmid', 'dmrole', 'spaceRefFrame']) | ||
| print(mivot_instance.Coordinate_coordSys.__dict__.keys()) | ||
| dict_keys(['dmtype', 'dmid', 'dmrole', 'spaceRefFrame']) | ||
| .. doctest-skip:: | ||
| print(mivot_instance.Coordinate_coordSys.spaceRefFrame.__dict__.keys()) | ||
| dict_keys(['dmtype', 'value', 'unit', 'ref']) | ||
| >>> print(mivot_instance.Coordinate_coordSys.spaceRefFrame.__dict__.keys()) | ||
| dict_keys(['dmtype', 'value', 'unit', 'ref']) | ||
| *More examples can be found* :ref:`here <mivot-examples>`. | ||
| Reference/API | ||
@@ -201,0 +255,0 @@ ============= |
+1
-1
| Metadata-Version: 2.4 | ||
| Name: pyvo | ||
| Version: 1.7.1 | ||
| Version: 1.8 | ||
| Summary: Astropy affiliated package for accessing Virtual Observatory data and services | ||
@@ -5,0 +5,0 @@ Author: the PyVO Developers |
| Metadata-Version: 2.4 | ||
| Name: pyvo | ||
| Version: 1.7.1 | ||
| Version: 1.8 | ||
| Summary: Astropy affiliated package for accessing Virtual Observatory data and services | ||
@@ -5,0 +5,0 @@ Author: the PyVO Developers |
@@ -154,2 +154,8 @@ .gitignore | ||
| pyvo/io/uws/tests/data/job-implicit-v1.0.xml | ||
| pyvo/io/uws/tests/data/job-with-duplicate-elements.xml | ||
| pyvo/io/uws/tests/data/job-with-empty-jobinfo.xml | ||
| pyvo/io/uws/tests/data/job-with-namespace-elements.xml | ||
| pyvo/io/uws/tests/data/job-with-nested-jobinfo.xml | ||
| pyvo/io/uws/tests/data/job-with-simple-jobinfo.xml | ||
| pyvo/io/uws/tests/data/job-with-typed-jobinfo.xml | ||
| pyvo/io/uws/tests/data/job.xml | ||
@@ -221,3 +227,2 @@ pyvo/io/vosi/__init__.py | ||
| pyvo/mivot/tests/test_mivot_instance.py | ||
| pyvo/mivot/tests/test_mivot_instance_generation.py | ||
| pyvo/mivot/tests/test_mivot_viewer.py | ||
@@ -230,5 +235,5 @@ pyvo/mivot/tests/test_mivot_writer.py | ||
| pyvo/mivot/tests/test_vizier_cs.py | ||
| pyvo/mivot/tests/test_xml_viewer.py | ||
| pyvo/mivot/tests/data/filter_gaia_grp.xml | ||
| pyvo/mivot/tests/data/filter_gaia_grvs.xml | ||
| pyvo/mivot/tests/data/simbad-cone-mivot.xml | ||
| pyvo/mivot/tests/data/static_reference.xml | ||
@@ -238,2 +243,3 @@ pyvo/mivot/tests/data/test.header_extraction.1.xml | ||
| pyvo/mivot/tests/data/test.header_extraction.xml | ||
| pyvo/mivot/tests/data/test.instance_multiple.xml | ||
| pyvo/mivot/tests/data/test.mango_annoter.xml | ||
@@ -253,3 +259,2 @@ pyvo/mivot/tests/data/test.mivot_viewer.first_instance.xml | ||
| pyvo/mivot/tests/data/reference/static_reference_resolved.xml | ||
| pyvo/mivot/tests/data/reference/templates_models.json | ||
| pyvo/mivot/tests/data/reference/test_header_extraction.xml | ||
@@ -271,3 +276,2 @@ pyvo/mivot/tests/data/reference/test_mivot_frames.xml | ||
| pyvo/mivot/viewer/mivot_viewer.py | ||
| pyvo/mivot/viewer/xml_viewer.py | ||
| pyvo/mivot/writer/__init__.py | ||
@@ -280,2 +284,5 @@ pyvo/mivot/writer/annotations.py | ||
| pyvo/mivot/writer/mivot-v1.xsd | ||
| pyvo/mivot/writer/v1.1.xsd | ||
| pyvo/mivot/writer/v1.2.xsd | ||
| pyvo/mivot/writer/v1.3.xsd | ||
| pyvo/registry/__init__.py | ||
@@ -282,0 +289,0 @@ pyvo/registry/regtap.py |
+53
-3
@@ -306,3 +306,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| def __init__(self, votable, *, url=None, session=None): | ||
| def __init__(self, votable, *, url=None, session=None, client_set_maxrec=None): | ||
| """ | ||
@@ -322,2 +322,4 @@ initialize the cursor. This constructor is not typically called | ||
| optional session to use for network requests | ||
| client_set_maxrec: int | ||
| the maximum number of records that were requested by the client. | ||
@@ -337,2 +339,3 @@ Raises | ||
| self._session = use_session(session) | ||
| self._client_set_maxrec = client_set_maxrec | ||
@@ -344,4 +347,3 @@ self._status = self._findstatus(votable) | ||
| if self._status[0].lower() == "overflow": | ||
| warn("Partial result set. Potential causes MAXREC, async storage space, etc.", | ||
| category=DALOverflowWarning) | ||
| self._handle_overflow_warning(client_set_maxrec) | ||
@@ -362,2 +364,50 @@ self._resultstable = self._findresultstable(votable) | ||
| def _handle_overflow_warning(self, client_set_maxrec=None): | ||
| """ | ||
| Handle overflow warning - can be overridden by subclasses. | ||
| Default implementation issues a generic overflow warning. | ||
| Subclasses can override this to customize or suppress warnings. | ||
| Parameters | ||
| ---------- | ||
| client_set_maxrec : int, optional | ||
| The maximum records requested by the client | ||
| """ | ||
| warn("Result set limited by user- or server-supplied MAXREC " | ||
| "parameter.", category=DALOverflowWarning) | ||
| def check_overflow_warning(self, client_set_maxrec=None): | ||
| """ | ||
| Check for overflow warnings and issue them if appropriate. | ||
| It will check if the results were truncated due to server limits | ||
| and issue a warning if the number of records returned is less than | ||
| the maximum records requested by the user. | ||
| If the results were truncated it distinguishes between expected | ||
| truncation (where the user requested a maxrec) | ||
| and unexpected truncation (where the user did not specify a maxrec). | ||
| If the results were truncated it issues a warning. | ||
| Parameters | ||
| ---------- | ||
| client_set_maxrec : int, optional | ||
| The maximum records explicitly requested by the client | ||
| """ | ||
| if self._status[0].lower() == "overflow": | ||
| maxrec_to_check = client_set_maxrec if client_set_maxrec is not None else self._client_set_maxrec | ||
| if (maxrec_to_check is not None | ||
| and len(self.resultstable.array) == maxrec_to_check): | ||
| pass | ||
| else: | ||
| if maxrec_to_check is not None: | ||
| warn(f"Results truncated at {len(self.resultstable.array)} records by service limits " | ||
| f"(you requested maxrec={maxrec_to_check})", | ||
| category=DALOverflowWarning) | ||
| else: | ||
| warn("Results truncated due to server limits. Consider " | ||
| "setting a maxrec value.", | ||
| category=DALOverflowWarning) | ||
| def _findresultstable(self, votable): | ||
@@ -364,0 +414,0 @@ # this can be overridden to specialize for a particular DAL protocol |
+60
-14
@@ -7,3 +7,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| from datetime import datetime | ||
| import time | ||
| from time import sleep | ||
| import random | ||
@@ -43,3 +45,7 @@ import requests | ||
| # common transient errors that can be retried | ||
| TRANSIENT_ERRORS = (requests.exceptions.ConnectionError, | ||
| requests.exceptions.Timeout) | ||
| def _from_ivoa_format(datetime_str): | ||
@@ -340,3 +346,3 @@ """ | ||
| result = job.fetch_result() | ||
| result = job.fetch_result(max_retries=keywords.get('max_retries', 0)) | ||
@@ -660,2 +666,3 @@ if delete: | ||
| job = cls(response.url, session=session) | ||
| job._client_set_maxrec = maxrec | ||
| return job | ||
@@ -679,2 +686,3 @@ | ||
| self._delete_on_exit = delete | ||
| self._client_set_maxrec = None | ||
| self._update() | ||
@@ -1033,5 +1041,11 @@ | ||
| def fetch_result(self): | ||
| def fetch_result(self, max_retries=0): | ||
| """ | ||
| returns the result votable if query is finished | ||
| Parameters | ||
| ---------- | ||
| max_retries : int, optional | ||
| Maximum number of retry attempts for transient network errors. | ||
| Default is 0 (no retries). | ||
| """ | ||
@@ -1045,16 +1059,30 @@ result_uri = self.result_uri | ||
| try: | ||
| response = self._session.get(self.result_uri, stream=True) | ||
| response.raise_for_status() | ||
| except requests.RequestException as ex: | ||
| self._update() | ||
| # we propably got a 404 because query error. raise with error msg | ||
| self.raise_if_error() | ||
| raise DALServiceError.from_except(ex, self.url) | ||
| response = None | ||
| response.raw.read = partial( | ||
| response.raw.read, decode_content=True) | ||
| return TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session) | ||
| for attempt in range(max_retries + 1): | ||
| try: | ||
| response = self._session.get(self.result_uri, stream=True) | ||
| response.raise_for_status() | ||
| break | ||
| except TRANSIENT_ERRORS as ex: | ||
| if attempt < max_retries: | ||
| delay = (2 ** attempt) + random.uniform(0.8, 1) | ||
| time.sleep(delay) | ||
| continue | ||
| else: | ||
| raise DALServiceError.from_except(ex, self.url) | ||
| except requests.RequestException as ex: | ||
| # Non-retryable error - update and check for query errors | ||
| self._update() | ||
| self.raise_if_error() | ||
| raise DALServiceError.from_except(ex, self.url) | ||
| response.raw.read = partial(response.raw.read, decode_content=True) | ||
| result = TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session) | ||
| result.check_overflow_warning(self._client_set_maxrec) | ||
| return result | ||
| class TAPQuery(DALQuery): | ||
@@ -1113,2 +1141,4 @@ """ | ||
| self._client_set_maxrec = maxrec | ||
| if maxrec: | ||
@@ -1166,4 +1196,11 @@ self["MAXREC"] = maxrec | ||
| """ | ||
| return TAPResults(self.execute_votable(), url=self.queryurl, session=self._session) | ||
| result = TAPResults( | ||
| self.execute_votable(), | ||
| url=self.queryurl, | ||
| session=self._session | ||
| ) | ||
| result.check_overflow_warning(self._client_set_maxrec) | ||
| return result | ||
| def submit(self, *, post=False): | ||
@@ -1277,4 +1314,13 @@ """ | ||
| def _handle_overflow_warning(self, client_set_maxrec=None): | ||
| """ | ||
| TAP-specific overflow warning handling. | ||
| For TAP results we suppress the default overflow warning during | ||
| initialization because TAPQuery.execute() will call check_overflow_warning() | ||
| """ | ||
| pass | ||
| class TAPRecord(SodaRecordMixin, DatalinkRecordMixin, Record): | ||
| pass |
@@ -6,2 +6,3 @@ #!/usr/bin/env python | ||
| """ | ||
| import warnings | ||
| from functools import partial | ||
@@ -423,3 +424,67 @@ | ||
| def test_check_overflow_warning_no_maxrec(self): | ||
| with pytest.warns(DALOverflowWarning): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') | ||
| with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"): | ||
| dalresults.check_overflow_warning() | ||
| def test_check_overflow_warning_exact_match(self): | ||
| with pytest.warns(DALOverflowWarning): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("error") | ||
| dalresults.check_overflow_warning(client_set_maxrec=3) | ||
| def test_check_overflow_warning_service_truncation(self): | ||
| with pytest.warns(DALOverflowWarning): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') | ||
| with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"): | ||
| dalresults.check_overflow_warning(client_set_maxrec=1000) | ||
| def test_check_overflow_warning_uses_stored_maxrec(self): | ||
| with pytest.warns(DALOverflowWarning): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') | ||
| dalresults._client_set_maxrec = 1000 | ||
| with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"): | ||
| dalresults.check_overflow_warning() | ||
| def test_check_overflow_warning_parameter_overrides_stored(self): | ||
| with pytest.warns(DALOverflowWarning): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') | ||
| dalresults._client_set_maxrec = 1000 | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("error") | ||
| dalresults.check_overflow_warning(client_set_maxrec=3) | ||
| def test_check_overflow_warning_no_overflow_status(self): | ||
| dalresults = DALResults.from_result_url('http://example.com/query/basic') | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("error") | ||
| dalresults.check_overflow_warning(client_set_maxrec=1000) | ||
| def test_handle_overflow_warning_default_behavior(self): | ||
| votable = votableparse(BytesIO(get_pkg_data_contents('data/query/overflowstatus.xml'))) | ||
| with pytest.warns(DALOverflowWarning, | ||
| match="Result set limited by user- or server-supplied MAXREC parameter."): | ||
| _ = DALResults(votable, url='http://test.com') | ||
| def test_stored_client_set_maxrec_initialization(self): | ||
| votable = votableparse(BytesIO(get_pkg_data_contents('data/query/basic.xml'))) | ||
| result = DALResults(votable, url='http://test.com', client_set_maxrec=100) | ||
| assert result._client_set_maxrec == 100 | ||
| result2 = DALResults(votable, url='http://test.com') | ||
| assert result2._client_set_maxrec is None | ||
| @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03') | ||
@@ -426,0 +491,0 @@ @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06') |
@@ -5,2 +5,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| import warnings | ||
| from functools import partial | ||
@@ -20,3 +21,3 @@ from contextlib import ExitStack | ||
| from pyvo.dal.tap import escape, search, AsyncTAPJob, TAPService | ||
| from pyvo.dal import DALQueryError, DALServiceError | ||
| from pyvo.dal import DALQueryError, DALServiceError, DALOverflowWarning | ||
@@ -136,2 +137,28 @@ from pyvo.io.uws import JobFile | ||
| @pytest.fixture() | ||
| def overflow_fixture(mocker): | ||
| """Mock TAP service that returns overflow status with exactly 10 records""" | ||
| def callback(request, context): | ||
| votable_content = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <VOTABLE version="1.3" xmlns="http://www.ivoa.net/xml/VOTable/v1.3"> | ||
| <RESOURCE type="results"> | ||
| <INFO name="QUERY_STATUS" value="OVERFLOW">Result truncated due to MAXREC</INFO> | ||
| <TABLE> | ||
| <FIELD name="id" datatype="int"/> | ||
| <FIELD name="value" datatype="char" arraysize="*"/> | ||
| <DATA> | ||
| <TABLEDATA>''' + ''.join(f'<TR><TD>{i}</TD><TD>test{i}</TD></TR>' for i in range(10)) + ''' | ||
| </TABLEDATA> | ||
| </DATA> | ||
| </TABLE> | ||
| </RESOURCE> | ||
| </VOTABLE>''' | ||
| return votable_content.encode('utf-8') | ||
| with mocker.register_uri( | ||
| 'POST', 'http://example.com/tap/sync', content=callback | ||
| ) as matcher: | ||
| yield matcher | ||
| class MockAsyncTAPServer: | ||
@@ -1051,3 +1078,183 @@ def __init__(self): | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") | ||
| def test_fetch_result_retry_connection_error(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore") | ||
| job.run() | ||
| job.wait() | ||
| status_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| <uws:jobId>1</uws:jobId> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:results> | ||
| <uws:result id="result" xsi:type="vot:VOTable" | ||
| href="http://example.com/tap/async/1/results/result"/> | ||
| </uws:results> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) | ||
| call_count = 0 | ||
| def response_callback(request, context): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
| if call_count <= 2: | ||
| raise requests.exceptions.ConnectionError() | ||
| else: | ||
| return get_pkg_data_contents('data/tap/obscore-image.xml') | ||
| rm.get( | ||
| f'http://example.com/tap/async/{job.job_id}/results/result', | ||
| content=response_callback | ||
| ) | ||
| result = job.fetch_result(max_retries=2) | ||
| assert len(result) == 10 | ||
| assert call_count == 3 | ||
| job.delete() | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") | ||
| def test_fetch_result_retry_timeout_error(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore") | ||
| job.run() | ||
| job.wait() | ||
| status_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| <uws:jobId>1</uws:jobId> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:results> | ||
| <uws:result id="result" xsi:type="vot:VOTable" | ||
| href="http://example.com/tap/async/1/results/result"/> | ||
| </uws:results> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) | ||
| call_count = 0 | ||
| def response_callback(request, context): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
| if call_count == 1: | ||
| raise requests.exceptions.Timeout() | ||
| else: | ||
| return get_pkg_data_contents('data/tap/obscore-image.xml') | ||
| rm.get( | ||
| f'http://example.com/tap/async/{job.job_id}/results/result', | ||
| content=response_callback | ||
| ) | ||
| result = job.fetch_result(max_retries=1) | ||
| assert len(result) == 10 | ||
| assert call_count == 2 | ||
| job.delete() | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| def test_fetch_result_no_retry_on_http_error(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore") | ||
| job.run() | ||
| job.wait() | ||
| status_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| <uws:jobId>1</uws:jobId> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:results> | ||
| <uws:result id="result" xsi:type="vot:VOTable" | ||
| href="http://example.com/tap/async/1/results/result"/> | ||
| </uws:results> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) | ||
| rm.get( | ||
| f'http://example.com/tap/async/{job.job_id}/results/result', | ||
| status_code=404 | ||
| ) | ||
| with pytest.raises(DALServiceError): | ||
| job.fetch_result(max_retries=2) | ||
| job.delete() | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| def test_fetch_result_retry_exhausted(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore") | ||
| job.run() | ||
| job.wait() | ||
| status_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| <uws:jobId>1</uws:jobId> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:results> | ||
| <uws:result id="result" xsi:type="vot:VOTable" | ||
| href="http://example.com/tap/async/1/results/result"/> | ||
| </uws:results> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) | ||
| rm.get( | ||
| f'http://example.com/tap/async/{job.job_id}/results/result', | ||
| exc=requests.exceptions.ConnectionError() | ||
| ) | ||
| with pytest.raises(DALServiceError): | ||
| job.fetch_result(max_retries=2) | ||
| job.delete() | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| def test_fetch_result_default_no_retry(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore") | ||
| job.run() | ||
| job.wait() | ||
| status_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
| <uws:jobId>1</uws:jobId> | ||
| <uws:phase>COMPLETED</uws:phase> | ||
| <uws:results> | ||
| <uws:result id="result" xsi:type="vot:VOTable" | ||
| href="http://example.com/tap/async/1/results/result"/> | ||
| </uws:results> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) | ||
| rm.get( | ||
| f'http://example.com/tap/async/{job.job_id}/results/result', | ||
| exc=requests.exceptions.ConnectionError() | ||
| ) | ||
| with pytest.raises(DALServiceError): | ||
| job.fetch_result() | ||
| job.delete() | ||
| @pytest.mark.usefixtures("tapservice") | ||
@@ -1342,1 +1549,127 @@ class TestTAPCapabilities: | ||
| assert row['proposal_id'] == '2013.1.01365.S' | ||
| @pytest.mark.usefixtures('overflow_fixture') | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") | ||
| @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") | ||
| class TestTAPOverflowWarnings: | ||
| """Test TAP overflow warning behavior""" | ||
| def test_sync_no_maxrec_overflow_warning(self): | ||
| service = TAPService('http://example.com/tap') | ||
| with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"): | ||
| results = service.run_sync("SELECT * FROM ivoa.obscore") | ||
| assert len(results) == 10 | ||
| def test_sync_maxrec_exact_match_no_warning(self): | ||
| service = TAPService('http://example.com/tap') | ||
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
| _ = service.run_sync("SELECT * FROM ivoa.obscore", maxrec=10) | ||
| overflow_warnings = [warning for warning in w | ||
| if issubclass(warning.category, DALOverflowWarning)] | ||
| assert len(overflow_warnings) == 0, f"Unexpected overflow warnings: {overflow_warnings}" | ||
| def test_sync_maxrec_service_truncation_warning(self): | ||
| service = TAPService('http://example.com/tap') | ||
| with pytest.warns(DALOverflowWarning, match="Results truncated at " | ||
| "10 records by service limits.*you requested maxrec=100"): | ||
| results = service.run_sync("SELECT * FROM ivoa.obscore", maxrec=100) | ||
| assert len(results) == 10 | ||
| def test_search_function_no_maxrec(self): | ||
| with pytest.warns(DALOverflowWarning, | ||
| match="Results truncated due to server limits"): | ||
| results = search('http://example.com/tap', | ||
| "SELECT * FROM ivoa.obscore") | ||
| assert len(results) == 10 | ||
| def test_search_function_with_maxrec(self): | ||
| with warnings.catch_warnings(record=True) as w: | ||
| warnings.simplefilter("always") | ||
| _ = search('http://example.com/tap', "SELECT * FROM " | ||
| "ivoa.obscore", maxrec=10) | ||
| overflow_warnings = [warning for warning in w | ||
| if issubclass(warning.category, DALOverflowWarning)] | ||
| assert len(overflow_warnings) == 0, f"Unexpected overflow warnings: {overflow_warnings}" | ||
| def test_tapresults_suppresses_construction_warning(self): | ||
| from pyvo.dal.tap import TAPResults | ||
| from astropy.io.votable import parse as votableparse | ||
| import requests | ||
| response = requests.post('http://example.com/tap/sync', | ||
| data={'QUERY': 'SELECT * FROM test'}) | ||
| votable = votableparse(BytesIO(response.content)) | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("error") | ||
| result = TAPResults(votable, url='http://test.com', session=None) | ||
| assert len(result) == 10 | ||
| def test_tap_create_query_maxrec_tracking(self): | ||
| service = TAPService('http://example.com/tap') | ||
| query = service.create_query("SELECT * FROM ivoa.obscore", maxrec=10) | ||
| assert query._client_set_maxrec == 10 | ||
| assert query["MAXREC"] == 10 | ||
| query2 = service.create_query("SELECT * FROM ivoa.obscore") | ||
| assert query2._client_set_maxrec is None | ||
| assert "MAXREC" not in query2 | ||
| def test_edge_case_maxrec_zero(self): | ||
| service = TAPService('http://example.com/tap') | ||
| query = service.create_query("SELECT * FROM ivoa.obscore", maxrec=0) | ||
| assert query._client_set_maxrec == 0 | ||
| assert "MAXREC" not in query | ||
| @pytest.mark.usefixtures('async_fixture') | ||
| class TestTAPAsyncOverflowWarnings: | ||
| """Test async TAP overflow warnings""" | ||
| def test_async_job_stores_maxrec(self): | ||
| service = TAPService('http://example.com/tap') | ||
| job = service.submit_job("SELECT * FROM ivoa.obscore", maxrec=5) | ||
| assert job._client_set_maxrec == 5 | ||
| def test_async_job_manual_creation_no_maxrec(self): | ||
| job_response = '''<?xml version="1.0" encoding="UTF-8"?> | ||
| <uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"> | ||
| <uws:jobId>123</uws:jobId> | ||
| <uws:phase>PENDING</uws:phase> | ||
| <uws:quote>2025-07-29T17:34:19.638</uws:quote> | ||
| <uws:creationTime>2025-07-28T17:34:19.638</uws:creationTime> | ||
| <uws:executionDuration>14400</uws:executionDuration> | ||
| <uws:destruction>2025-07-04T17:34:19.638</uws:destruction> | ||
| <uws:parameters/> | ||
| <uws:results/> | ||
| </uws:job>''' | ||
| with requests_mock.Mocker() as rm: | ||
| rm.get('http://example.com/tap/async/123', text=job_response) | ||
| job = AsyncTAPJob('http://example.com/tap/async/123') | ||
| assert job._client_set_maxrec is None | ||
| assert job.job_id == '123' | ||
| assert job.phase == 'PENDING' | ||
| def test_tap_integration_with_existing_tests(): | ||
| service = TAPService('http://example.com/tap') | ||
| query = service.create_query("SELECT * FROM ivoa.obscore") | ||
| assert query is not None | ||
| assert hasattr(query, '_client_set_maxrec') |
| #!/usr/bin/env python | ||
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| Tests for pyvo.io.vosi | ||
| Tests for pyvo.io.uws | ||
| """ | ||
| import pytest | ||
| import pyvo.io.uws as uws | ||
| from pyvo.io.uws.tree import ExtensibleUWSElement | ||
@@ -35,1 +37,181 @@ from astropy.utils.data import get_pkg_data_filename | ||
| assert job.errorsummary.message.content == 'We have problem' | ||
| def test_simple_jobinfo(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-simple-jobinfo.xml")) | ||
| assert job.jobinfo is not None | ||
| assert 'tapQueryInfo' in job.jobinfo | ||
| assert job.jobinfo['tapQueryInfo'] is not None | ||
| tap_info = job.jobinfo['tapQueryInfo'] | ||
| assert 'pct_complete' in tap_info | ||
| assert 'chunks_processed' in tap_info | ||
| assert 'total_chunks' in tap_info | ||
| assert tap_info['pct_complete'].value == 100 | ||
| assert tap_info['chunks_processed'].value == 1 | ||
| assert tap_info['total_chunks'].value == 1 | ||
| assert tap_info['pct_complete'].text == "100" | ||
| keys = list(job.jobinfo.keys()) | ||
| assert 'tapQueryInfo' in keys | ||
| def test_jobinfo_multiple_access_patterns(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-simple-jobinfo.xml")) | ||
| assert job.jobinfo is not None | ||
| tap_info1 = job.jobinfo['tapQueryInfo'] | ||
| tap_info2 = job.jobinfo.get('tapQueryInfo') | ||
| assert tap_info1 is tap_info2 | ||
| assert tap_info1 is not None | ||
| def test_jobinfo_text_content_and_types(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-typed-jobinfo.xml")) | ||
| assert job.jobinfo is not None | ||
| int_elem = job.jobinfo['integer_value'] | ||
| assert int_elem.value == 100 | ||
| assert isinstance(int_elem.value, int) | ||
| assert int_elem.text == "100" | ||
| float_elem = job.jobinfo['float_value'] | ||
| assert float_elem.value == 3.14 | ||
| assert isinstance(float_elem.value, float) | ||
| assert float_elem.text == "3.14" | ||
| string_elem = job.jobinfo['string_value'] | ||
| assert string_elem.value == "pyvo" | ||
| assert isinstance(string_elem.value, str) | ||
| assert string_elem.text == "pyvo" | ||
| empty_elem = job.jobinfo['empty_value'] | ||
| assert empty_elem.value is None | ||
| assert empty_elem.text is None | ||
| def test_jobinfo_get_methods(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-simple-jobinfo.xml")) | ||
| jobinfo = job.jobinfo | ||
| assert jobinfo.get('nonexistent') is None | ||
| assert jobinfo.get('nonexistent', 'default') == 'default' | ||
| tap_info = jobinfo.get('tapQueryInfo') | ||
| assert tap_info is not None | ||
| assert 'tapQueryInfo' in jobinfo | ||
| assert 'nonexistent' not in jobinfo | ||
| tap_info = jobinfo['tapQueryInfo'] | ||
| assert tap_info is not None | ||
| with pytest.raises(KeyError): | ||
| _ = jobinfo['nonexistent'] | ||
| def test_jobinfo_edge_cases(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-simple-jobinfo.xml")) | ||
| jobinfo = job.jobinfo | ||
| str_repr = str(jobinfo) | ||
| assert 'jobInfo' in str_repr or len(str_repr) > 0 | ||
| repr_str = repr(jobinfo) | ||
| assert 'ExtensibleUWSElement' in repr_str | ||
| assert 'elements=' in repr_str | ||
| def test_no_jobinfo(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job.xml")) | ||
| assert job.jobinfo is None | ||
| def test_nested_jobinfo_access(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-nested-jobinfo.xml")) | ||
| assert job.jobinfo is not None | ||
| query_info = job.jobinfo['queryInfo'] | ||
| assert query_info is not None | ||
| metrics = query_info['metrics'] | ||
| assert metrics is not None | ||
| assert metrics['execution_time'].value == 1500 | ||
| assert metrics['rows_returned'].value == 100 | ||
| def test_jobinfo_overwrite_behavior(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-duplicate-elements.xml")) | ||
| assert job.jobinfo is not None | ||
| status = job.jobinfo.get('status') | ||
| assert status is not None | ||
| assert status.value == "completed" | ||
| def test_extensible_element_creation(self): | ||
| element = ExtensibleUWSElement(config={}, pos=(1, 1), _name='test') | ||
| assert element._name == 'test' | ||
| assert len(element._elements) == 0 | ||
| assert 'test' in str(element) | ||
| assert 'elements=0' in repr(element) | ||
| assert 'nonexistent' not in element | ||
| assert element.get('nonexistent') is None | ||
| assert list(element.keys()) == [] | ||
| def test_jobinfo_namespace_elements(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-namespace-elements.xml")) | ||
| assert job.jobinfo is not None | ||
| progress = job.jobinfo.get('progress') | ||
| assert progress is not None | ||
| # Assert that the value is the last one of the elements with the same name | ||
| assert progress.value == 75 | ||
| unique_elem = job.jobinfo.get('uniqueElement') | ||
| assert unique_elem is not None | ||
| keys = job.jobinfo.keys() | ||
| assert len(keys) > 0 | ||
| def test_multiple_elements_same_name(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-duplicate-elements.xml")) | ||
| assert job.jobinfo is not None | ||
| _ = [key for key in job.jobinfo.keys() if 'status' in key] | ||
| status = job.jobinfo['status'] | ||
| assert status.value == "completed" | ||
| def test_empty_jobinfo(self): | ||
| job = uws.parse_job(get_pkg_data_filename( | ||
| "data/job-with-empty-jobinfo.xml")) | ||
| assert job.jobinfo is not None | ||
| assert len(job.jobinfo.keys()) == 0 | ||
| assert len(job.jobinfo._elements) == 0 | ||
| def test_jobinfo_numeric_content_conversion(self): | ||
| element = ExtensibleUWSElement(config={}, pos=(1, 1), _name='test') | ||
| element.content = 100 | ||
| assert not isinstance(element.content, str) | ||
| assert element.content == 100 | ||
| element.parse(iter([]), {}) | ||
| assert element.text == "100" | ||
| assert element.value == 100 | ||
| assert isinstance(element.text, str) |
+121
-1
@@ -39,3 +39,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| 'UWSElement', 'Reference', 'JobSummary', 'Parameters', 'Parameter', | ||
| 'Results', 'Result', 'Jobs'] | ||
| 'Results', 'Result', 'ExtensibleUWSElement', 'Jobs', 'JobInfo'] | ||
@@ -101,2 +101,3 @@ | ||
| self._message = None | ||
| self._jobinfo = None | ||
@@ -274,3 +275,14 @@ @uwselement(name='jobId', plain=True) | ||
| @uwselement(name='jobInfo', plain=True) # ← Add plain=True | ||
| def jobinfo(self): | ||
| """Implementation-specific job information""" | ||
| return self._jobinfo | ||
| @jobinfo.adder | ||
| def jobinfo(self, iterator, tag, data, config, pos): | ||
| jobinfo = JobInfo(config, pos, 'jobInfo', **data) | ||
| jobinfo.parse(iterator, config) | ||
| self._jobinfo = jobinfo | ||
| class Jobs(HomogeneousList, UWSElement): | ||
@@ -448,1 +460,109 @@ """A parsed representation of the joblist endpoint. | ||
| super().__init__(config, pos, _name, **kwargs) | ||
| class ExtensibleUWSElement(ContentMixin, UWSElement): | ||
| """ | ||
| UWS Element that can handle arbitrary child elements. | ||
| """ | ||
| def __init__(self, config=None, pos=None, _name='', **kwargs): | ||
| super().__init__(config, pos, _name, **kwargs) | ||
| self._elements = {} | ||
| self._text_content = None | ||
| self._name = _name | ||
| def _add_unknown_tag(self, iterator, tag, data, config, pos): | ||
| """Handle unknown tags without generating warnings | ||
| Parameters | ||
| ---------- | ||
| iterator : iterator | ||
| The iterator that provides the XML elements. | ||
| tag : str | ||
| The tag name of the unknown element. | ||
| data : dict | ||
| Additional data associated. | ||
| config : dict | ||
| Configuration options. | ||
| pos : tuple | ||
| The position of the element in the XML document (line, column). | ||
| Returns | ||
| ------- | ||
| ExtensibleUWSElement object | ||
| """ | ||
| element = ExtensibleUWSElement(config, pos, tag, **data) | ||
| element.parse(iterator, config) | ||
| # Last element with the same tag wins | ||
| self._elements[tag] = element | ||
| return element | ||
| def parse(self, iterator, config): | ||
| """Override parse to capture text content for leaf elements""" | ||
| super().parse(iterator, config) | ||
| # Capture text content from ContentMixin | ||
| if hasattr(self, 'content') and self.content is not None: | ||
| if isinstance(self.content, str): | ||
| self._text_content = self.content.strip() | ||
| else: | ||
| self._text_content = str(self.content).strip() if self.content else None | ||
| @property | ||
| def text(self): | ||
| """Get the text content of this element""" | ||
| if self._text_content is not None and self._text_content.strip(): | ||
| return self._text_content | ||
| if hasattr(self, 'content') and self.content is not None: | ||
| content_str = str(self.content).strip() | ||
| return content_str if content_str else None | ||
| return None | ||
| @property | ||
| def value(self): | ||
| """Get the text content converted to appropriate type""" | ||
| text = self.text | ||
| if not text: | ||
| return None | ||
| # Try to convert to int, float, if not leave as string | ||
| try: | ||
| return int(text) | ||
| except ValueError: | ||
| try: | ||
| return float(text) | ||
| except ValueError: | ||
| return text | ||
| def get(self, name, default=None): | ||
| """Get element by name (supports both local names and full namespaced names)""" | ||
| return self._elements.get(name, default) | ||
| def keys(self): | ||
| """Return all available keys (both local and namespaced)""" | ||
| return list(self._elements.keys()) | ||
| def __contains__(self, name): | ||
| """Support 'in' operator""" | ||
| return name in self._elements | ||
| def __getitem__(self, name): | ||
| """Dict-like access""" | ||
| if name not in self._elements: | ||
| raise KeyError(f"Element '{name}' not found") | ||
| return self._elements[name] | ||
| def __str__(self): | ||
| if self._text_content: | ||
| return self._text_content | ||
| return f"<{self._name} with {len(set(self._elements.values()))} children>" | ||
| def __repr__(self): | ||
| unique_elements = len(set(self._elements.values())) | ||
| return f"ExtensibleUWSElement(name='{self._name}', elements={unique_elements})" | ||
| class JobInfo(ExtensibleUWSElement): | ||
| """JobInfo element that can contain arbitrary elements.""" | ||
| def __init__(self, config=None, pos=None, _name='jobInfo', **kwargs): | ||
| super().__init__(config, pos, _name, **kwargs) |
@@ -5,37 +5,11 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| import numbers | ||
| from astropy.coordinates import SkyCoord | ||
| from astropy import units as u | ||
| from astropy.coordinates import ICRS, Galactic, FK4, FK5 | ||
| from pyvo.mivot.utils.exceptions import NoMatchingDMTypeError | ||
| from astropy.time.core import Time | ||
| from pyvo.mivot.glossary import SkyCoordMapping | ||
| from pyvo.mivot.utils.exceptions import NoMatchingDMTypeError, MappingError | ||
| class MangoRoles: | ||
| """ | ||
| Place holder for the roles (attribute names) of the mango:EpochPosition class | ||
| """ | ||
| LONGITUDE = "longitude" | ||
| LATITUDE = "latitude" | ||
| PM_LONGITUDE = "pmLongitude" | ||
| PM_LATITUDE = "pmLatitude" | ||
| PARALLAX = "parallax" | ||
| RADIAL_VELOCITY = "radialVelocity" | ||
| EPOCH = "obsDate" | ||
| FRAME = "frame" | ||
| EQUINOX = "equinox" | ||
| PMCOSDELTAPPLIED = "pmCosDeltApplied" | ||
| # Mapping of the MANGO parameters on the SkyCoord parameters | ||
| skycoord_param_default = { | ||
| MangoRoles.LONGITUDE: 'ra', MangoRoles.LATITUDE: 'dec', MangoRoles.PARALLAX: 'distance', | ||
| MangoRoles.PM_LONGITUDE: 'pm_ra_cosdec', MangoRoles.PM_LATITUDE: 'pm_dec', | ||
| MangoRoles.RADIAL_VELOCITY: 'radial_velocity', MangoRoles.EPOCH: 'obstime'} | ||
| skycoord_param_galactic = { | ||
| MangoRoles.LONGITUDE: 'l', MangoRoles.LATITUDE: 'b', MangoRoles.PARALLAX: 'distance', | ||
| MangoRoles.PM_LONGITUDE: 'pm_l_cosb', MangoRoles.PM_LATITUDE: 'pm_b', | ||
| MangoRoles.RADIAL_VELOCITY: 'radial_velocity', MangoRoles.EPOCH: 'obstime'} | ||
| class SkyCoordBuilder: | ||
@@ -51,3 +25,3 @@ ''' | ||
| def __init__(self, mivot_instance_dict): | ||
| def __init__(self, mivot_instance): | ||
| ''' | ||
@@ -58,6 +32,6 @@ Constructor | ||
| ----------- | ||
| mivot_instance_dict: viewer.MivotInstance.to_dict() | ||
| Internal dictionary of the dynamic Python object generated from the MIVOT block | ||
| mivot_instance: dict or MivotInstance | ||
| Python object generated from the MIVOT block as either a Pyhon object or a dict | ||
| ''' | ||
| self._mivot_instance_dict = mivot_instance_dict | ||
| self._mivot_instance_dict = mivot_instance.to_dict() | ||
| self._map_coord_names = None | ||
@@ -68,3 +42,5 @@ | ||
| Build a SkyCoord instance from the MivotInstance dictionary. | ||
| The operation requires the dictionary to have ``mango:EpochPosition`` as dmtype | ||
| The operation requires the dictionary to have ``mango:EpochPosition`` as dmtype. | ||
| This instance can be either the root of the dictionary or it can be one | ||
| of the Mango properties if the root object is a mango:MangoObject instance | ||
| This is a public method which could be extended to support other dmtypes. | ||
@@ -82,3 +58,15 @@ | ||
| """ | ||
| if self._mivot_instance_dict and self._mivot_instance_dict["dmtype"] == "mango:EpochPosition": | ||
| if self._mivot_instance_dict and self._mivot_instance_dict["dmtype"] == "mango:MangoObject": | ||
| property_dock = self._mivot_instance_dict["propertyDock"] | ||
| for mango_property in property_dock: | ||
| if mango_property["dmtype"] == "mango:EpochPosition": | ||
| self._mivot_instance_dict = mango_property | ||
| return self._build_sky_coord_from_mango() | ||
| raise NoMatchingDMTypeError( | ||
| "No INSTANCE with dmtype='mango:EpochPosition' has been found:" | ||
| " in the property dock of the MangoObject, " | ||
| "cannot build a SkyCoord from annotations") | ||
| elif self._mivot_instance_dict and self._mivot_instance_dict["dmtype"] == "mango:EpochPosition": | ||
| return self._build_sky_coord_from_mango() | ||
@@ -89,5 +77,6 @@ raise NoMatchingDMTypeError( | ||
| def _set_year_time_format(self, hk_field, besselian=False): | ||
| def _get_time_instance(self, hk_field, besselian=False): | ||
| """ | ||
| Format a date expressed in year as [scale]year | ||
| - Exception possibly risen by Astropy are not caught | ||
@@ -103,18 +92,80 @@ parameters | ||
| ------- | ||
| string or None | ||
| attribute value formatted as [scale]year | ||
| Time instance or None | ||
| raise | ||
| ----- | ||
| MappingError: if the Time instance cannot be built for some reason | ||
| """ | ||
| scale = "J" if not besselian else "B" | ||
| # Process complex type "mango:DateTime | ||
| # only "year" representation are supported yet | ||
| if hk_field['dmtype'] == "mango:DateTime": | ||
| representation = hk_field['representation']['value'] | ||
| timestamp = hk_field['dateTime']['value'] | ||
| if representation == "year": | ||
| return f"{scale}{timestamp}" | ||
| # Process simple attribute | ||
| else: | ||
| representation = hk_field.get("unit") | ||
| timestamp = hk_field.get("value") | ||
| if not representation or not timestamp: | ||
| raise MappingError(f"Cannot interpret field {hk_field} " | ||
| f"as a {('besselian' if besselian else 'julian')} timestamp") | ||
| time_instance = self. _build_time_instance(timestamp, representation, besselian) | ||
| if not time_instance: | ||
| raise MappingError(f"Cannot build a Time instance from {hk_field}") | ||
| return time_instance | ||
| def _build_time_instance(self, timestamp, representation, besselian=False): | ||
| """ | ||
| Build a Time instance matching the input parameters. | ||
| - Returns None if the parameters do not allow any Time setup | ||
| - Exception possibly risen by Astropy are not caught at this level | ||
| parameters | ||
| ---------- | ||
| timestamp: string or number | ||
| The timestamp must comply with the given representation | ||
| representation: string | ||
| year, iso, ... (See MANGO primitive types derived from ivoa:timeStamp) | ||
| besselian: boolean (optional) | ||
| Flag telling to use the besselain calendar. We assume it to only be | ||
| relevant for FK5 frame | ||
| returns | ||
| ------- | ||
| Time instance or None | ||
| """ | ||
| if representation in ("year", "yr", "y"): | ||
| # it the timestamp is numeric, we infer its format from the besselian flag | ||
| if isinstance(timestamp, numbers.Number): | ||
| return Time(f"{('B' if besselian else 'J')}{timestamp}", | ||
| format=("byear_str" if besselian else "jyear_str")) | ||
| if besselian: | ||
| if timestamp.startswith("B"): | ||
| return Time(f"{timestamp}", format="byear_str") | ||
| elif timestamp.startswith("J"): | ||
| # a besselain year cannot be given as "Jxxxx" | ||
| return None | ||
| elif timestamp.isnumeric(): | ||
| # we force the string representation not to break the test assertions | ||
| return Time(f"B{timestamp}", format="byear_str") | ||
| else: | ||
| if timestamp.startswith("J"): | ||
| return Time(f"{timestamp}", format="jyear_str") | ||
| elif timestamp.startswith("B"): | ||
| # a julian year cannot be given as "Bxxxx" | ||
| return None | ||
| elif timestamp.isnumeric(): | ||
| # we force the string representation not to break the test assertions | ||
| return Time(f"J{timestamp}", format="jyear_str") | ||
| # no case matches | ||
| return None | ||
| return (f"{scale}{hk_field['value']}" if hk_field["unit"] in ("yr", "year") | ||
| else hk_field["value"]) | ||
| # in the following cases, the calendar (B or J) is given by the besselian flag | ||
| # We force to use the string representation to avoid breaking unit tests. | ||
| elif representation in ("mjd", "jd", "iso"): | ||
| time = Time(f"{timestamp}", format=representation) | ||
| return (Time(time.byear_str) if besselian else time) | ||
| def _get_space_frame(self, obstime=None): | ||
| return None | ||
| def _get_space_frame(self): | ||
| """ | ||
@@ -126,7 +177,2 @@ Build an astropy space frame instance from the MIVOT annotations. | ||
| parameters | ||
| ---------- | ||
| obstime: str | ||
| Observation time is given to the space frame builder (this method) because | ||
| it must be set by the coordinate system constructor in case of FK4 frame. | ||
| returns | ||
@@ -142,12 +188,13 @@ ------- | ||
| if frame == 'fk4': | ||
| self._map_coord_names = skycoord_param_default | ||
| self._map_coord_names = SkyCoordMapping.default_params | ||
| if "equinox" in coo_sys: | ||
| equinox = self._set_year_time_format(coo_sys["equinox"], True) | ||
| return FK4(equinox=equinox, obstime=obstime) | ||
| equinox = self._get_time_instance(coo_sys["equinox"], True) | ||
| # by FK4 takes obstime=equinox by default | ||
| return FK4(equinox=equinox) | ||
| return FK4() | ||
| if frame == 'fk5': | ||
| self._map_coord_names = skycoord_param_default | ||
| self._map_coord_names = SkyCoordMapping.default_params | ||
| if "equinox" in coo_sys: | ||
| equinox = self._set_year_time_format(coo_sys["equinox"]) | ||
| equinox = self._get_time_instance(coo_sys["equinox"]) | ||
| return FK5(equinox=equinox) | ||
@@ -157,6 +204,6 @@ return FK5() | ||
| if frame == 'galactic': | ||
| self._map_coord_names = skycoord_param_galactic | ||
| self._map_coord_names = SkyCoordMapping.galactic_params | ||
| return Galactic() | ||
| self._map_coord_names = skycoord_param_default | ||
| self._map_coord_names = SkyCoordMapping.default_params | ||
| return ICRS() | ||
@@ -166,5 +213,3 @@ | ||
| """ | ||
| Build silently a SkyCoord instance from the ``mango:EpochPosition instance``. | ||
| No error is trapped, unconsistencies in the ``mango:EpochPosition`` instance will | ||
| raise Astropy errors. | ||
| Build a SkyCoord instance from the ``mango:EpochPosition instance``. | ||
@@ -184,24 +229,27 @@ - The epoch (obstime) is meant to be given in year. | ||
| for key, value in self._map_coord_names.items(): | ||
| # ignore not set parameters | ||
| if key not in self._mivot_instance_dict: | ||
| for mango_role, skycoord_field in self._map_coord_names.items(): | ||
| # ignore not mapped parameters | ||
| if mango_role not in self._mivot_instance_dict: | ||
| continue | ||
| hk_field = self._mivot_instance_dict[key] | ||
| # format the observation time (J-year by default) | ||
| if value == "obstime": | ||
| # obstime must be set into the KK4 frame but not as an input parameter | ||
| fobstime = self._set_year_time_format(hk_field) | ||
| if isinstance(kwargs["frame"], FK4): | ||
| kwargs["frame"] = self._get_space_frame(obstime=fobstime) | ||
| hk_field = self._mivot_instance_dict[mango_role] | ||
| if mango_role == "obsDate": | ||
| besselian = isinstance(kwargs["frame"], FK4) | ||
| fobstime = self._get_time_instance(hk_field, | ||
| besselian=besselian) | ||
| # FK4 class has an obstime attribute which must be set at instanciation time | ||
| if besselian: | ||
| kwargs["frame"] = FK4(equinox=kwargs["frame"].equinox, obstime=fobstime) | ||
| # This is not the case for any other space frames | ||
| else: | ||
| kwargs[value] = fobstime | ||
| # Convert the parallax (mango) into a distance | ||
| elif value == "distance": | ||
| kwargs[value] = (hk_field["value"] | ||
| * u.Unit(hk_field["unit"]).to(u.parsec, equivalencies=u.parallax())) | ||
| kwargs[value] = kwargs[value] * u.parsec | ||
| elif "unit" in hk_field and hk_field["unit"]: | ||
| kwargs[value] = hk_field["value"] * u.Unit(hk_field["unit"]) | ||
| else: | ||
| kwargs[value] = hk_field["value"] | ||
| kwargs[skycoord_field] = fobstime | ||
| # ignore not set parameters | ||
| elif (hk_value := hk_field["value"]) is not None: | ||
| # Convert the parallax (mango) into a distance | ||
| if skycoord_field == "distance": | ||
| kwargs[skycoord_field] = ( | ||
| (hk_value * u.Unit(hk_field["unit"])).to(u.parsec, equivalencies=u.parallax())) | ||
| elif "unit" in hk_field and hk_field["unit"]: | ||
| kwargs[skycoord_field] = hk_value * u.Unit(hk_field["unit"]) | ||
| else: | ||
| kwargs[skycoord_field] = hk_value | ||
| return SkyCoord(**kwargs) |
@@ -44,3 +44,4 @@ """ | ||
| """ | ||
| #: Roles of the EpochPosition class that are supported | ||
| # Roles of the EpochPosition class that are supported | ||
| # Do not change the ordering: it used below to access fields | ||
| EpochPosition = [ | ||
@@ -195,1 +196,20 @@ "longitude", | ||
| radialVelocity = "spect.dopplerVeloc.opt" | ||
| class SkyCoordMapping: | ||
| """ | ||
| Mapping of the MANGO:EpochPosition parameters to the SkyCoord parameters | ||
| """ | ||
| default_params = { | ||
| Roles.EpochPosition[0]: 'ra', Roles.EpochPosition[1]: 'dec', | ||
| Roles.EpochPosition[2]: 'distance', | ||
| Roles.EpochPosition[3]: 'radial_velocity', | ||
| Roles.EpochPosition[4]: 'pm_ra_cosdec', Roles.EpochPosition[5]: 'pm_dec', | ||
| Roles.EpochPosition[6]: 'obstime'} | ||
| galactic_params = { | ||
| Roles.EpochPosition[0]: 'l', Roles.EpochPosition[1]: 'b', | ||
| Roles.EpochPosition[2]: 'distance', | ||
| Roles.EpochPosition[3]: 'radial_velocity', | ||
| Roles.EpochPosition[4]: 'pm_l_cosb', Roles.EpochPosition[5]: 'pm_b', | ||
| Roles.EpochPosition[6]: 'obstime'} |
@@ -158,5 +158,7 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| Return a list of TEMPLATES @tableref. | ||
| Returns | ||
| ------- | ||
| list: TEMPLATES tablerefs | ||
| [string] | ||
| tablerefs of all TEMPLATES elements | ||
| """ | ||
@@ -174,10 +176,12 @@ templates_found = [] | ||
| """ | ||
| Return the TEMPLATES mapping block of the table matching @tableref. | ||
| If tableref is None returns all values of templates_blocks. | ||
| Return the TEMPLATES mapping block of the table identified @tableref. | ||
| If tableref is None or equals to Constant.FIRST_TABLE, return the first TEMPLATES. | ||
| Parameters | ||
| ---------- | ||
| tableref (str): @tableref of the searched TEMPLATES | ||
| Returns | ||
| ------- | ||
| dict: TEMPLATES tablerefs and their mapping blocks {'tableref': mapping_block, ...} | ||
| XML element: matching TEMPLATES block or None | ||
| """ | ||
@@ -188,2 +192,7 @@ # one table: name forced to DEFAULT or take the first | ||
| return tmpl | ||
| if tableref not in self._templates_blocks: | ||
| raise MivotError( | ||
| "No TEMPLATES with tableref=" + tableref) | ||
| return self._templates_blocks[tableref] | ||
@@ -197,2 +206,3 @@ | ||
| Get @dmtypes of all mapped instances | ||
| Returns | ||
@@ -199,0 +209,0 @@ ------- |
@@ -34,3 +34,4 @@ """ | ||
| """ | ||
| return self._format_xml(want.strip()) == self._format_xml(got.strip()) | ||
| return (self._format_xml(want.strip()) | ||
| == self._format_xml(got.strip())) | ||
@@ -125,5 +126,6 @@ def output_difference(self, want, got): | ||
| xmltree2 = XMLOutputChecker.xmltree_from_file(xmltree2_file).getroot() | ||
| xml_str1 = etree.tostring(xmltree1).decode("utf-8") | ||
| xml_str2 = etree.tostring(xmltree2).decode("utf-8") | ||
| xml_str1 = etree.tostring(xmltree1).decode("utf-8").strip() | ||
| xml_str2 = etree.tostring(xmltree2).decode("utf-8").strip() | ||
| checker = XMLOutputChecker() | ||
| assert checker.check_output(xml_str1, xml_str2), f"XML trees differ:\n{xml_str1}\n---\n{xml_str2}" |
| <TEMPLATES tableref="Results"> | ||
| <INSTANCE dmid="_ts_data" dmrole="" dmtype="cube:NDPoint"> | ||
| <COLLECTION dmrole="cube:NDPoint.observable" dmtype="root_collection"> | ||
| <COLLECTION dmrole="cube:NDPoint.observable"> | ||
| <INSTANCE dmtype="cube:Observable"> | ||
@@ -5,0 +5,0 @@ <ATTRIBUTE dmrole="cube:DataAxis.dependent" dmtype="ivoa:boolean" value="False"> |
@@ -1,11 +0,6 @@ | ||
| { | ||
| "COLLECTION": [ | ||
| "coords:TimeSys", | ||
| "coords:SpaceSys", | ||
| "mango:coordinates.PhotometryCoordSys", | ||
| "ds:experiment.ObsDataset" | ||
| ], | ||
| "INSTANCE": [ | ||
| "ds:experiment.Target" | ||
| ] | ||
| } | ||
| {"coords": "https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml", | ||
| "cube": "https://volute.g-vo.org/svn/trunk/projects/dm/Cube/vo-dml/Cube-1.0.vo-dml.xml", | ||
| "ds": "https://volute.g-vo.org/svn/trunk/projects/dm/DatasetMetadata/vo-dml/DatasetMetadata-1.0.vo-dml.xml", | ||
| "ivoa": "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml", | ||
| "mango": "file:/Users/sao/Documents/IVOA/GitHub/ivoa-dm-examples/tmp/Mango-v1.0.vo-dml.xml", | ||
| "meas": "https://www.ivoa.net/xml/Meas/20200908/Meas-v1.0.vo-dml.xml"} |
@@ -15,16 +15,7 @@ <?xml version="1.0" encoding="UTF-8"?> | ||
| </TEMPLATES> | ||
| <TEMPLATES tableref="coll_and_instances"> | ||
| <COLLECTION dmrole="coll"/> | ||
| <TEMPLATES tableref="some_instances"> | ||
| <INSTANCE dmtype="first"/> | ||
| <INSTANCE dmtype="second"/> | ||
| </TEMPLATES> | ||
| <TEMPLATES tableref="one_collection"> | ||
| <COLLECTION dmrole="OneCollection"/> | ||
| </TEMPLATES> | ||
| <TEMPLATES tableref="only_collection"> | ||
| <COLLECTION dmrole="First"/> | ||
| <COLLECTION dmrole="Second"/> | ||
| </TEMPLATES> | ||
| <TEMPLATES tableref="empty"> | ||
| </TEMPLATES> | ||
| <TEMPLATES tableref="empty"/> | ||
| </VODML> | ||
@@ -31,0 +22,0 @@ </RESOURCE> |
@@ -15,3 +15,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| from pyvo.mivot.viewer import MivotViewer | ||
| from . import XMLOutputChecker | ||
| from pyvo.mivot.tests import XMLOutputChecker | ||
@@ -28,2 +28,10 @@ | ||
| @pytest.fixture | ||
| def a_multiple_seeker(): | ||
| m_viewer = MivotViewer( | ||
| get_pkg_data_filename("data/test.instance_multiple.xml"), | ||
| tableref="Results") | ||
| return AnnotationSeeker(m_viewer._mapping_block) | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
@@ -92,1 +100,7 @@ def test_multiple_templates(): | ||
| assert a_seeker.get_globals_instance_from_collection("wrong_dmid", "ICRS") is None | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_multiple_seeker(a_multiple_seeker): | ||
| assert (a_multiple_seeker.get_instance_dmtypes()["TEMPLATES"] | ||
| == {"Results": ["mango:Brightness", "mango:Brightness", "mango:Brightness"]}) |
@@ -155,4 +155,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| add_epoch_positon(builder) | ||
| builder.pack_into_votable() | ||
| XmlUtils.pretty_print(builder._annotation.mivot_block) | ||
| builder.pack_into_votable(schema_check=False) | ||
| assert XmlUtils.strip_xml(builder._annotation.mivot_block) == ( | ||
@@ -175,5 +174,5 @@ XmlUtils.strip_xml(get_pkg_data_contents("data/reference/mango_object.xml")) | ||
| builder.add_mango_epoch_position(**epoch_position_mapping) | ||
| builder.pack_into_votable() | ||
| builder.pack_into_votable(schema_check=True) | ||
| assert XmlUtils.strip_xml(builder._annotation.mivot_block) == ( | ||
| XmlUtils.strip_xml(get_pkg_data_contents("data/reference/test_header_extraction.xml")) | ||
| ) |
@@ -7,10 +7,5 @@ ''' | ||
| ''' | ||
| import os | ||
| import pytest | ||
| from astropy.table import Table | ||
| from astropy.utils.data import get_pkg_data_filename | ||
| from pyvo.mivot.version_checker import check_astropy_version | ||
| from pyvo.mivot.viewer.mivot_instance import MivotInstance | ||
| from pyvo.mivot.utils.mivot_utils import MivotUtils | ||
| from pyvo.mivot.viewer import MivotViewer | ||
@@ -107,18 +102,2 @@ fake_hk_dict = { | ||
| @pytest.fixture | ||
| def m_viewer(): | ||
| data_path = get_pkg_data_filename(os.path.join("data", | ||
| "test.mivot_viewer.xml") | ||
| ) | ||
| return MivotViewer(data_path, tableref="Results") | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_xml_viewer(m_viewer): | ||
| xml_instance = m_viewer.xml_viewer.view | ||
| dm_instance = MivotInstance(**MivotUtils.xml_to_dict(xml_instance)) | ||
| assert dm_instance.to_dict() == test_dict | ||
| def test_mivot_instance_constructor(): | ||
@@ -125,0 +104,0 @@ """Test the class generation from a dict.""" |
@@ -16,3 +16,150 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| dm_raw_instances = [ | ||
| { | ||
| "dmrole": "", | ||
| "dmtype": "mango:Brightness", | ||
| "value": { | ||
| "dmtype": "ivoa:RealQuantity", | ||
| "value": None, | ||
| "unit": None, | ||
| "ref": "SC_EP_1_FLUX", | ||
| }, | ||
| }, | ||
| { | ||
| "dmrole": "", | ||
| "dmtype": "mango:Brightness", | ||
| "value": { | ||
| "dmtype": "ivoa:RealQuantity", | ||
| "value": None, | ||
| "unit": None, | ||
| "ref": "SC_EP_2_FLUX", | ||
| }, | ||
| }, | ||
| { | ||
| "dmrole": "", | ||
| "dmtype": "mango:Brightness", | ||
| "value": { | ||
| "dmtype": "ivoa:RealQuantity", | ||
| "value": None, | ||
| "unit": None, | ||
| "ref": "SC_EP_3_FLUX", | ||
| }, | ||
| }, | ||
| ] | ||
| globals_photcal = { | ||
| "dmid": "CoordSystem_XMM_EB1_id", | ||
| "dmtype": "Phot:PhotCal", | ||
| "identifier": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "XMM/EPIC/EB1", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "magnitudeSystem": { | ||
| "dmrole": "Phot:PhotCal.magnitudeSystem", | ||
| "dmtype": "Phot:MagnitudeSystem", | ||
| "type": { | ||
| "dmtype": "Phot:TypeOfMagSystem", | ||
| "value": "XMM", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "referenceSpectrum": { | ||
| "dmtype": "ivoa:anyURI", | ||
| "value": "https://xmm-tools.cosmos.esa.int/external" | ||
| "/xmm_user_support/documentation/sas_usg/USG/SASUSG.html", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| }, | ||
| "photometryFilter": { | ||
| "dmid": "CoordSystem_XMM_FILTER_EB1_id", | ||
| "dmtype": "Phot:PhotometryFilter", | ||
| "dmrole": "Phot:PhotCal.photometryFilter", | ||
| "identifier": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "XMM/EPIC/EB1", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "name": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "XMM EPIC EB1", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "description": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "Soft", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "bandName": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "EB1", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "spectralLocation": { | ||
| "dmrole": "Phot:PhotometryFilter.spectralLocation", | ||
| "dmtype": "Phot:SpectralLocation", | ||
| "ucd": { | ||
| "dmtype": "Phot:UCD", | ||
| "value": "em.wl.effective", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "unitexpression": { | ||
| "dmtype": "ivoa:Unit", | ||
| "value": "keV", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "value": {"dmtype": "ivoa:real", "value": 0.35, "unit": None, "ref": None}, | ||
| }, | ||
| "bandwidth": { | ||
| "dmrole": "Phot:PhotometryFilter.bandwidth", | ||
| "dmtype": "Phot:Bandwidth", | ||
| "ucd": { | ||
| "dmtype": "Phot:UCD", | ||
| "value": "instr.bandwidth;stat.fwhm", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "unitexpression": { | ||
| "dmtype": "ivoa:Unit", | ||
| "value": "keV", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "extent": {"dmtype": "ivoa:real", "value": 0.3, "unit": None, "ref": None}, | ||
| "start": {"dmtype": "ivoa:real", "value": 0.2, "unit": None, "ref": None}, | ||
| "stop": {"dmtype": "ivoa:real", "value": 0.5, "unit": None, "ref": None}, | ||
| }, | ||
| "transmissionCurve": { | ||
| "dmrole": "Phot:PhotometryFilter.transmissionCurve", | ||
| "dmtype": "Phot:TransmissionCurve", | ||
| "access": { | ||
| "dmrole": "Phot:TransmissionCurve.access", | ||
| "dmtype": "Phot:Access", | ||
| "reference": { | ||
| "dmtype": "ivoa:anyURI", | ||
| "value": "https://xmm-tools.cosmos.esa.int/external/xmm_user_support" | ||
| "/documentation/sas_usg/USG/SASUSG.html", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| "format": { | ||
| "dmtype": "ivoa:string", | ||
| "value": "text/html", | ||
| "unit": None, | ||
| "ref": None, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
@@ -25,10 +172,12 @@ def test_get_first_instance_dmtype(path_to_first_instance): | ||
| m_viewer = MivotViewer(votable_path=path_to_first_instance) | ||
| assert m_viewer.get_first_instance_dmtype("one_instance") == "one_instance" | ||
| assert m_viewer.get_first_instance_dmtype("coll_and_instances") == "first" | ||
| assert m_viewer.get_first_instance_dmtype("one_collection") == Constant.ROOT_COLLECTION | ||
| assert m_viewer.get_first_instance_dmtype("only_collection") == Constant.ROOT_COLLECTION | ||
| with pytest.raises(Exception, match="Can't find the first INSTANCE/COLLECTION in TEMPLATES"): | ||
| m_viewer.get_first_instance_dmtype("empty") | ||
| assert m_viewer.get_dm_instance_dmtypes("one_instance")[0] == "one_instance" | ||
| assert m_viewer.get_dm_instance_dmtypes("some_instances")[0] == "first" | ||
| with pytest.raises(Exception, match="Can't find INSTANCE in TEMPLATES"): | ||
| m_viewer.get_dm_instance_dmtypes("empty") | ||
| with pytest.raises(Exception, match="No TEMPLATES with tableref=not_existing_tableref"): | ||
| m_viewer.get_dm_instance_dmtypes("not_existing_tableref") | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
@@ -62,6 +211,5 @@ def test_table_ref(m_viewer): | ||
| assert m_viewer.get_table_ids() == ['_PKTable', 'Results'] | ||
| assert m_viewer.get_globals_models() == DictUtils.read_dict_from_file( | ||
| assert m_viewer.get_models() == DictUtils.read_dict_from_file( | ||
| get_pkg_data_filename("data/reference/globals_models.json")) | ||
| assert m_viewer.get_templates_models() == DictUtils.read_dict_from_file( | ||
| get_pkg_data_filename("data/reference/templates_models.json")) | ||
| m_viewer._connect_table('_PKTable') | ||
@@ -83,9 +231,8 @@ row = m_viewer.next_table_row() | ||
| """ | ||
| Test each getter for GLOBALS of the model_viewer specific . | ||
| Test the viewer behavior when there is no mapping | ||
| """ | ||
| m_viewer = MivotViewer(path_no_mivot) | ||
| assert m_viewer.get_table_ids() is None | ||
| assert m_viewer.get_globals_models() is None | ||
| assert m_viewer.get_models() is None | ||
| assert m_viewer.get_templates_models() is None | ||
| with pytest.raises(MappingError): | ||
@@ -99,2 +246,53 @@ m_viewer._connect_table('_PKTable') | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_instance_mutiple_in_templates(path_to_multiple_instance): | ||
| """ | ||
| Test case with a TEMPLATES containing multiple instances | ||
| """ | ||
| m_viewer = MivotViewer(votable_path=path_to_multiple_instance) | ||
| instance_dict = [] | ||
| # test the DM instances children of TEMPLATES before their values are set | ||
| for dmi in m_viewer.dm_instances: | ||
| instance_dict.append(dmi.to_hk_dict()) | ||
| assert instance_dict == dm_raw_instances | ||
| # test the DM instances children of TEMPLATES set with the values of the first row | ||
| m_viewer.next_row_view() | ||
| row_values = [] | ||
| for dmi in m_viewer.dm_instances: | ||
| row_values.append(dmi.value.value) | ||
| assert row_values == pytest.approx([0.0, 0.1, 0.2], rel=1e-3) | ||
| # test the DM instances children of TEMPLATES set with the values of the second row | ||
| m_viewer.next_row_view() | ||
| row_values = [] | ||
| for dmi in m_viewer.dm_instances: | ||
| row_values.append(dmi.value.value) | ||
| assert row_values == pytest.approx([1.0, 2.1, 3.2], rel=1e-3) | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_globals_instances(path_to_multiple_instance): | ||
| """ | ||
| Test case for the GLOBALS instance access as MivotInstances | ||
| """ | ||
| m_viewer = MivotViewer(votable_path=path_to_multiple_instance) | ||
| instance_dict = [] | ||
| photcals = 0 | ||
| photfilters = 0 | ||
| # test the DM instances children of TEMPLATES before their values are set | ||
| for dmi in m_viewer.dm_globals_instances: | ||
| if dmi.dmtype == "Phot:PhotCal": | ||
| photcals += 1 | ||
| elif dmi.dmtype == "Phot:PhotometryFilter": | ||
| photfilters += 1 | ||
| else: | ||
| assert False, f"Unexpected dmtype {dmi.dmtype} in GLOBALS " | ||
| instance_dict.append(dmi.to_hk_dict()) | ||
| assert photcals == 3 | ||
| assert photfilters == 3 | ||
| # just check the first one | ||
| assert instance_dict[0] == globals_photcal | ||
| def test_check_version(path_to_viewer): | ||
@@ -134,3 +332,11 @@ if not check_astropy_version(): | ||
| @pytest.fixture | ||
| def path_to_multiple_instance(): | ||
| votable_name = "test.instance_multiple.xml" | ||
| return get_pkg_data_filename(os.path.join("data", votable_name)) | ||
| @pytest.fixture | ||
| def path_to_first_instance(): | ||
| votable_name = "test.mivot_viewer.first_instance.xml" | ||
@@ -137,0 +343,0 @@ return get_pkg_data_filename(os.path.join("data", votable_name)) |
@@ -10,2 +10,5 @@ ''' | ||
| import pytest | ||
| from copy import deepcopy | ||
| from astropy.utils.data import get_pkg_data_filename | ||
| from astropy import units as u | ||
| from pyvo.mivot.version_checker import check_astropy_version | ||
@@ -15,3 +18,8 @@ from pyvo.mivot.viewer.mivot_instance import MivotInstance | ||
| from pyvo.mivot.utils.exceptions import NoMatchingDMTypeError | ||
| from pyvo.mivot.viewer.mivot_viewer import MivotViewer | ||
| from pyvo.utils import activate_features | ||
| # Enable MIVOT-specific features in the pyvo library | ||
| activate_features("MIVOT") | ||
| # annotations generated by Vizier as given to the MivotInstance | ||
@@ -187,3 +195,3 @@ vizier_dict = { | ||
| mivot_instance = MivotInstance(**vizier_dummy_type) | ||
| scb = SkyCoordBuilder(mivot_instance.to_dict()) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scb.build_sky_coord() | ||
@@ -197,3 +205,3 @@ | ||
| mivot_instance = MivotInstance(**vizier_dict) | ||
| scb = SkyCoordBuilder(mivot_instance.to_dict()) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
@@ -225,19 +233,72 @@ assert (str(scoo).replace("\n", "").replace(" ", "") | ||
| def test_vizier_output_with_equinox_and_parallax(): | ||
| """Test the SkyCoord issued from the modofier Vizier response * | ||
| """Test the SkyCoord issued from the modified Vizier response * | ||
| (parallax added and FK5 + Equinox frame) | ||
| """ | ||
| mivot_instance = MivotInstance(**vizier_equin_dict) | ||
| scb = SkyCoordBuilder(mivot_instance.to_dict()) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
| assert (str(scoo).replace("\n", "").replace(" ", "") | ||
| == "<SkyCoord (FK5: equinox=J2012.000): (ra, dec, distance) in " | ||
| "(deg, deg, pc)(52.26722684, 59.94033461, 600.) " | ||
| "(deg, deg, pc)(52.26722684, 59.94033461, 1666.66666667) " | ||
| "(pm_ra_cosdec, pm_dec) in mas / yr(-0.82, -1.85)>") | ||
| vizier_equin_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "FK4" | ||
| mivot_instance = MivotInstance(**vizier_equin_dict) | ||
| mydict = deepcopy(vizier_equin_dict) | ||
| mydict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "FK4" | ||
| mivot_instance = MivotInstance(**mydict) | ||
| scoo = mivot_instance.get_SkyCoord() | ||
| assert (str(scoo).replace("\n", "").replace(" ", "") | ||
| == "<SkyCoord (FK4: equinox=B2012.000, obstime=J1991.250): (ra, dec, distance) in " | ||
| "(deg, deg, pc)(52.26722684, 59.94033461, 600.) " | ||
| == "<SkyCoord (FK4: equinox=B2012.000, obstime=B1991.250): (ra, dec, distance) in " | ||
| "(deg, deg, pc)(52.26722684, 59.94033461, 1666.66666667) " | ||
| "(pm_ra_cosdec, pm_dec) in mas / yr(-0.82, -1.85)>") | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_simad_cs_output(): | ||
| """Test the SkyCoord issued from a Simbad SCS response | ||
| """ | ||
| filename = get_pkg_data_filename('data/simbad-cone-mivot.xml') | ||
| m_viewer = MivotViewer(filename, resolve_ref=True) | ||
| mivot_instance = m_viewer.dm_instance | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
| assert scoo.ra.degree == pytest.approx(269.45207696) | ||
| assert scoo.dec.degree == pytest.approx(4.69336497) | ||
| assert scoo.distance.pc == pytest.approx(1.82823411) | ||
| x = scoo.pm_ra_cosdec.value | ||
| y = (-801.551 * u.mas/u.yr).value | ||
| assert x == pytest.approx(y) | ||
| x = scoo.pm_dec.value | ||
| y = (10362.394 * u.mas/u.yr).value | ||
| assert x == pytest.approx(y) | ||
| assert str(scoo.obstime) == "J2000.000" | ||
| def test_time_representation(): | ||
| """ | ||
| Test various time representations | ||
| Inconsistent values are not tested since there are detected by ``astropy.core.Time`` | ||
| """ | ||
| # work with a copy to not alter other test functions | ||
| mydict = deepcopy(vizier_equin_dict) | ||
| mydict["obsDate"]["unit"] = "mjd" | ||
| mivot_instance = MivotInstance(**mydict) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
| assert scoo.obstime.jyear_str == "J1864.331" | ||
| mydict["obsDate"]["unit"] = "jd" | ||
| mydict["obsDate"]["value"] = "2460937.36" | ||
| mivot_instance = MivotInstance(**mydict) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
| assert scoo.obstime.jyear_str == "J2025.715" | ||
| mydict = deepcopy(vizier_equin_dict) | ||
| mydict["obsDate"]["unit"] = "iso" | ||
| mydict["obsDate"]["dmtype"] = "ivoa:string" | ||
| mydict["obsDate"]["value"] = "2025-05-03" | ||
| mivot_instance = MivotInstance(**mydict) | ||
| scb = SkyCoordBuilder(mivot_instance) | ||
| scoo = scb.build_sky_coord() | ||
| assert scoo.obstime.jyear_str == "J2025.335" |
@@ -18,3 +18,2 @@ """ | ||
| ref_ra = [ | ||
@@ -21,0 +20,0 @@ 0.04827189, |
@@ -16,4 +16,2 @@ """ | ||
| COL_INDEX = "col_index" | ||
| ROOT_COLLECTION = "root_collection" | ||
| ROOT_OBJECT = "root_object" | ||
| NOT_SET = "NotSet" | ||
@@ -20,0 +18,0 @@ ANONYMOUS_TABLE = "AnonymousTable" |
@@ -13,3 +13,2 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| from pyvo.mivot.utils.vocabulary import Constant | ||
| from pyvo.utils.prototype import prototype_feature | ||
@@ -46,5 +45,11 @@ from pyvo.mivot.utils.mivot_utils import MivotUtils | ||
| def __str__(self): | ||
| """ | ||
| return a human readable representation of object | ||
| """ | ||
| return f"<MivotInstance: dmtype=\"{self.dmtype}\">" | ||
| def __repr__(self): | ||
| """ | ||
| return a human readable (json) representation of object | ||
| return a human readable (json) unambigous representation of object | ||
| """ | ||
@@ -80,5 +85,2 @@ return DictUtils._get_pretty_json(self.to_dict()) | ||
| for key, value in kwargs.items(): | ||
| # roles are used as key and the first element in a TEMPLATE has no role | ||
| if not key: | ||
| key = Constant.ROOT_OBJECT | ||
| if isinstance(value, list): # COLLECTION | ||
@@ -137,3 +139,3 @@ setattr(self, self._remove_model_name(key), []) | ||
| """ | ||
| return SkyCoordBuilder(self.to_dict()).build_sky_coord() | ||
| return SkyCoordBuilder(self).build_sky_coord() | ||
@@ -193,2 +195,3 @@ @staticmethod | ||
| # This case is likely not to occur because MIVOT does not support dictionaries | ||
| if isinstance(obj, dict): | ||
@@ -195,0 +198,0 @@ data = {} |
@@ -45,3 +45,2 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| from pyvo.mivot.utils.mivot_utils import MivotUtils | ||
| from pyvo.mivot.viewer.xml_viewer import XMLViewer | ||
| # Use defusedxml only if already present in order to avoid a new depency. | ||
@@ -66,8 +65,11 @@ try: | ||
| ---------- | ||
| votable_path : str, parsed VOTable or DALResults instance | ||
| VOTable that will be parsed with the parser of Astropy, | ||
| which extracts the annotation block. | ||
| votable_path : str, DALResults, VOTableFile | ||
| Reference of the VOTable from which Astropy will extracts the annotation block | ||
| tableref : str, optional | ||
| Used to identify the table to process. If not specified, | ||
| the first table is taken by default. | ||
| resolve_ref : boolean | ||
| Ask for references between MIVOT instances to be resolved (referenced instances | ||
| are copied into the host object). This is usually used to copy the coordinates | ||
| systems into the object that uses them. | ||
| Parameters | ||
@@ -104,3 +106,4 @@ ---------- | ||
| self._resource_seeker = None | ||
| self._dm_instance = None | ||
| self._dm_instances = [] | ||
| self._dm_globals_instances = [] | ||
| self._resolve_ref = resolve_ref | ||
@@ -113,3 +116,4 @@ try: | ||
| self._connect_table(tableref) | ||
| self._init_instance() | ||
| self._init_instances() | ||
| self._init_globals_instances() | ||
| except MappingError as mnf: | ||
@@ -169,31 +173,35 @@ logging.error(str(mnf)) | ||
| ------- | ||
| A Python object (MivotInstance) built from the XML view of | ||
| the mapped model with attribute values set from the last values | ||
| of the last read data rows | ||
| MivotInstance: The Python object (MivotInstance) built from the XML view of the | ||
| first 'TEMPLATES' child, with the attribute values set according | ||
| to the values of the current read data row. | ||
| """ | ||
| return self._dm_instance | ||
| dm_instances = self._dm_instances | ||
| return self.dm_instances[0] if dm_instances else None | ||
| @property | ||
| def xml_view(self): | ||
| def dm_instances(self): | ||
| """ | ||
| returns | ||
| Returns | ||
| ------- | ||
| The XML view on the current data row | ||
| [MivotInstance]: The list of Python objects (MivotInstance) built from the XML views of | ||
| the TEMPLATES children, whose attribute values are set from the values | ||
| of the current read data row. | ||
| """ | ||
| return self.xml_viewer.view | ||
| return self._dm_instances | ||
| @property | ||
| def xml_viewer(self): | ||
| def dm_globals_instances(self): | ||
| """ | ||
| returns | ||
| XMLViewer tuned to browse the TEMPLATES content | ||
| Returns | ||
| ------- | ||
| [MivotInstance]: The list of Python objects (MivotInstance) built from the XML views of | ||
| the GLOBALS children, whose attribute values are set from the values | ||
| of the current read data row. | ||
| This method allows to retrieve the GLOBALS (coordinates systems usually) | ||
| even when the viewer is in ``resolve_ref=False`` mode or if the reference | ||
| to the coordinates systems have not been setup in the objects representing | ||
| the mapped data. | ||
| """ | ||
| # build a first XMLViewer for extract the content of the TEMPLATES element | ||
| model_view = XMLViewer(self._get_model_view()) | ||
| first_instance_dmype = self.get_first_instance_dmtype(tableref=self.connected_table_ref) | ||
| model_view.get_instance_by_type(first_instance_dmype) | ||
| return self._dm_globals_instances | ||
| # return an XMLViewer tuned to process the TEMPLATES content | ||
| return XMLViewer(model_view._xml_view) | ||
| @property | ||
@@ -206,8 +214,9 @@ def table_row(self): | ||
| """ | ||
| jump to the next table row and update the MivotInstance instance | ||
| jump to the next table row and update the MivotInstance instance with the row values | ||
| returns | ||
| Returns | ||
| ------- | ||
| MivotInstance: the updated instance or None | ||
| it he able end has been reached | ||
| [MivotInstance] | ||
| List of updated instances or None | ||
| it he able end has been reached | ||
| """ | ||
@@ -218,8 +227,7 @@ self.next_table_row() | ||
| return None | ||
| self._init_instances() | ||
| if self._dm_instance is None: | ||
| xml_instance = self.xml_viewer.view | ||
| self._dm_instance = MivotInstance(**MivotUtils.xml_to_dict(xml_instance)) | ||
| self._dm_instance.update(self._current_data_row) | ||
| return self._dm_instance | ||
| for dm_instance in self._dm_instances: | ||
| dm_instance.update(self._current_data_row) | ||
| return self._dm_instances | ||
@@ -234,21 +242,2 @@ def get_table_ids(self): | ||
| def get_globals_models(self): | ||
| """ | ||
| Get collection types in GLOBALS. | ||
| Collection types are GLOBALS/COLLECTION/INSTANCE@dmtype: | ||
| used for collections of static objects. | ||
| Returns | ||
| ------- | ||
| dict | ||
| A dictionary containing the dmtypes of all the top-level INSTANCE/COLLECTION of GLOBALS. | ||
| The structure of the dictionary is {'COLLECTION': [dmtypes], 'INSTANCE': [dmtypes]}. | ||
| """ | ||
| if self._annotation_seeker is None: | ||
| return None | ||
| globals_models = {} | ||
| globals_models[Ele.COLLECTION] = self._annotation_seeker.get_globals_collection_dmtypes() | ||
| globals_models[Ele.INSTANCE] = self._annotation_seeker.get_globals_instance_dmtypes() | ||
| return globals_models | ||
| def get_models(self): | ||
@@ -267,20 +256,2 @@ """ | ||
| def get_templates_models(self): | ||
| """ | ||
| Get dmtypes (except ivoa:..) of all INSTANCE/COLLECTION of all TEMPLATES. | ||
| Note: COLLECTION not implemented yet. | ||
| Returns | ||
| ------- | ||
| dict: A dictionary containing dmtypes of all INSTANCE/COLLECTION of all TEMPLATES. | ||
| The format is {'tableref': {'COLLECTIONS': [dmtypes], 'INSTANCE': [dmtypes]}, ...}. | ||
| """ | ||
| if self._annotation_seeker is None: | ||
| return None | ||
| templates_models = {} | ||
| gni = self._annotation_seeker.get_instance_dmtypes()[Ele.TEMPLATES] | ||
| for tid, tmplids in gni.items(): | ||
| templates_models[tid] = {Ele.COLLECTION: [], Ele.INSTANCE: tmplids} | ||
| return templates_models | ||
| def next_table_row(self): | ||
@@ -305,45 +276,44 @@ """ | ||
| def get_first_instance_dmtype(self, tableref=None): | ||
| def _get_templates_child_instances(self, tableref=None): | ||
| """ | ||
| Return the dmtype of the head INSTANCE (first TEMPLATES child). | ||
| If no INSTANCE is found, take the first COLLECTION. | ||
| Returns | ||
| ------- | ||
| [`xml.etree.ElementTree.Element`] | ||
| List of all INSTANCES elements children of the current TEMPLATES block | ||
| """ | ||
| if self._annotation_seeker is None: | ||
| return None | ||
| templates_block = self._annotation_seeker.get_templates_block(tableref) | ||
| return XPath.x_path(templates_block, ".//" + Ele.INSTANCE) | ||
| def get_dm_instance_dmtypes(self, tableref): | ||
| """ | ||
| Return the dmtypes of the INSTANCEs children of the | ||
| TEMPLATES block mapping the data table identified by tableref. | ||
| Parameters | ||
| ---------- | ||
| tableref : str or None, optional | ||
| Identifier of the table. | ||
| tableref : str or None | ||
| Identifier of the data table. | ||
| Returns | ||
| ------- | ||
| ~`xml.etree.ElementTree.Element` | ||
| The first child of TEMPLATES. | ||
| [string] | ||
| list of dmtypes | ||
| Raises | ||
| ------ | ||
| MivotError | ||
| if no INSTANCE can be found | ||
| """ | ||
| if self._annotation_seeker is None: | ||
| return None | ||
| child_template = self._annotation_seeker.get_templates_block(tableref) | ||
| child = child_template.findall("*") | ||
| collection = XPath.x_path(self._annotation_seeker.get_templates_block(tableref), | ||
| ".//" + Ele.COLLECTION) | ||
| instance = XPath.x_path(self._annotation_seeker.get_templates_block(tableref), ".//" + Ele.INSTANCE) | ||
| if len(collection) >= 1: | ||
| collection[0].set(Att.dmtype, Constant.ROOT_COLLECTION) | ||
| (self._annotation_seeker.get_templates_block(tableref).find(".//" + Ele.COLLECTION) | ||
| .set(Att.dmtype, Constant.ROOT_COLLECTION)) | ||
| if len(child) > 1: | ||
| if len(instance) >= 1: | ||
| for inst in instance: | ||
| if inst in child: | ||
| return inst.get(Att.dmtype) | ||
| elif len(collection) >= 1: | ||
| for coll in collection: | ||
| if coll in child: | ||
| return coll.get(Att.dmtype) | ||
| elif len(child) == 1: | ||
| if child[0] in instance: | ||
| return child[0].get(Att.dmtype) | ||
| elif child[0] in collection: | ||
| return collection[0].get(Att.dmtype) | ||
| else: | ||
| raise MivotError("Can't find the first " + Ele.INSTANCE | ||
| + "/" + Ele.COLLECTION + " in " + Ele.TEMPLATES) | ||
| dmtypes = [] | ||
| templates_block = self._annotation_seeker.get_templates_block(tableref) | ||
| instances = XPath.x_path(templates_block, ".//" + Ele.INSTANCE) | ||
| for instance in instances: | ||
| dmtypes.append(instance.get(Att.dmtype)) | ||
| if not dmtypes: | ||
| raise MivotError("Can't find " + Ele.INSTANCE + " in " + Ele.TEMPLATES) | ||
| return dmtypes | ||
| def _connect_table(self, tableref=None): | ||
@@ -392,8 +362,16 @@ """ | ||
| def _get_model_view(self): | ||
| def _get_model_view(self, xml_instance): | ||
| """ | ||
| Return an XML model view of the last read row. | ||
| This function resolves references by default. | ||
| - References are possibly resolved here. | ||
| - ``ATTRIBUTE@value`` are set with actual data row values | ||
| Returns | ||
| ------- | ||
| `xml.etree.ElementTree.Element` | ||
| XML model view of the last read row. | ||
| """ | ||
| templates_copy = deepcopy(self._templates) | ||
| templates_copy = deepcopy(xml_instance) | ||
| if self._resolve_ref is True: | ||
@@ -411,3 +389,2 @@ while StaticReferenceResolver.resolve(self._annotation_seeker, self._connected_tableref, | ||
| .get_id_unit_mapping(self._connected_tableref)) | ||
| # for ele in templates_copy.xpath("//ATTRIBUTE"): | ||
| for ele in XPath.x_path(templates_copy, ".//ATTRIBUTE"): | ||
@@ -420,21 +397,41 @@ ref = ele.get(Att.ref) | ||
| def _init_instance(self): | ||
| def _init_instances(self): | ||
| """ | ||
| Read the first table row and build the MivotInstance (_instance attribute) from it. | ||
| The table row iterator in rewind at he end to make sure we won't lost the first data row. | ||
| Read the first table row and build all MivotInstances (_dm_instances attribute) from it. | ||
| The table row iterator in rewind at the end to make sure we won't lost the first data row. | ||
| """ | ||
| if self._dm_instance is None: | ||
| if not self._dm_instances: | ||
| self.next_table_row() | ||
| first_instance = self.get_first_instance_dmtype(tableref=self.connected_table_ref) | ||
| xml_instance = self.xml_viewer.get_instance_by_type(first_instance) | ||
| self._dm_instance = MivotInstance(**MivotUtils.xml_to_dict(xml_instance)) | ||
| xml_instances = self._get_templates_child_instances(self.connected_table_ref) | ||
| self._dm_instances = [] | ||
| for xml_instance in xml_instances: | ||
| self._dm_instances.append( | ||
| MivotInstance( | ||
| **MivotUtils.xml_to_dict(self._get_model_view(xml_instance)) | ||
| )) | ||
| self.rewind() | ||
| return self._dm_instance | ||
| def _init_globals_instances(self): | ||
| """ | ||
| Build one MivotInstance for each GLOBALS/INSTANCE. Internal references are always resolved | ||
| Globals MivotInstance are stored in the _dm_globals_instances list | ||
| """ | ||
| if not self._dm_globals_instances: | ||
| globals_copy = deepcopy(self._annotation_seeker.globals_block) | ||
| while StaticReferenceResolver.resolve(self._annotation_seeker, None, | ||
| globals_copy) > 0: | ||
| pass | ||
| for ele in XPath.x_path(globals_copy, "./" + Ele.INSTANCE): | ||
| self._dm_globals_instances.append( | ||
| MivotInstance( | ||
| **MivotUtils.xml_to_dict(ele) | ||
| )) | ||
| def _set_mapped_tables(self): | ||
| """ | ||
| Set the mapped tables with a list of the TEMPLATES tablerefs. | ||
| Set the _mapped_tables list with the TEMPLATES tablerefs. | ||
| """ | ||
| if not self.resource_seeker: | ||
| self._mapped_table = [] | ||
| self._mapped_tables = [] | ||
| else: | ||
@@ -494,3 +491,3 @@ self._mapped_tables = self._annotation_seeker.get_templates() | ||
| Add column ranks to attribute having a ref. | ||
| Using ranks allow identifying columns even numpy raw have been serialised as [] | ||
| Using ranks allow identifying columns even when numpy raw have been serialised as [] | ||
| """ | ||
@@ -497,0 +494,0 @@ index_map = self._resource_seeker.get_id_index_mapping(self._connected_tableref) |
@@ -173,3 +173,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| The ID to associate with the <TEMPLATES> block. Defaults to None. | ||
| schema_check : boolean, optional | ||
| schema_check : boolean, optional (default True) | ||
| Skip the XSD validation if False (use to make test working in local mode). | ||
@@ -176,0 +176,0 @@ |
@@ -200,3 +200,3 @@ """ | ||
| else: | ||
| return ucd.startswith(dict_entry) | ||
| return ucd and ucd.startswith(dict_entry) | ||
@@ -203,0 +203,0 @@ def _check_obs_date(field): |
@@ -699,3 +699,3 @@ ''' | ||
| def pack_into_votable(self, *, report_msg="", sparse=False): | ||
| def pack_into_votable(self, *, report_msg="", sparse=False, schema_check=True): | ||
| """ | ||
@@ -706,7 +706,10 @@ Pack all mapped objects in the annotation block and put it in the VOTable. | ||
| ---------- | ||
| report_msg: string, optional (default to an empty string) | ||
| report_msg : string, optional (default to an empty string) | ||
| Content of the REPORT Mivot tag | ||
| sparse: boolean, optional (default to False) | ||
| sparse : boolean, optional (default to False) | ||
| If True, all properties are added in a independent way to the the TEMPLATES. | ||
| They are packed in a MangoObject otherwise. | ||
| schema_check : boolean, optional (default to True) | ||
| If True the MIVOT block is validated against its schema. | ||
| This may test failing due to remote accesses. | ||
| """ | ||
@@ -723,3 +726,3 @@ self._annotation.set_report(True, report_msg) | ||
| self._annotation.build_mivot_block() | ||
| self._annotation.build_mivot_block(schema_check=schema_check) | ||
| self._annotation.insert_into_votable(self._votable, override=True) |
@@ -12,5 +12,5 @@ <!-- XML Schema for the VODML lite mapping L. Michel 06/2020 --> | ||
| <!-- Required to validate mapping block within a VOTable (LM 08/2021) --> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.3" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3"/> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.2" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.2"/> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.1" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.1"/> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.3" schemaLocation="v1.3.xsd"/> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.2" schemaLocation="v1.2.xsd"/> | ||
| <xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.1" schemaLocation="v1.1.xsd"/> | ||
@@ -17,0 +17,0 @@ <!-- Top level structure of the mapping block --> |
@@ -431,6 +431,12 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'") | ||
| elif std == 'hats': | ||
| self.extra_fragments.append( | ||
| "standard_id like 'ivo://ivoa.net/std/hats#hats-%'") | ||
| elif std == 'hips': | ||
| self.extra_fragments.append( | ||
| "standard_id like 'ivo://ivoa.net/std/hips#hipslist-%'") | ||
| else: | ||
| raise dalq.DALQueryError("Service type {} is neither a full" | ||
| " standard URI nor one of the bespoke identifiers" | ||
| " {}, sia2".format(std, ", ".join(SERVICE_TYPE_MAP))) | ||
| " {}, sia2, hats, hips".format(std, ", ".join(SERVICE_TYPE_MAP))) | ||
@@ -437,0 +443,0 @@ def clone(self): |
@@ -715,3 +715,3 @@ #!/usr/bin/env python | ||
| assert len(w) == 1 | ||
| assert str(w[0].message).startswith("Partial result set.") | ||
| assert str(w[0].message).startswith("Result set limited") | ||
@@ -718,0 +718,0 @@ |
@@ -169,3 +169,3 @@ #!/usr/bin/env python | ||
| " image, sia, sia1, spectrum, ssap, ssa, scs, conesearch, line, slap," | ||
| " table, tap, sia2") | ||
| " table, tap, sia2, hats, hips") | ||
@@ -172,0 +172,0 @@ def test_legacy_term(self): |
+1
-1
@@ -8,2 +8,2 @@ # Note that we need to fall back to the hard-coded version if either | ||
| except Exception: | ||
| version = '1.7.1' | ||
| version = '1.8' |
| { | ||
| "_PKTable": { | ||
| "COLLECTION": [], | ||
| "INSTANCE": [ | ||
| "cube:SparseCube" | ||
| ] | ||
| }, | ||
| "Results": { | ||
| "COLLECTION": [], | ||
| "INSTANCE": [ | ||
| "cube:NDPoint", | ||
| "cube:Observable", | ||
| "meas:Time", | ||
| "coords:MJD", | ||
| "cube:Observable", | ||
| "meas:GenericMeasure", | ||
| "coords:PhysicalCoordinate", | ||
| "cube:Observable", | ||
| "meas:GenericMeasure", | ||
| "coords:PhysicalCoordinate", | ||
| "meas:Error", | ||
| "meas:Symmetrical" | ||
| ] | ||
| } | ||
| } |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| Test for mivot.viewer.model_viewer_level3.py and mivot.viewer.mivot_time.py | ||
| """ | ||
| import os | ||
| import pytest | ||
| from urllib.request import urlretrieve | ||
| from pyvo.mivot.version_checker import check_astropy_version | ||
| from pyvo.mivot.viewer import MivotViewer | ||
| from pyvo.mivot.utils.mivot_utils import MivotUtils | ||
| @pytest.mark.remote_data | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_model_viewer3(votable_test, simple_votable): | ||
| """ | ||
| Recursively compare an XML element with an element of MIVOT | ||
| class with the function recursive_xml_check. | ||
| This test run on 2 votables : votable_test and simple_votable. | ||
| """ | ||
| m_viewer_simple_votable = MivotViewer(votable_path=simple_votable) | ||
| MivotInstance = m_viewer_simple_votable.dm_instance | ||
| xml_simple_votable = m_viewer_simple_votable.xml_view | ||
| assert xml_simple_votable.tag == 'TEMPLATES' | ||
| recusive_xml_check(xml_simple_votable, MivotInstance) | ||
| m_viewer_votable_test = MivotViewer(votable_path=votable_test) | ||
| m_viewer_votable_test.next_row_view() | ||
| mivot_instance = m_viewer_votable_test.dm_instance | ||
| xml_votable_test = m_viewer_votable_test.xml_view | ||
| assert xml_simple_votable.tag == 'TEMPLATES' | ||
| recusive_xml_check(xml_votable_test, mivot_instance) | ||
| @pytest.mark.remote_data | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def recusive_xml_check(xml_simple_votable, MivotInstance): | ||
| if xml_simple_votable.tag == 'TEMPLATES': | ||
| recusive_xml_check(xml_simple_votable[0], MivotInstance) | ||
| else: | ||
| for child in xml_simple_votable: | ||
| if child.tag == 'INSTANCE': | ||
| for key, value in child.attrib.items(): | ||
| if key == 'dmrole': | ||
| if value == '': | ||
| if child.tag == 'ATTRIBUTE': | ||
| recusive_xml_check(child, | ||
| getattr(MivotInstance, | ||
| MivotInstance._remove_model_name( | ||
| child.get('dmrole')))) | ||
| elif child.tag == 'INSTANCE': | ||
| recusive_xml_check(child, getattr(MivotInstance, | ||
| MivotInstance._remove_model_name | ||
| (child.get('dmrole')))) | ||
| else: | ||
| if child.tag == 'ATTRIBUTE': | ||
| recusive_xml_check(child, getattr(MivotInstance, | ||
| MivotInstance._remove_model_name( | ||
| child.get('dmrole')))) | ||
| elif child.tag == 'INSTANCE': | ||
| recusive_xml_check(child, getattr(MivotInstance, | ||
| MivotInstance._remove_model_name( | ||
| child.get('dmrole')))) | ||
| elif child.tag == 'COLLECTION': | ||
| recusive_xml_check(child, getattr(MivotInstance, | ||
| MivotInstance._remove_model_name( | ||
| child.get('dmrole')))) | ||
| elif child.tag == 'COLLECTION': | ||
| for key, value in child.attrib.items(): | ||
| assert len(getattr(MivotInstance, | ||
| MivotInstance._remove_model_name(child.get('dmrole')))) == len(child) | ||
| i = 0 | ||
| for child2 in child: | ||
| recusive_xml_check(child2, getattr(MivotInstance, MivotInstance._remove_model_name | ||
| (child.get('dmrole')))[i]) | ||
| i += 1 | ||
| elif child.tag == 'ATTRIBUTE': | ||
| MivotInstance_attribute = getattr(MivotInstance, | ||
| MivotInstance._remove_model_name(child.get('dmrole'))) | ||
| for key, value in child.attrib.items(): | ||
| if key == 'dmtype': | ||
| assert MivotInstance_attribute.dmtype in value | ||
| elif key == 'value': | ||
| if (MivotInstance_attribute.value is not None | ||
| and not isinstance(MivotInstance_attribute.value, bool)): | ||
| if isinstance(MivotInstance_attribute.value, float): | ||
| pytest.approx(float(value), MivotInstance_attribute.value, 0.0001) | ||
| else: | ||
| assert value == MivotInstance_attribute.value | ||
| elif child.tag.startswith("REFERENCE"): | ||
| # Viewer not in resolve_ref mode: REFRENCEs are not filtered | ||
| pass | ||
| else: | ||
| print(child.tag) | ||
| assert False | ||
| @pytest.mark.remote_data | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_dict_model_viewer3(votable_test, simple_votable): | ||
| """ | ||
| To test the generation of the MIVOT class, the function builds a ModelViewerLevel3 | ||
| with his MIVOT class and his previous dictionary from XML. | ||
| Then, it calls the function recursive_check which recursively compares an element of MIVOT class | ||
| with the dictionary on which it was built. | ||
| MIVOT class is itself a dictionary with only essential information of the ModelViewerLevel3._dict. | ||
| This test run on 2 votables : votable_test and simple_votable. | ||
| """ | ||
| m_viewer_votable_test = MivotViewer(votable_path=votable_test) | ||
| m_viewer_votable_test.next_row_view() | ||
| mivot_instance = m_viewer_votable_test.dm_instance | ||
| _dict = MivotUtils.xml_to_dict(m_viewer_votable_test.xml_viewer.view) | ||
| recursive_check(mivot_instance, **_dict) | ||
| mivot_instance = m_viewer_votable_test.dm_instance | ||
| _dict = MivotUtils.xml_to_dict(m_viewer_votable_test.xml_view) | ||
| recursive_check(mivot_instance, **_dict) | ||
| def recursive_check(MivotInstance, **kwargs): | ||
| for key, value in kwargs.items(): | ||
| # the root instance ha no role: this makes an empty value in the unpacked dict | ||
| if key == '': | ||
| continue | ||
| if isinstance(value, list): | ||
| nbr_item = 0 | ||
| for item in value: | ||
| if isinstance(item, dict): | ||
| assert 'dmtype' in item.keys() | ||
| recursive_check(getattr(MivotInstance, | ||
| MivotInstance._remove_model_name(key))[nbr_item], | ||
| **item | ||
| ) | ||
| nbr_item += 1 | ||
| elif isinstance(value, dict) and 'value' not in value: | ||
| # for INSTANCE of INSTANCEs dmrole needs model_name | ||
| assert MivotInstance._remove_model_name(key, True) in vars(MivotInstance).keys() | ||
| recursive_check(getattr(MivotInstance, MivotInstance._remove_model_name(key, True)), **value) | ||
| else: | ||
| if isinstance(value, dict) and MivotInstance._is_leaf(**value): | ||
| assert value.keys().__contains__('dmtype' and 'value' and 'unit' and 'ref') | ||
| lower_dmtype = value['dmtype'].lower() | ||
| if "real" in lower_dmtype or "double" in lower_dmtype or "float" in lower_dmtype: | ||
| assert isinstance(value['value'], float) | ||
| elif "bool" in lower_dmtype: | ||
| assert isinstance(value['value'], bool) | ||
| elif value['dmtype'] is None: | ||
| assert (value['value'] in | ||
| ('notset', 'noset', 'null', 'none', 'NotSet', 'NoSet', 'Null', 'None')) | ||
| else: | ||
| if value['value'] is not None: | ||
| assert isinstance(value['value'], str) | ||
| recursive_check(getattr(MivotInstance, MivotInstance._remove_model_name(key)), **value) | ||
| else: | ||
| assert key == 'dmtype' or 'value' | ||
| @pytest.fixture | ||
| def votable_test(data_path, data_sample_url): | ||
| votable_name = "vizier_csc2_gal.annot.xml" | ||
| votable_path = os.path.join(data_path, "data", votable_name) | ||
| urlretrieve(data_sample_url + votable_name, | ||
| votable_path) | ||
| yield votable_path | ||
| os.remove(votable_path) | ||
| @pytest.fixture | ||
| def simple_votable(data_path, data_sample_url): | ||
| votable_name = "simple-annotation-votable.xml" | ||
| votable_path = os.path.join(data_path, "data", votable_name) | ||
| urlretrieve(data_sample_url + votable_name, | ||
| votable_path) | ||
| yield votable_path | ||
| os.remove(votable_path) | ||
| @pytest.fixture | ||
| def data_path(): | ||
| return os.path.dirname(os.path.realpath(__file__)) | ||
| @pytest.fixture | ||
| def data_sample_url(): | ||
| return "https://raw.githubusercontent.com/ivoa/dm-usecases/main/pyvo-ci-sample/" |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| Test for mivot.viewer.model_viewer_level2.py | ||
| """ | ||
| import pytest | ||
| try: | ||
| from defusedxml.ElementTree import Element as element | ||
| except ImportError: | ||
| from xml.etree.ElementTree import Element as element | ||
| from astropy.utils.data import get_pkg_data_filename | ||
| from pyvo.mivot.version_checker import check_astropy_version | ||
| from pyvo.mivot.viewer import MivotViewer | ||
| from pyvo.mivot.utils.exceptions import MivotError | ||
| @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") | ||
| def test_xml_viewer(m_viewer): | ||
| m_viewer.next_row_view() | ||
| xml_viewer = m_viewer.xml_viewer | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmrole wrong_role in any instances of the VOTable"): | ||
| xml_viewer.get_instance_by_role("wrong_role") | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmrole wrong_role in any instances of the VOTable"): | ||
| xml_viewer.get_instance_by_role("wrong_role", all_instances=True) | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmtype wrong_dmtype in any instances of the VOTable"): | ||
| xml_viewer.get_instance_by_type("wrong_dmtype") | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmtype wrong_dmtype in any instances of the VOTable"): | ||
| xml_viewer.get_instance_by_type("wrong_dmtype", all_instances=True) | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmrole wrong_role in any collections of the VOTable"): | ||
| xml_viewer.get_collection_by_role("wrong_role") | ||
| with pytest.raises(MivotError, | ||
| match="Cannot find dmrole wrong_role in any collections of the VOTable"): | ||
| xml_viewer.get_collection_by_role("wrong_role", all_instances=True) | ||
| instances_list_role = xml_viewer.get_instance_by_role("cube:MeasurementAxis.measure") | ||
| assert isinstance(instances_list_role, element) | ||
| instances_list_role = xml_viewer.get_instance_by_role("cube:MeasurementAxis.measure", all_instances=True) | ||
| assert len(instances_list_role) == 3 | ||
| instances_list_type = xml_viewer.get_instance_by_type("cube:Observable") | ||
| assert isinstance(instances_list_type, element) | ||
| instances_list_type = xml_viewer.get_instance_by_type("cube:Observable", all_instances=True) | ||
| assert len(instances_list_type) == 3 | ||
| collections_list_role = xml_viewer.get_collection_by_role("cube:NDPoint.observable") | ||
| assert isinstance(collections_list_role, element) | ||
| collections_list_role = xml_viewer.get_collection_by_role("cube:NDPoint.observable", all_instances=True) | ||
| assert len(collections_list_role) == 1 | ||
| @pytest.fixture | ||
| def m_viewer(): | ||
| return MivotViewer(get_pkg_data_filename("data/test.mivot_viewer.xml"), | ||
| tableref="Results") |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| XMLViewer provides several getters on XML instances built by | ||
| `pyvo.mivot.viewer.mivot_viewer`. | ||
| """ | ||
| from pyvo.mivot.utils.exceptions import MivotError | ||
| from pyvo.mivot.utils.xpath_utils import XPath | ||
| from pyvo.utils.prototype import prototype_feature | ||
| @prototype_feature('MIVOT') | ||
| class XMLViewer: | ||
| """ | ||
| The XMLViewer is used by `~pyvo.mivot.viewer.mivot_viewer` | ||
| to extract from the XML serialization of the model, | ||
| elements that will be used to build the dictionary from which | ||
| the Python class holding the mapped model will be generated. | ||
| """ | ||
| def __init__(self, xml_view): | ||
| self._xml_view = xml_view | ||
| @property | ||
| def view(self): | ||
| """ | ||
| getter returning the XML model view | ||
| returns | ||
| ------- | ||
| XML model view to be parsed | ||
| by different methods | ||
| """ | ||
| return self._xml_view | ||
| def get_instance_by_role(self, dmrole, all_instances=False): | ||
| """ | ||
| If all_instances is False, return the first INSTANCE matching with @dmrole. | ||
| If all_instances is True, return a list of all instances matching with @dmrole. | ||
| Parameters | ||
| ---------- | ||
| dmrole : str | ||
| The @dmrole to look for. | ||
| all_instances : bool, optional | ||
| If True, returns a list of all instances, otherwise returns the first instance. | ||
| Default is False. | ||
| Returns | ||
| ------- | ||
| Union[`xml.etree.ElementTree.Element`, List[`xml.etree.ElementTree.Element`], None] | ||
| If all_instances is False, returns the instance matching with @dmrole. | ||
| If all_instances is True, returns a list of all instances matching with @dmrole. | ||
| If no matching instance is found, returns None. | ||
| Raises | ||
| ------ | ||
| MivotElementNotFound | ||
| If dmrole is not found. | ||
| """ | ||
| instances = XPath.select_elements_by_atttribute( | ||
| self._xml_view, | ||
| "INSTANCE", | ||
| "dmrole", | ||
| dmrole) | ||
| if len(instances) == 0: | ||
| raise MivotError( | ||
| f"Cannot find dmrole {dmrole} in any instances of the VOTable") | ||
| if all_instances is False: | ||
| return instances[0] | ||
| else: | ||
| return instances | ||
| def get_instance_by_type(self, dmtype, all_instances=False): | ||
| """ | ||
| Return the instance matching with @dmtype. | ||
| If all_instances is False, returns the first INSTANCE matching with @dmtype. | ||
| If all_instances is True, returns a list of all instances matching with @dmtype. | ||
| Parameters | ||
| ---------- | ||
| dmtype : str | ||
| The @dmtype to look for. | ||
| all : bool, optional | ||
| If True, returns a list of all instances, otherwise returns the first instance. | ||
| Default is False. | ||
| Returns | ||
| ------- | ||
| Union[~`xml.etree.ElementTree.Element`, List[~`xml.etree.ElementTree.Element`], None] | ||
| If all_instances is False, returns the instance matching with @dmtype. | ||
| If all_instances is True, returns a list of all instances matching with @dmtype. | ||
| If no matching instance is found, returns None. | ||
| Raises | ||
| ------ | ||
| MivotElementNotFound | ||
| If dmtype is not found. | ||
| """ | ||
| instances = XPath.select_elements_by_atttribute( | ||
| self._xml_view, | ||
| "INSTANCE", | ||
| "dmtype", | ||
| dmtype) | ||
| if len(instances) == 0: | ||
| raise MivotError( | ||
| f"Cannot find dmtype {dmtype} in any instances of the VOTable") | ||
| if all_instances is False: | ||
| return instances[0] | ||
| else: | ||
| return instances | ||
| def get_collection_by_role(self, dmrole, all_instances=False): | ||
| """ | ||
| Return the collection matching with @dmrole. | ||
| If all_instances is False, returns the first COLLECTION matching with @dmrole. | ||
| If all_instances is True, returns a list of all COLLECTION matching with @dmrole. | ||
| Parameters | ||
| ---------- | ||
| dmrole : str | ||
| The @dmrole to look for. | ||
| all_instances : bool, optional | ||
| If True, returns a list of all COLLECTION, otherwise returns the first COLLECTION. | ||
| Default is False. | ||
| Returns | ||
| ------- | ||
| Union[~`xml.etree.ElementTree.Element`, List[~`xml.etree.ElementTree.Element`], None] | ||
| If all_instances is False, returns the collection matching with @dmrole. | ||
| If all_instances is True, returns a list of all collections matching with @dmrole. | ||
| If no matching collection is found, returns None. | ||
| Raises | ||
| ------ | ||
| MivotElementNotFound | ||
| If dmrole is not found. | ||
| """ | ||
| collections = XPath.select_elements_by_atttribute( | ||
| self._xml_view, | ||
| "COLLECTION", | ||
| "dmrole", | ||
| dmrole) | ||
| if len(collections) == 0: | ||
| raise MivotError( | ||
| f"Cannot find dmrole {dmrole} in any collections of the VOTable") | ||
| if all_instances is False: | ||
| return collections[0] | ||
| else: | ||
| return collections |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
4136111
3.09%313
2.29%26528
2.12%