From 8a054a3c1d9b2ac934712152a188c0daed250337 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 17 Sep 2024 16:50:29 -0400 Subject: [PATCH 01/41] first pass at tapregext --- .../TAPRegExt-v1.0-with-erratum1.xsd.xml | 521 +++++++ tests/voresource/VOResource-v1.1.xsd.xml | 1345 +++++++++++++++++ vo_models/tapregext/__init__.py | 5 + vo_models/tapregext/models.py | 199 +++ vo_models/voresource/models.py | 1 + 5 files changed, 2071 insertions(+) create mode 100644 tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml create mode 100644 tests/voresource/VOResource-v1.1.xsd.xml create mode 100644 vo_models/tapregext/__init__.py create mode 100644 vo_models/tapregext/models.py create mode 100644 vo_models/voresource/models.py diff --git a/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml b/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml new file mode 100644 index 0000000..c1e9ccd --- /dev/null +++ b/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml @@ -0,0 +1,521 @@ + + + + + + + + TAPRegExt + xs + tr + + + A description of the capabilities metadata for TAP services. + + + + + + + An abstract capability that fixes the standardID to the + IVOA ID for the TAP standard. + + + See vr:Capability for documentation on inherited children. + + + + + + + + + + + + + + + + + + The capabilities of a TAP server. + + + The capabilities attempt to define most issues that the + TAP standard leaves to the implementors ("may", "should"). + + + + + + + + + + Identifier of IVOA-approved data model supported by the + service. + + + + + + + + Language supported by the service. + + + + + + + + Output format supported by the service. + + + + + + + + Upload method supported by the service. + + + The absence of upload methods indicates + that the service does not support uploads + at all. + + + + + + + + Limits on the time between job creation and + destruction time. + + + + + + + + Limits on executionDuration. + + + + + + + + Limits on the size of data returned. + + + + + + + + Limits on the size of uploaded data. + + + + + + + + + + + + + + An IVOA defined data model, identified by an IVORN + intended for machine consumption and a short label + intended for human comsumption. + + + + + + + + + The IVORN of the data model. + + + + + + + + + + + A query language supported by the service. + + + Each language element can describe one or more versions + of a language. Either name alone or name-version can be + used as values for the server's LANG parameter. + + + + + + + + The name of the language without a version suffix. + + + + + + + + A version of the language supported by the server. + + + + + + + + A short, human-readable description of the + query language. + + + + + + + + Optional features of the query language, grouped by + feature type. + + + This includes listing user defined functions, geometry support, + or similar concepts. + + + + + + + + + + One version of the language supported by the service. + + + If the service supports more than one version of the + language, include multiple version elements. + It is recommended that you use a version numbering + scheme like MAJOR.MINOR in such a way that sorting + by ascending character codes will leave the most + recent version at the bottom of the list. + + + + + + + + + An optional IVORN of the language. + + + To more formally define a language supported by a service, + a resource record for the language can be created, either + centrally on the Registry of Registries or by other registry operators. + When such a record exists, the language element's ivo-id + should point to it. + + + + + + + + + + + An enumeration of non-standard or non-mandatory features of + a specific type implemented by the language. + + + A feature type is a language-dependent concept like + "user defined function", "geometry support", or possibly + "units supported". A featureList gives all features of + a given type applicable for the service. Multiple featureLists + are possible. + + All feature in a given list are of the same type. This type + is declared using the mandatory type attribute, + the value of which will typically be an IVORN. + To see values defined in TAPRegExt, + retrieve the ivo://ivoa.net/std/TAPRegExt + resource record and look for keys starting with "features-". + + + + + + + A language feature of the type given by this + element's type attribute. + + + + + + + + The type of the features given here. + + + This is in general an IVORN. TAPRegExt itself gives + IVORNs for defining user defined functions and geometry + support. + + + + + + + + + A non-standard or non-mandatory feature implemented + by the language.. + + + + + + + Formal notation for the language feature. + + + The syntax for the content of this element is defined by the + type attribute of its parent language list. + + + + + + + Human-readable freeform documentation for the language feature. + + + + + + + + + + An output format supported by the service. + + + All TAP services must support VOTable output, preserving + the MIME type of the input. Other output formats are + optional. + + The primary identifier for an output format is the MIME + type. If you want to register an output format, you must + use a MIME type (or make one up using the x- syntax), although + the concrete MIME syntax is not enforced by the schema. + + For more detailed specification, an IVORN may be used. + + + + + + + + The MIME type of this format. + + + The format of this string is specified by RFC 2045. + The service has to accept this string as a + value of the FORMAT parameter. + + + + + + + + Other values of FORMAT ("shorthands") that make the service return + documents with the MIME type. + + + + + + + + + + An optional IVORN of the output format. + + + When the MIME type does not uniquely define the + format (or a generic MIME like application/octet-stream or + text/plain is given), the IVORN can point to a key + or StandardsRegExt document defining the format more + precisely. To see values defined in TAPRegExt, + retrieve the ivo://ivoa.net/std/TAPRegExt + resource record and look for keys starting with "output-". + + + + + + + + + + An upload method as defined by IVOA. + + + Upload methods are always identified by an IVORN. + Descriptions can be obtained by dereferencing this + IVORN. To see values defined in TAPRegExt, + retrieve the ivo://ivoa.net/std/TAPRegExt + resource record and look for keys starting with "upload-". + + + You can register custom upload methods, but you must use the + standard IVORNs for the upload methods defined in the TAP + specification. + + + + + + + + + The IVORN of the upload method. + + + + + + + + + + + + Time-valued limits, all values given in seconds. + + + + + + + + The value of this limit for newly-created jobs, given in seconds. + + + + + + + The value this limit cannot be raised above, given in seconds. + + + + + + + + + + Limits on data sizes, given in rows or bytes. + + + + + + + + The value of this limit for newly-created jobs. + + + + + + + The value this limit cannot be raised above. + + + + + + + + + + A limit on some data size, either in rows or in bytes. + + + + + + + + + The unit of the limit specified. + + + + + + + + + + + + + + diff --git a/tests/voresource/VOResource-v1.1.xsd.xml b/tests/voresource/VOResource-v1.1.xsd.xml new file mode 100644 index 0000000..ff6b4a4 --- /dev/null +++ b/tests/voresource/VOResource-v1.1.xsd.xml @@ -0,0 +1,1345 @@ + + + + + + + + VOResource + xs + vr + + + An XML Schema describing a resource to be used in the Virtual + Observatory Project. + + Please see http://www.ivoa.net/documents/latest/VOResource.html + for further information on the standard governing this + schema. + + + + + + + A timestamp that is compliant with ISO8601 and fixes + the timezone indicator, if present, to "Z" (UTC). VOResource + writers should always include the timezone marker. VOResource + readers must interpret timestamps without a timezone marker as + UTC. + + + + + + + + + + + + A date stamp that can be given to a precision of either a + day (type xs:date) or seconds (type xs:dateTime). Where only a + date is given, it is to be interpreted as the span of the day + on the UTC timezone if such distinctions are relevant. + + + + + + + + + Any entity or component of a VO application that is + describable and identifiable by an IVOA Identifier. + + + + + + + A numeric grade describing the quality of the + resource description, when applicable, + to be used to indicate the confidence an end-user + can put in the resource as part of a VO application + or research study. + + + See vr:Validation for an explanation of the + allowed levels. + + + Note that when this resource is a Service, this + grade applies to the core set of metadata. + Capability and interface metadata, as well as the + compliance of the service with the interface + standard, is rated by validationLevel tag in the + capability element (see the vr:Service complex + type). + + + + + + + + Title + + + the full name given to the resource + + + + + + + + A short name or abbreviation given to the resource. + + + This name will be used where brief annotations for + the resource name are required. Applications may + use to refer to this resource in a compact display. + + + One word or a few letters is recommended. No more + than sixteen characters are allowed. + + + + + + + + Identifier + + + Unambiguous reference to the resource conforming to the IVOA + standard for identifiers + + + + + + + + A reference to this resource in a non-IVOA identifier + scheme, e.g., DOI or bibcode. Always use the an URI scheme + here, e.g., doi:10.1016/j.epsl.2011.11.037. For bibcodes, + use a form like bibcode:2008ivoa.spec.0222P. + + + + + + + + Information regarding the general curation of the resource + + + + + + + + Information regarding the general content of the resource + + + + + + + + + + The UTC date and time this resource metadata description + was created. + + + This timestamp must not be in the future. This time is + not required to be accurate; it should be at least + accurate to the day. Any non-significant time fields + should be set to zero. + + + + + + + + The UTC date this resource metadata description was last updated. + + + This timestamp must not be in the future. This time is + not required to be accurate; it should be at least + accurate to the day. Any non-significant time fields + should be set to zero. + + + + + + + + a tag indicating whether this resource is believed to be still + actively maintained. + + + + + + + + resource is believed to be currently maintained, and its + description is up to date (default). + + + + + + + resource is apparently not being maintained at the present. + + + + + + + resource publisher has explicitly deleted the resource. + + + + + + + + + + + The VOResource XML schema version + against which this instance was written. + Implementors should set this to the value of the version + attribute of their schema's root (xs:schema) element. + Clients may assume version 1.0 if this attribute is + missing. + + + + + + + + + The allowed values for describing the resource descriptions + and interfaces. + + + See the RM (v1.1, section 4) for more guidance on the use of + these values. + + + + + + + + The resource has a description that is stored in a + registry. This level does not imply a compliant + description. + + + + + + + In addition to meeting the level 0 definition, the + resource description conforms syntactically to this + standard and to the encoding scheme used. + + + + + + + In addition to meeting the level 1 definition, the + resource description refers to an existing resource that + has demonstrated to be functionally compliant. + + + When the resource is a service, it is considered to exist + and functionally compliant if use of the + service accessURL responds without error when used as + intended by the resource. If the service is a standard + one, it must also demonstrate the response is syntactically + compliant with the service standard in order to be + considered functionally compliant. If the resource is + not a service, then the ReferenceURL must be shown to + return a document without error. + + + + + + + In addition to meeting the level 2 definition, the + resource description has been inspected by a human and + judged to comply semantically to this standard as well + as meeting any additional minimum quality criteria (e.g., + providing values for important but non-required + metadata) set by the human inspector. + + + + + + + In addition to meeting the level 3 definition, the + resource description meets additional quality criteria + set by the human inspector and is therefore considered + an excellent description of the resource. Consequently, + the resource is expected to operate well as part of a + VO application or research study. + + + + + + + + + + a validation stamp combining a validation level and the ID of + the validator. + + + + + + + + The IVOA ID of the registry or organisation that + assigned the validation level. + + + + + + + + + + + + + + + + + + + + + + + A reference to a registry record. + + + This type should only be used if what is referenced + must actually be a true Registry record; vr:IdentifierURI + does not allow query or fragment parts and is hence + not suitable for everything defined by IVOA Identifiers, + in particular not standard keys (which are used for versions + of standards, for instance) or dataset identifiers. + + When something does not need to be locked down to a + reference to a single registry record, xs:anyURI should + be used. + + + + + + + + + + + A short name or abbreviation given to something. + + + This name will be used where brief annotations for + the resource name are required. Applications may + use to refer to this resource in a compact display. + + + One word or a few letters is recommended. No more + than sixteen characters are allowed. + + + + + + + + + + + + Information regarding the general curation of a resource + + + + + + + + Publisher + + + Entity (e.g. person or organisation) responsible for making the + resource available + + + + + + + + Creator + + + The entity/ies (e.g. person(s) or organisation) primarily responsible + for creating the content or constitution of the resource. + + + This is the equivalent of the author of a publication. + + + + + + + + Contributor + + + Entity responsible for contributions to the content of + the resource + + + + + + + + Date + + + Date associated with an event in the life cycle of the + resource. + + + This will typically be associated with the creation or + availability (i.e., most recent release or version) of + the resource. Use the role attribute to clarify. + + + + + + + + Label associated with creation or availablilty of a version of + a resource. + + + + + + + + Information that can be used for contacting someone with + regard to this resource. + + + + + + + + + + + The name of a potentially registered resource. That is, the entity + referred to may have an associated identifier. + + + + + + + + + + The IVOA identifier for the resource referred to. + + + + + + + + + + + + Information allowing establishing contact, e.g., for purposes + of support. + + + + + + + the name or title of the contact person. + + + This can be a person's name, e.g. “John P. Jones” or + a group, “Archive Support Team”. + + + + + + + the contact mailing address + + All components of the mailing address are given in one + string, e.g. “3700 San Martin Drive, Baltimore, MD 21218 USA”. + + + + + + + the contact email address + + + + + + the contact telephone number + + Complete international dialing codes should be given, e.g. + “+1-410-338-1234”. + + + + + + + + A reference to this entitiy in a non-IVOA identifier + scheme, e.g., orcid. Always use a URI form including + a scheme here. + + + + + + + + + + An IVOA identifier for the contact (typically when it is + an organization). + + + + + + + + + + The entity (e.g. person or organisation) primarily responsible + for creating something + + + + + + + + the name or title of the creating person or organisation + + + Users of the creation should use this name in + subsequent credits and acknowledgements. + + This should be exactly one name, preferably last name + first (as in "van der Waals, Johannes Diderik"). + + + + + + + + URL pointing to a graphical logo, which may be used to help + identify the information source + + + A logo needs only be provided for the first occurrence. + When multiple logos are supplied via multiple creator + elements, the application is free to choose which to + use. + + + + + + + + A reference to this entitiy in a non-IVOA identifier + scheme, e.g., orcid. Always use a URI form including + a scheme here. + + + + + + + + + + An IVOA identifier for the creator (typically when it is + an organization). + + + + + + + + + + + + + A string indicating what the date refers to. + + + The value of role should be taken from the vocabulary + maintained at + http://www.ivoa.net/rdf/voresource/date_role. + This includes the traditional and deprecated strings + “creation”, indicating the date that the resource + itself was created, and “update”, indicating when the + resource was updated last, and the default value, + “representative”, meaning the date is a rough + representation of the time coverage of the resource. + The preferred terms from that vocabulary are the DataCite + Metadata terms. It is expected that the vocabulary will + be kept synchronous with the corresponding list of terms + in the DataCite Metadata schema. + + + Note that this date refers to the resource; dates describing + the metadata description of the resource are handled by + the “created” and “updated” attributes of the Resource + element. + + + + + + + + + + + Information regarding the general content of a resource + + + + + + + + Subject + + + a topic, object type, or other descriptive keywords + about the resource. + + + Terms for Subject should be drawn from the Unified + Astronomy Thesaurus (http://astrothesaurus.org). + + + + + + + + Description + + + An account of the nature of the resource + + + The description may include but is not limited to an abstract, + table of contents, reference to a graphical representation of + content or a free-text account of the content. + + Note that description is xs:string-typed, which means that + whitespace is considered significant. Clients should + render empty lines as paragraph boundaries and ideally + refrain from reflowing material that looks formatted (i.e., + is broken to about 80-character lines). + + + + + + + + Source + + + a bibliographic reference from which the present resource is + derived or extracted. + + + This is intended to point to an article in the published + literature. An ADS Bibcode is recommended as a value when + available. + + + + + + + + URL pointing to a human-readable document describing this + resource. + + + + + + + + Type + + + Nature or genre of the content of the resource. Values for + type should be taken from the controlled vocabulary + http://www.ivoa.net/rdf/voresource/content_type + + + + + + + + Subject + Subject.ContentLevel + + + Description of the content level or intended audience. + Values for contentLevel should be taken from the controlled + vocabulary + http://www.ivoa.net/rdf/voresource/content_level. + + + + + + + + a description of a relationship to another resource. + + + + + + + + + + + + + + + The reference format. Recognized values include “bibcode”, + referring to a standard astronomical bibcode + (http://cdsweb.u-strasbg.fr/simbad/refcode.html). + + + + + + + + + + + A description of the relationship between one resource and one or + more other resources. + + + + + + + + the named type of relationship + + + The value of relationshipType should be taken from the + vocabulary at + http://www.ivoa.net/rdf/voresource/relationship_type. + + + + + + + + the name of resource that this resource is related to. + + + + + + + + + + + + A named group of one or more persons brought together to pursue + participation in VO applications. + + + According to the Resource Metadata Recommendation, organisations + “can be hierarchical and range in size and scope. At a high level, + an organisation could be a university, observatory, or government + agency. At a finer level, it could be a specific scientific + project, mission, or individual researcher.” + + + The main purpose of an organisation as a registered resource is + to serve as a publisher of other resources. + + + + + + + + + + Subject + + + the observatory or facility used to collect the data + contained or managed by this resource. + + + + + + + + Subject + Subject.Instrument + + + the Instrument used to collect the data contain or + managed by a resource. + + + + + + + + + + + + + + + a resource that can be invoked by a client to perform some action + on its behalf. + + + + + + + + + + Rights + + + Information about rights held in and over the resource. + + + Mainly for compatibility with DataCite, this element + is repeatable. Resource record authors are advised + that within the Virtual Observatory clients will + typically only display and/or use the rights + element occurring first and ignore later elements. + + + + + + + + a description of a general capability of the + service and how to use it. + + + This describes a general function of the + service, usually in terms of a standard + service protocol (e.g. SIA), but not + necessarily so. + + + A service can have many capabilities + associated with it, each reflecting different + aspects of the functionality it provides. + + + + + + + + + + + + A statement of usage conditions. This will typically + include a license, + which should be given as a full string (e.g., Creative Commons + Attribution 3.0 International). Further free-text information, + e.g., on how to attribute or on embargo periods is allowed. + + + + + + + + A URI identifier for a license + + + Where formal licenses are available, this URI can + reference the full license text. The IVOA may define + standard URIs for a set of recommended + licenses, in which case these should be used here. + + + + + + + + + + + a description of what the service does (in terms of + context-specific behavior), and how to use it (in terms of + an interface) + + + + + + + + A numeric grade describing the quality of the + capability description and interface, when applicable, + to be used to indicate the confidence an end-user + can put in the resource as part of a VO application + or research study. + + + See vr:ValidationLevel for an explanation of the + allowed levels. + + + + + + + + A human-readable description of what this capability + provides as part of the over-all service + + + Use of this optional element is especially encouraged when + this capability is non-standard and is one of several + capabilities listed. + + + + + + + + a description of how to call the service to access + this capability + + + Since the Interface type is abstract, one must describe + the interface using a subclass of Interface, denoting + it via xsi:type. + + + Multiple occurences can describe different interfaces to + the logically same capability, i.e. data or functionality. + That is, the inputs accepted and the output provides should + be logically the same. For example, a WebBrowser interface + given in addition to a WebService interface would simply + provide an interactive, human-targeted interface to the + underlying WebService interface. + + + + + + + + + A URI identifier for a standard service. + + + This provides a unique way to refer to a service + specification standard, such as a Simple Image Access service. + The use of an IVOA identifier here implies that a + VOResource description of the standard is registered and + accessible. + + + + + + + + + A description of a service interface. + + + Since this type is abstract, one must use an Interface subclass + to describe an actual interface. + + + Additional interface subtypes (beyond WebService and WebBrowser) are + defined in the VODataService schema. + + + + + + + + The URL (or base URL) that a client uses to access the + service. How this URL is to be interpreted and used + depends on the specific Interface subclass + + + Although the schema allows multiple occurrences of + accessURL, multiple accessURLs are deprecated. Each + interface should have exactly one access URL. Where an + interface has several mirrors, the accessURL should + reflect the “primary” (fastest, best-connected, + best-maintained) site, the one that non-sophisticated + clients will go to. + + Additional accessURLs should be put into mirrorURLs. + Advanced clients can retrieve the mirrorURLs and + empirically determine interfaces closer to their + network location. + + + + + + + + A (base) URL of a mirror of this interface. As with + accessURL, how this URL is to be interpreted and used + depends on the specific Interface subclass + + + This is intended exclusively for true mirrors, i.e., + interfaces that are functionally identical to the + original interface and that are operated by the same + publisher. Other arrangements should be represented as + separate services linked by mirror-of relationships. + + + + + + + + The mechanism the client must employ to authenticate + to the service. + + + Services not requiring authentication must provide + at least one interface definition without a + securityMethod defined. + + + + + + + + Test data for exercising the service. + + + This contains data that can be passed to the interface to + retrieve a non-empty result. This can be used by validators + within test suites. + + Exactly how agents should use the data contained in + the testQueryString depends on the concrete interface class. + For interfaces employing the HTTP GET method, however, + this will typically be urlencoded parameters (as for + the application/x-www-form-urlencoded media type). + + + + + + + + + The version of a standard interface specification that this + interface complies with. Most VO standards indicate the + version in the standardID attribute of the capability. For + these standards, the version attribute should not be used. + + + + + + + + A tag name that identifies the role the interface plays + in the particular capability. If the value is equal to + "std" or begins with "std:", then the interface refers + to a standard interface defined by the standard + referred to by the capability's standardID attribute. + + + For an interface complying with some registered + standard (i.e. has a legal standardID), the role can be + matched against interface roles enumerated in standard + resource record. The interface descriptions in + the standard record can provide default descriptions + so that such details need not be repeated here. + + + + + + + + + + + + A flag indicating whether this should be interpreted as a base + URL, a full URL, or a URL to a directory that will produce a + listing of files. + + + The default value assumed when one is not given depends on the + context. + + + + + + + + Assume a full URL--that is, one that can be invoked + directly without alteration. This usually returns a + single document or file. + + + + + + + Assume a base URL--that is, one requiring an extra portion + to be appended before being invoked. + + + + + + + Assume URL points to a directory that will return a listing + of files. + + + + + + + + + + + + + + A URL of a mirror (i.e., a functionally identical additional + service interface) to + + + + + + + + A terse, human-readable phrase indicating the function + or location of this mirror, e.g., “Primary Backup” or + “European Mirror”. + + + + + + + + + + + a description of a security mechanism. + + + This type only allows one to refer to the mechanism via a + URI. Derived types would allow for more metadata. + + + + + + + + + A URI identifier for a standard security mechanism. + + + This provides a unique way to refer to a security + specification standard. The use of an IVOA identifier here + implies that a VOResource description of the standard is + registered and accessible. + + + + + + + + + + A (form-based) interface intended to be accesed interactively + by a user via a web browser. + + + The accessURL represents the URL of the web form itself. + + + + + + + + + + + + + + A Web Service that is describable by a WSDL document. + + + The accessURL element gives the Web Service's endpoint URL. + + + + + + + + + + The location of the WSDL that describes this + Web Service. If not provided, the location is + assumed to be the accessURL with "?wsdl" appended. + + + Multiple occurrences should represent mirror copies of + the same WSDL file. + + + + + + + + + diff --git a/vo_models/tapregext/__init__.py b/vo_models/tapregext/__init__.py new file mode 100644 index 0000000..94de027 --- /dev/null +++ b/vo_models/tapregext/__init__.py @@ -0,0 +1,5 @@ +""" +Module containing VO TapRegExt classes. + +IVOA UWS Spec: https://ivoa.net/documents/TAPRegExt/20120827/REC-TAPRegExt-1.0.html +""" diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py new file mode 100644 index 0000000..e5d29ec --- /dev/null +++ b/vo_models/tapregext/models.py @@ -0,0 +1,199 @@ +"""TAPRegExt classes.""" + +from typing import Literal, Optional + +from pydantic_xml import BaseXmlModel, attr, element + +from vo_models.voresource import Capability, IdentifierURI, Interface, Validation + +NSMAP = { + "xs": "http://www.w3.org/2001/XMLSchema", + "vr": "http://www.ivoa.net/xml/VOResource/v1.0", + "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", + "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", +} + + +class TAPCapRestriction(Capability, nsmap=NSMAP): + """An abstract capability that fixes the standardID to the IVOA ID for the TAP standard.""" + + validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=[]) + description: Optional[str] = element(tag="description", default=None) + interface: Optional[list[Interface]] = element(tag="interface", default_factory=[]) + + standard_id: IdentifierURI = attr(tag="standardID", default="ivo://ivoa.net/std/TAP") + + +class DataModelType(BaseXmlModel, nsmap=NSMAP): + """IVOA defined data model, identified by an IVORN. + + Parameters: + value: + (content) - The human-readable name of the data model. + ivo_id: + (attribute) - The IVORN of the data model. + """ + + value: str + ivo_id: str = attr(tag="ivo-id") + + +class Version(BaseXmlModel, nsmap=NSMAP): + """One version of the language supported by the service. + + Parameters: + value: + (content) - The version of the language. + ivo_id: + (attribute) - An optional IVORN of the language. + """ + + value: str + ivo_id: Optional[str] = attr(tag="ivo-id", default=None) + + +class LanguageFeature(BaseXmlModel, nsmap=NSMAP): + """A non-standard or non-mandatory feature implemented by the language. + + Parameters: + form: + (element) - Formal notation for the language feature. + description: + (element) - Human-readable freeform documentation for the language feature. + """ + + form: str = element(tag="form") + description: Optional[str] = element(tag="description", default=None) + + +class OutputFormat(BaseXmlModel, nsmap=NSMAP): + """An output format supported by the service. + + Parameters: + mime: + (element) - The MIME type of this format. + alias: + (element) - Other values of FORMAT that make the service return documents with this MIME type. + """ + + mime: str = element(tag="mime") + alias: Optional[list[str]] = element(tag="alias", default_factory=[]) + + +class UploadMethod(BaseXmlModel, nsmap=NSMAP): + """An upload method as defined by IVOA. + + Parameters: + ivo_id: + (attribute) - The IVORN of the upload method. + """ + + ivo_id: str = attr(tag="ivo-id") + + +class TimeLimits(BaseXmlModel, nsmap=NSMAP): + """Time-valued limits, all values given in seconds. + + Parameters: + default: + (element) - The value of this limit for newly-created jobs, given in seconds. + hard: + (element) - The value this limit cannot be raised above, given in seconds. + """ + + default: Optional[int] = element(tag="default", default=None) + hard: Optional[int] = element(tag="hard", default=None) + + +class DataLimits(BaseXmlModel, nsmap=NSMAP): + """Limits on data sizes, given in rows or bytes. + + Parameters: + default: + (element) - The value of this limit for newly-created jobs. + hard: + (element) - The value this limit cannot be raised above. + """ + + default: Optional[int] = element(tag="default", default=None) + hard: Optional[int] = element(tag="hard", default=None) + + +class DataLimit(BaseXmlModel, nsmap=NSMAP): + """A limit on some data size, either in rows or in bytes. + + Parameters: + value: + (content) - The value of this limit. + unit: + (attribute) - The unit of the limit specified. + """ + + value: int + unit: Literal["byte", "row"] = attr(tag="unit") + + +class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): + """An enumeration of non-standard or non-mandatory features of a specific type implemented by the language. + + Parameters: + feature: + (element) - A language feature of the type given by this element's type attribute. + type: + (attribute) - The type of the language feature. + """ + + feature: Optional[list[LanguageFeature]] = element(tag="feature", default_factory=[]) + type: str = attr(tag="type") + + +class Language(BaseXmlModel, nsmap=NSMAP): + """A query language supported by the service. + + Parameters: + name: + (element) - The name of the language without a version suffix. + version: + (element) - A version of the language supported by the server. + description: + (element) - A short, human-readable description of the query language. + language_features: + (element) - Optional features of the query language, grouped by feature type. + """ + + name: str = element(tag="name") + version: list[Version] = element(tag="version") + description: Optional[str] = element(tag="description", default=None) + language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) + + +class TableAccess(TAPCapRestriction): + """The capabilities of a TAP server. + + Parameters: + data_model: + (element) - Identifier of IVOA-approved data model supported by the service. + language: + (element) - Language supported by the service. + output_format: + (element) - Output format supported by the service. + upload_method: + (element) - Upload method supported by the service. + retention_period: + (element) - Limits on the time between job creation and destruction time. + execution_duration: + (element) - Limits on executionDuration. + output_limit: + (element) - Limits on the size of data returned. + upload_limit: + (element) - Limits on the size of uploaded data. + """ + + data_model: Optional[list[DataModelType]] = element(tag="dataModel", default_factory=[]) + language: list[Language] = element(tag="language") + output_format: list[OutputFormat] = element(tag="outputFormat") + upload_method: Optional[list[UploadMethod]] = element(tag="uploadMethod", default_factory=[]) + retention_period: Optional[TimeLimits] = element(tag="retentionPeriod", default=None) + execution_duration: Optional[TimeLimits] = element(tag="executionDuration", default=None) + output_limit: Optional[DataLimits] = element(tag="outputLimit", default=None) + upload_limit: Optional[DataLimits] = element(tag="uploadLimit", default=None) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py new file mode 100644 index 0000000..e885eb9 --- /dev/null +++ b/vo_models/voresource/models.py @@ -0,0 +1 @@ +"""VOResource models.""" From e4ce7c5f20fad11886b67097eddfb7d1b912c35d Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 17 Sep 2024 16:56:52 -0400 Subject: [PATCH 02/41] voresource / stc models --- vo_models/stc/__init__.py | 0 vo_models/stc/models.py | 12 + vo_models/voresource/__init__.py | 30 +- vo_models/voresource/models.py | 516 ++++++++++++++++++++++++++++++- vo_models/voresource/types.py | 67 +++- 5 files changed, 620 insertions(+), 5 deletions(-) create mode 100644 vo_models/stc/__init__.py create mode 100644 vo_models/stc/models.py diff --git a/vo_models/stc/__init__.py b/vo_models/stc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vo_models/stc/models.py b/vo_models/stc/models.py new file mode 100644 index 0000000..0fbd79e --- /dev/null +++ b/vo_models/stc/models.py @@ -0,0 +1,12 @@ +"""STC models. TODO: NOT IMPLEMENTED""" +from pydantic_xml import BaseXmlModel + +# pylint: disable=too-few-public-methods + + +class STCResourceProfile(BaseXmlModel): + """NOT IMPLEMENTED""" + + +class STCDescriptionType(BaseXmlModel): + """NOT IMPLEMENTED""" diff --git a/vo_models/voresource/__init__.py b/vo_models/voresource/__init__.py index 5f7879b..2a86c2f 100644 --- a/vo_models/voresource/__init__.py +++ b/vo_models/voresource/__init__.py @@ -1,3 +1,27 @@ -""" -Module containing VOResource classes. -""" +"""IVOA VOResource-v1.1.xsd pydantic-xml models""" + +from vo_models.voresource.models import ( + AccessURL, + Capability, + Contact, + Content, + Creator, + Curation, + Date, + Interface, + MirrorURL, + Organisation, + Relationship, + Resource, + ResourceName, + Rights, + SecurityMethod, + Service, + Source, + UTCDateTime, + UTCTimestamp, + Validation, + ValidationLevel, + WebBrowser, + WebService, +) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index e885eb9..55c62dc 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -1 +1,515 @@ -"""VOResource models.""" +"""Pydantic-xml models for IVOA schema VOResource-v1.1.xsd""" +import datetime +from typing import Literal, Optional + +from pydantic import field_validator, networks, types +from pydantic_xml import BaseXmlModel, RootXmlModel, attr, element + +from vo_models.voresource.types import IdentifierURI, UTCDateTime, UTCTimestamp, ValidationLevel + +# pylint: disable=no-self-argument +# pylint: disable=too-few-public-methods + +NSMAP = { + "xml": "http://www.w3.org/XML/1998/namespace", + "": "http://www.w3.org/2001/XMLSchema", + "xs": "http://www.w3.org/2001/XMLSchema", + "vr": "http://www.ivoa.net/xml/VOResource/v1.0", + "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", +} + + +class Validation(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A validation stamp combining a validation level and the ID of the validator. + + Parameters: + validated_by: (networks.AnyUrl): + (attr) - The IVOA ID of the registry or organisation that assigned the validation level. + """ + + value: ValidationLevel + + validated_by: networks.AnyUrl = attr( + name="validatedBy", + ) + + +class ResourceName(BaseXmlModel, ns="vr", nsmap=NSMAP): + """The name of a potentially registered resource. + + That is, the entity referred to may have an associated identifier. + + Parameters: + ivo_id: (Optional[IdentifierURI]): + (attr) - The IVOA identifier for the resource referred to. + """ + + value: str + ivo_id: Optional[IdentifierURI] = attr(name="ivo_id", default=None) + + +class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A string indicating what the date refers to. + + The value of role should be taken from the vocabulary maintained at http://www.ivoa.net/rdf/voresource/date_role. + This includes the traditional and deprecated strings “creation”, indicating the date that the resource itself was + created, and “update”, indicating when the resource was updated last, and the default value, “representative”, + meaning the date is a rough representation of the time coverage of the resource. The preferred terms from that + vocabulary are the DataCite Metadata terms. It is expected that the vocabulary will be kept synchronous with the + corresponding list of terms in the DataCite Metadata schema. + + Parameters: + value: (UTCTimestamp): The date and time of the event. + role: (Optional[str]): + (attr) - A string indicating what the date refers to. + """ + + value: UTCTimestamp + role: Optional[str] = attr( + name="role", + default="representative", + ) + + +class Source(BaseXmlModel, ns="vr", nsmap=NSMAP): + """ + + Parameters: + value: (networks.AnyUrl): A bibliographic reference from which the present resource is derived or extracted. + format: (Optional[str]): + (attr) - The reference format. + Recognized values include "bibcode", referring to a standard astronomical bibcode + (http://cdsweb.u-strasbg.fr/simbad/refcode.html). + """ + + value: networks.AnyUrl + format: Optional[str] = attr(name="format", default=None) + + +class Rights(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A statement of usage conditions. + + This will typically include a license, which should be given as a full string + (e.g., Creative Commons Attribution 3.0 International). Further free-text information, e.g., on how to attribute or + on embargo periods is allowed. + + Parameters: + value: (str): The statement of usage conditions. + rights_uri: (Optional[networks.AnyUrl]): + (attr) - A URI identifier for a license + """ + + value: str + rights_uri: Optional[networks.AnyUrl] = attr(name="rightsURI", default=None) + + +class AccessURL(BaseXmlModel, ns="vr", nsmap=NSMAP): + """The URL (or base URL) that a client uses to access the service. + + Parameters: + value: (networks.AnyUrl): The URL (or base URL) that a client uses to access the service. + use: (Literal["full", "base", "dir"]): + (attr) - A flag indicating whether this should be interpreted as a base URL, a full URL, or a URL to a + directory that will produce a listing of files. Allowed values are: + "full" - Assume a full URL--that is, one that can be invoked directly without alteration. + "base" - Assume a base URL--that is, one requiring an extra portion to be appended before being invoked. + "dir" - Assume URL points to a directory that will return a listing of files. + """ + + value: networks.AnyUrl + + use: Literal["full", "base", "dir"] = attr(name="use") + + +class MirrorURL(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A URL of a mirror (i.e., a functionally identical additional service interface) to + + Parameters: + value: (networks.AnyUrl): A URL of a mirror + title: (Optional[str]): + (attr) - A terse, human-readable phrase indicating the function or location of this mirror, e.g., + "Primary Backup" or "European Mirror". + """ + + value: networks.AnyUrl + title: Optional[str] = attr(name="title", default=None) + + +class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): + """Information allowing establishing contact, e.g., for purposes of support. + + Parameters: + ivo_id: (Optional[Type]): + (attr) - An IVOA identifier for the contact (typically when it is an organization). + name: (ResourceName): + (element) - The name or title of the contact person. + This can be a person's name, e.g. “John P. Jones” or a group, “Archive Support Team”. + address: (Optional[str]): + (element) - The contact mailing address + All components of the mailing address are given in one string, e.g. + “3700 San Martin Drive, Baltimore, MD 21218 USA”. + email: (Optional[str]): + (element) - The contact email address + telephone: (Optional[str]): + (element) - The contact telephone number + Complete international dialing codes should be given, e.g. + “+1-410-338-1234”. + alt_identifier: (Optional[list[networks.AnyUrl]]): + (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. Always use a URI form + including a scheme here. + """ + + ivo_id: Optional[IdentifierURI] = attr(name="ivo_id") + + name: ResourceName = element(tag="name") + address: Optional[str] = element(tag="address", default=None) + email: Optional[str] = element(tag="email", default=None) + telephone: Optional[str] = element(tag="telephone", default=None) + alt_identifier: Optional[list[networks.AnyUrl]] = element(tag="altIdentifier", default_factory=list) + + +class Creator(BaseXmlModel, ns="vr", nsmap=NSMAP): + """The entity (e.g. person or organisation) primarily responsible for creating something + + Parameters: + ivo_id: (Optional[IdentifierURI]): + (attr) - An IVOA identifier for the creator (typically when it is an organization). + name: (ResourceName): + (element) - The name or title of the creating person or organisation + Users of the creation should use this name in + subsequent credits and acknowledgements. + This should be exactly one name, preferably last name + first (as in "van der Waals, Johannes Diderik"). + logo: (Optional[networks.AnyUrl]): + (element) - URL pointing to a graphical logo, which may be used to help identify the information source. + alt_identifier: (Optional[list[networks.AnyUrl]]): + (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. Always use a URI form + including a scheme here. + """ + + ivo_id: Optional[IdentifierURI] = attr(name="ivo_id", default=None) + + name: ResourceName = element(tag="name") + logo: Optional[networks.AnyUrl] = element(tag="logo", default=None) + alt_identifier: Optional[list[networks.AnyUrl]] = element(tag="altIdentifier", default_factory=list) + + +class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A description of the relationship between one resource and one or more other resources. + + Parameters: + relationship_type: (str): + (element) - The named type of relationship + The value of relationshipType should be taken from the vocabulary at + http://www.ivoa.net/rdf/voresource/relationship_type. + related_resource: (list[ResourceName]): + (element) - the name of resource that this resource is related to. + """ + + relationship_type: str = element(tag="relationshipType") + + related_resource: list[ResourceName] = element(tag="relatedResource") + + +class SecurityMethod(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A description of a security mechanism. + + This type only allows one to refer to the mechanism via a URI. Derived types would allow for more metadata. + + Parameters: + standard_id: (Optional[networks.AnyUrl]): + (attr) - A URI identifier for a standard security mechanism. + This provides a unique way to refer to a security specification standard. The use of an IVOA identifier + here implies that a VOResource description of the standard is registered and accessible. + """ + + standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) + + +class Curation(BaseXmlModel, ns="vr", nsmap=NSMAP): + """Information regarding the general curation of a resource + + Parameters: + publisher: (ResourceName): + (element) - Entity (e.g. person or organisation) responsible for making the resource available + creator: (Optional[list[Creator]]): + (element) - The entity/ies (e.g. person(s) or organisation) primarily responsible for creating the content + or constitution of the resource. + This is the equivalent of the author of a publication. + contributor: (Optional[list[ResourceName]]): + (element) - Entity responsible for contributions to the content of the resource + date: (Optional[list[Date]]): + (element) - Date associated with an event in the life cycle of the resource. + This will typically be associated with the creation or availability (i.e., most recent release or + version) of the resource. Use the role attribute to clarify. + version: (Optional[str]): + (element) - Label associated with creation or availablilty of a version of a resource. + contact: (list[Contact]): + (element) - Information that can be used for contacting someone with regard to this resource. + """ + + publisher: ResourceName = element(tag="publisher") + creator: Optional[list[Creator]] = element(tag="creator", default_factory=list) + contributor: Optional[list[ResourceName]] = element(tag="contributor", default_factory=list) + date: Optional[list[Date]] = element(tag="date", default_factory=list) + version: Optional[str] = element(tag="version", default=None) + contact: list[Contact] = element(tag="contact") + + +class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): + """Information regarding the general content of a resource + + Parameters: + subject: (list[str]): + (element) - A topic, object type, or other descriptive keywords about the resource. + Terms for Subject should be drawn from the Unified Astronomy Thesaurus (http://astrothesaurus.org). + description: (str): + (element) - An account of the nature of the resource + The description may include but is not limited to an abstract, table of contents, reference to a + graphical representation of content or a free-text account of the content. + source: (Optional[Source]): + (element) - A bibliographic reference from which the present resource is derived or extracted. + This is intended to point to an article in the published literature. An ADS Bibcode is recommended as a + value when available. + reference_url: (networks.AnyUrl): + (element) - URL pointing to a human-readable document describing this resource. + type: (Optional[list[str]]): + (element) - Nature or genre of the content of the resource. Values for type should be taken from the + controlled vocabulary http://www.ivoa.net/rdf/voresource/content_type + content_level: (Optional[list[str]]): + (element) - Description of the content level or intended audience. Values for contentLevel should be taken + from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_level. + relationship: (Optional[list[Relationship]]): + (element) - a description of a relationship to another resource. + """ + + subject: list[str] = element(tag="subject") + description: str = element(tag="description") + source: Optional[Source] = element(tag="source", default=None) + reference_url: networks.AnyUrl = element(tag="referenceURL") + type: Optional[list[str]] = element(tag="type", default=None) + content_level: Optional[list[str]] = element(tag="contentLevel", default_factory=list) + relationship: Optional[list[Relationship]] = element(tag="relationship", default_factory=list) + + +class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A description of a service interface. + + Since this type is abstract, one must use an Interface subclass to describe an actual interface. + + Additional interface subtypes (beyond WebService and WebBrowser) are defined in the VODataService schema. + + Parameters: + version: (Optional[str]): + (attr) - The version of a standard interface specification that this interface complies with. + Most VO standards indicate the version in the standardID attribute of the capability. For these standards, + the version attribute should not be used. + role: (Optional[str]): + (attr) - A tag name that identifies the role the interface plays in the particular capability. + If the value is equal to 'std' or begins with 'std:', then the interface refers to a standard + interface defined by the standard referred to by the capability's standardID attribute. + access_url: (list[AccessURL]): + (element) - The URL (or base URL) that a client uses to access the service. + How this URL is to be interpreted and used depends on the specific Interface subclass + mirror_url: (Optional[list[MirrorURL]]): + (element) - A (base) URL of a mirror of this interface. + As with accessURL, how this URL is to be interpreted and used depends on the specific Interface subclass + security_method: (Optional[list[SecurityMethod]]): + (element) - The mechanism the client must employ to authenticate to the service. + test_querystring: (Optional[str]): + (element) - Test data for exercising the service. + """ + + version: Optional[str] = attr(name="version", default=None) + role: Optional[str] = attr(name="role", default=None) + + access_url: list[AccessURL] = element(tag="accessURL") + mirror_url: Optional[list[MirrorURL]] = element(tag="mirrorURL", default_factory=list) + security_method: Optional[list[SecurityMethod]] = element(tag="securityMethod", default_factory=list) + test_querystring: Optional[str] = element(tag="testQueryString", default=None) + + +class WebBrowser(Interface, ns="vr", nsmap=NSMAP): + """A (form-based) interface intended to be accesed interactively by a user via a web browser.""" + + +class WebService(Interface, ns="vr", nsmap=NSMAP): + """A Web Service that is describable by a WSDL document. + + The accessURL element gives the Web Service's endpoint URL. + + Parameters: + wsdl_url: (Optional[list[networks.AnyUrl]]): + (element) - The location of the WSDL that describes this Web Service. If not provided, the location is + assumed to be the accessURL with "?wsdl" appended. + Multiple occurrences should represent mirror copies of the same WSDL file. + """ + + wsdl_url: Optional[list[networks.AnyUrl]] = element(tag="wsdlURL", default_factory=list) + + +class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): + """Any entity or component of a VO application that is describable and + identifiable by an IVOA Identifier. + + Parameters: + created: (UTCTimestamp): + (attr) - The UTC date and time this resource metadata description was created. + This timestamp must not be in the future. This time is not required to be accurate; it should be at + least accurate to the day. Any non-significant time fields should be set to zero. + updated: (UTCTimestamp): + (attr) - The UTC date this resource metadata description was last updated. + This timestamp must not be in the future. This time is not required to be accurate; it should be at + least accurate to the day. Any non-significant time fields should be set to zero. + status: (Literal["active", "inactive", "deleted"]): + (attr) - A tag indicating whether this resource is believed to be still actively maintained. + "active" - Resource is believed to be currently maintained, and its description is up to date (default) + "inactive" - Resource is apparently not being maintained at the present + "deleted" - Resource publisher has explicitly deleted the resource. + version: (Optional[str]): + (attr) - The VOResource XML schema version against which this instance was written. + Implementors should set this to the value of the version attribute of their schema's root (xs:schema) + element. Clients may assume version 1.0 if this attribute is missing. + validation_level: (Optional[list[Validation]]): + (element) - A numeric grade describing the quality of the resource description, when applicable, to be used + to indicate the confidence an end-user can put in the resource as part of a VO application or research + study. + title: (str): + (element) - The full name given to the resource + short_name: (Optional[str]): + (element) - A short name or abbreviation given to the resource. + One word or a few letters is recommended. No more than sixteen characters are allowed. + identifier: (networks.AnyUrl): + (element) - Unambiguous reference to the resource conforming to the IVOA standard for identifiers + alt_identifier: (Optional[list[networks.AnyUrl]]): + (element) - A reference to this resource in a non-IVOA identifier scheme, e.g., DOI or bibcode. Always use + the an URI scheme here, e.g., bibcode:2008ivoa.spec.0222P. + curation: (Curation): + (element) - Information regarding the general curation of the resource + content: (Content): + (element) - Information regarding the general content of the resource + """ + + created: UTCTimestamp = attr(name="created") + updated: UTCTimestamp = attr(name="updated") + status: Literal["active", "inactive", "deleted"] = attr(name="status") + version: Optional[str] = attr(name="version", default=None) + + validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) + title: str = element(tag="title") + short_name: Optional[str] = element(tag="shortName", default=None) + identifier: networks.AnyUrl = element(tag="identifier") + alt_identifier: Optional[list[networks.AnyUrl]] = element(tag="altIdentifier", default_factory=list) + curation: Curation = element(tag="curation") + content: Content = element(tag="content") + + @field_validator("created", "updated") + def _validate_timestamps(cls, values): + """Ensure that the created and updated timestamps are not in the future""" + for timestamp in ("created", "updated"): + if values[timestamp] > datetime.datetime.utcnow(): + raise ValueError(f"{timestamp} timestamp must not be in the future") + + @field_validator("short_name") + def _validate_short_name(cls, values): + """Ensure that the short name is no more than 16 characters""" + if values["short_name"] and len(values["short_name"]) > 16: + raise ValueError("Short name must be no more than 16 characters") + + +class Organisation(Resource, ns="vr", nsmap=NSMAP): + """A named group of one or more persons brought together to pursue participation in VO applications. + + Parameters: + created: (UTCTimestamp): + (attr) - The UTC date and time this resource metadata description was created. + updated: (UTCTimestamp): + (attr) - The UTC date this resource metadata description was last updated. + status: (str): + (attr) - a tag indicating whether this resource is believed to be still actively maintained. + version: (Optional[str]): + (attr) - The VOResource XML schema version against which this instance was written. + Implementors should set this to the value of the version attribute of their schema's root (xs:schema) + element. Clients may assume version 1.0 if this attribute is missing. + facility: (Optional[list[ResourceName]]): + (element) - The observatory or facility used to collect the data contained or managed by this resource. + instrument: Optional[list[ResourceName]]): + (element) - The instrument used to collect the data contained or managed by a resource. + + """ + + created: UTCTimestamp = attr(name="created") + updated: UTCTimestamp = attr(name="updated") + status: str = attr(name="status") + version: Optional[str] = attr(name="version", default=None) + + facility: Optional[list[ResourceName]] = element(tag="facility", default_factory=list) + instrument: Optional[list[ResourceName]] = element(tag="instrument", default_factory=list) + + +class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): + """A description of what the service does (in terms of context-specific behavior), and how to use it + (in terms of an interface) + + Parameters: + standard_id: (Optional[networks.AnyUrl]): + (attr) - A URI identifier for a standard service. + This provides a unique way to refer to a service specification standard, such as a Simple Image Access + service. The use of an IVOA identifier here implies that a VOResource description of the standard is + registered and accessible. + validation_level: (Optional[list[Validation]]): + (element) - A numeric grade describing the quality of the capability description and interface, when + applicable, to be used to indicate the confidence an end-user can put in the resource as part of a + VO application or research study. + See ValidationLevel for an explanation of the allowed levels. + description: (Optional[str]): + (element) - A human-readable description of what this capability provides as part of the over-all service. + Use of this optional element is especially encouraged when this capability is non-standard and is one + of several capabilities listed. + interface: (Optional[list[Interface]]): + (element) - A description of how to call the service to access this capability. + Since the Interface type is abstract, one must describe the interface using a subclass of Interface, + denoting it via xsi:type. + """ + + standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) + + validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) + description: Optional[str] = element(tag="description", default=None) + interface: Optional[list[Interface]] = element(tag="interface", default_factory=list) + + +class Service(Resource, ns="vr", nsmap=NSMAP): + """A resource that can be invoked by a client to perform some action on its behalf. + + Parameters: + created: (UTCTimestamp): + (attr) - The UTC date and time this resource metadata description was created. + updated: (UTCTimestamp): + (attr) - The UTC date this resource metadata description was last updated. + status: (str): + (attr) - A tag indicating whether this resource is believed to be still actively maintained. + version: (Optional[str]): + (attr) - The VOResource XML schema version against which this instance was written. + Implementors should set this to the value of the version attribute of their schema's root (xs:schema) + element. Clients may assume version 1.0 if this attribute is missing. + rights: (Optional[list[Rights]]): + (element) - Information about rights held in and over the resource. + Mainly for compatibility with DataCite, this elementis repeatable. Resource record authors are advised + that within the Virtual Observatory clients will typically only display and/or use the rights element + occurring first and ignore later elements. + capability: (Optional[list[Capability]]): + (element) - A description of a general capability of the service and how to use it. + This describes a general function of the service, usually in terms of a standard service protocol + (e.g. SIA), but not necessarily so. + A service can have many capabilities associated with it, each reflecting different aspects of the + functionality it provides. + """ + + created: UTCTimestamp = attr(name="created") + updated: UTCTimestamp = attr(name="updated") + status: str = attr(name="status") + version: Optional[str] = attr(name="version", default=None) + + rights: Optional[list[Rights]] = element(tag="rights", default_factory=list) + capability: Optional[list[Capability]] = element(tag="capability", default_factory=list) diff --git a/vo_models/voresource/types.py b/vo_models/voresource/types.py index c6412ba..f65a867 100644 --- a/vo_models/voresource/types.py +++ b/vo_models/voresource/types.py @@ -2,9 +2,13 @@ import re from datetime import datetime +from enum import Enum -from pydantic import GetCoreSchemaHandler +from pydantic import Field, GetCoreSchemaHandler from pydantic_core import CoreSchema, core_schema +from typing_extensions import Annotated + +# pylint: disable=too-few-public-methods class UTCTimestamp(datetime): @@ -98,3 +102,64 @@ def isoformat(self, sep: str = "T", timespec: str = "milliseconds") -> str: """ iso_dt = super().isoformat(sep=sep, timespec=timespec) return iso_dt.replace("+00:00", "Z") + + +class UTCDateTime(str): + """A date stamp that can be given to a precision of either a day (type + xs:date) or seconds (type xs:dateTime). Where only a date is given, + it is to be interpreted as the span of the day on the UTC timezone + if such distinctions are relevant.""" + + +class ValidationLevel(Enum): + """ + The allowed values for describing the resource descriptions and interfaces. + """ + + VALUE_0 = 0 + """ + The resource has a description that is stored in a registry. This level does not imply a compliant description. + """ + VALUE_1 = 1 + """ + In addition to meeting the level 0 definition, the resource description conforms syntactically to this standard + and to the encoding scheme used. + """ + VALUE_2 = 2 + """ + In addition to meeting the level 1 definition, the resource description refers to an existing resource that has + demonstrated to be functionally compliant. + """ + VALUE_3 = 3 + """ + In addition to meeting the level 2 definition, the resource description has been inspected by a human and judged + to comply semantically to this standard as well as meeting any additional minimum quality criteria (e.g., providing + values for important but non-required metadata) set by the human inspector. + """ + VALUE_4 = 4 + """ + In addition to meeting the level 3 definition, the resource description meets additional quality criteria set by + the human inspector and is therefore considered an excellent description of the resource. Consequently, the resource + is expected to operate well as part of a VO application or research study. + """ + + +AuthorityID = Annotated[ + str, Field(pattern=r"[\w\d][\w\d\-_\.!~\*'\(\)\+=]{2,}", description="The authority identifier for the resource.") +] + +ResourceKey = Annotated[ + str, + Field( + pattern=r"[\w\d\-_\.!~\*'\(\)\+=]+(/[\w\d\-_\.!~\*'\(\)\+=]+)*", + description="The resource key for the resource.", + ), +] + +IdentifierURI = Annotated[ + str, + Field( + pattern=r"ivo://[\w\d][\w\d\-_\.!~\*'\(\)\+=]{2,}(/[\w\d\-_\.!~\*'\(\)\+=]+(/[\w\d\-_\.!~\*'\(\)\+=]+)*)?", + description="A reference to a registry record.", + ), +] From 8c5d459844f5065af75f63b5143c3b5fbc3951b6 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 17 Sep 2024 16:58:24 -0400 Subject: [PATCH 03/41] fix identifierURI import --- vo_models/tapregext/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index e5d29ec..aeb7f4d 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -4,7 +4,8 @@ from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource import Capability, IdentifierURI, Interface, Validation +from vo_models.voresource import Capability, Interface, Validation +from vo_models.voresource.types import IdentifierURI NSMAP = { "xs": "http://www.w3.org/2001/XMLSchema", From 4351c0953b861107f22e71a464ddfdde268f8bb2 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 19 Sep 2024 16:40:32 -0400 Subject: [PATCH 04/41] tapregext tests, model tweaks --- .../TAPRegExt-v1.0-with-erratum1.xsd | 384 +++++++++++++ .../TAPRegExt-v1.0-with-erratum1.xsd.xml | 521 ------------------ tests/tapregext/tapregext_models_test.py | 380 +++++++++++++ tests/voresource/voresource_models_test.py | 29 + vo_models/tapregext/__init__.py | 15 + vo_models/tapregext/models.py | 70 +-- vo_models/voresource/models.py | 6 +- 7 files changed, 846 insertions(+), 559 deletions(-) create mode 100644 tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd delete mode 100644 tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml create mode 100644 tests/tapregext/tapregext_models_test.py create mode 100644 tests/voresource/voresource_models_test.py diff --git a/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd b/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd new file mode 100644 index 0000000..95d2e2b --- /dev/null +++ b/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd @@ -0,0 +1,384 @@ + + + + + + + + TAPRegExt + xs + + tr + + A description of the capabilities metadata for TAP services. + + + + + An abstract capability that fixes the standardID to the IVOA ID for the TAP + standard. + See vr:Capability for documentation on inherited children. + + + + + + + + + + + + + + + + The capabilities of a TAP server. + The capabilities attempt to define most issues that the TAP standard leaves + to the implementors ("may", "should"). + + + + + + + + Identifier of IVOA-approved data model supported by the service. + + + + + + Language supported by the service. + + + + + + Output format supported by the service. + + + + + + Upload method supported by the service. + The absence of upload methods indicates that the service does not + support uploads at all. + + + + + + Limits on the time between job creation and destruction time. + + + + + + Limits on executionDuration. + + + + + + Limits on the size of data returned. + + + + + + Limits on the size of uploaded data. + + + + + + + + + + + + An IVOA defined data model, identified by an IVORN intended for machine + consumption and a short label intended for human comsumption. + + + + + + + The IVORN of the data model. + + + + + + + + + A query language supported by the service. + Each language element can describe one or more versions of a language. + Either name alone or name-version can be used as values for the server's LANG parameter. + + + + + + The name of the language without a version suffix. + + + + + + A version of the language supported by the server. + + + + + + A short, human-readable description of the query language. + + + + + + Optional features of the query language, grouped by feature type. + This includes listing user defined functions, geometry support, or + similar concepts. + + + + + + + + One version of the language supported by the service. + If the service supports more than one version of the language, include + multiple version elements. It is recommended that you use a version numbering scheme like + MAJOR.MINOR in such a way that sorting by ascending character codes will leave the most + recent version at the bottom of the list. + + + + + + + An optional IVORN of the language. + To more formally define a language supported by a service, a resource + record for the language can be created, either centrally on the Registry of Registries + or by other registry operators. When such a record exists, the language element's + ivo-id should point to it. + + + + + + + + + An enumeration of non-standard or non-mandatory features of a specific type + implemented by the language. + A feature type is a language-dependent concept like "user defined + function", "geometry support", or possibly "units supported". A featureList gives all + features of a given type applicable for the service. Multiple featureLists are possible. All + feature in a given list are of the same type. This type is declared using the mandatory type + attribute, the value of which will typically be an IVORN. To see values defined in + TAPRegExt, retrieve the ivo://ivoa.net/std/TAPRegExt resource record and look for keys + starting with "features-". + + + + + A language feature of the type given by this element's type attribute. + + + + + + The type of the features given here. + This is in general an IVORN. TAPRegExt itself gives IVORNs for defining + user defined functions and geometry support. + + + + + + + A non-standard or non-mandatory feature implemented by the language.. + + + + + Formal notation for the language feature. + The syntax for the content of this element is defined by the type + attribute of its parent language list. + + + + + Human-readable freeform documentation for the language feature. + + + + + + + + An output format supported by the service. + All TAP services must support VOTable output, preserving the MIME type of + the input. Other output formats are optional. The primary identifier for an output format is + the MIME type. If you want to register an output format, you must use a MIME type (or make + one up using the x- syntax), although the concrete MIME syntax is not enforced by the + schema. For more detailed specification, an IVORN may be used. + + + + + + The MIME type of this format. + The format of this string is specified by RFC 2045. The service has to + accept this string as a value of the FORMAT parameter. + + + + + + Other values of FORMAT ("shorthands") that make the service return + documents with the MIME type. + + + + + + + + An optional IVORN of the output format. + When the MIME type does not uniquely define the format (or a generic MIME + like application/octet-stream or text/plain is given), the IVORN can point to a key or + StandardsRegExt document defining the format more precisely. To see values defined in + TAPRegExt, retrieve the ivo://ivoa.net/std/TAPRegExt resource record and look for keys + starting with "output-". + + + + + + + + An upload method as defined by IVOA. + Upload methods are always identified by an IVORN. Descriptions can be + obtained by dereferencing this IVORN. To see values defined in TAPRegExt, retrieve the + ivo://ivoa.net/std/TAPRegExt resource record and look for keys starting with "upload-". You + can register custom upload methods, but you must use the standard IVORNs for the upload + methods defined in the TAP specification. + + + + + + + The IVORN of the upload method. + + + + + + + + + + Time-valued limits, all values given in seconds. + + + + + + The value of this limit for newly-created jobs, given in seconds. + + + + + The value this limit cannot be raised above, given in seconds. + + + + + + + + Limits on data sizes, given in rows or bytes. + + + + + + The value of this limit for newly-created jobs. + + + + + The value this limit cannot be raised above. + + + + + + + + A limit on some data size, either in rows or in bytes. + + + + + + + The unit of the limit specified. + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml b/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml deleted file mode 100644 index c1e9ccd..0000000 --- a/tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd.xml +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - - - TAPRegExt - xs - tr - - - A description of the capabilities metadata for TAP services. - - - - - - - An abstract capability that fixes the standardID to the - IVOA ID for the TAP standard. - - - See vr:Capability for documentation on inherited children. - - - - - - - - - - - - - - - - - - The capabilities of a TAP server. - - - The capabilities attempt to define most issues that the - TAP standard leaves to the implementors ("may", "should"). - - - - - - - - - - Identifier of IVOA-approved data model supported by the - service. - - - - - - - - Language supported by the service. - - - - - - - - Output format supported by the service. - - - - - - - - Upload method supported by the service. - - - The absence of upload methods indicates - that the service does not support uploads - at all. - - - - - - - - Limits on the time between job creation and - destruction time. - - - - - - - - Limits on executionDuration. - - - - - - - - Limits on the size of data returned. - - - - - - - - Limits on the size of uploaded data. - - - - - - - - - - - - - - An IVOA defined data model, identified by an IVORN - intended for machine consumption and a short label - intended for human comsumption. - - - - - - - - - The IVORN of the data model. - - - - - - - - - - - A query language supported by the service. - - - Each language element can describe one or more versions - of a language. Either name alone or name-version can be - used as values for the server's LANG parameter. - - - - - - - - The name of the language without a version suffix. - - - - - - - - A version of the language supported by the server. - - - - - - - - A short, human-readable description of the - query language. - - - - - - - - Optional features of the query language, grouped by - feature type. - - - This includes listing user defined functions, geometry support, - or similar concepts. - - - - - - - - - - One version of the language supported by the service. - - - If the service supports more than one version of the - language, include multiple version elements. - It is recommended that you use a version numbering - scheme like MAJOR.MINOR in such a way that sorting - by ascending character codes will leave the most - recent version at the bottom of the list. - - - - - - - - - An optional IVORN of the language. - - - To more formally define a language supported by a service, - a resource record for the language can be created, either - centrally on the Registry of Registries or by other registry operators. - When such a record exists, the language element's ivo-id - should point to it. - - - - - - - - - - - An enumeration of non-standard or non-mandatory features of - a specific type implemented by the language. - - - A feature type is a language-dependent concept like - "user defined function", "geometry support", or possibly - "units supported". A featureList gives all features of - a given type applicable for the service. Multiple featureLists - are possible. - - All feature in a given list are of the same type. This type - is declared using the mandatory type attribute, - the value of which will typically be an IVORN. - To see values defined in TAPRegExt, - retrieve the ivo://ivoa.net/std/TAPRegExt - resource record and look for keys starting with "features-". - - - - - - - A language feature of the type given by this - element's type attribute. - - - - - - - - The type of the features given here. - - - This is in general an IVORN. TAPRegExt itself gives - IVORNs for defining user defined functions and geometry - support. - - - - - - - - - A non-standard or non-mandatory feature implemented - by the language.. - - - - - - - Formal notation for the language feature. - - - The syntax for the content of this element is defined by the - type attribute of its parent language list. - - - - - - - Human-readable freeform documentation for the language feature. - - - - - - - - - - An output format supported by the service. - - - All TAP services must support VOTable output, preserving - the MIME type of the input. Other output formats are - optional. - - The primary identifier for an output format is the MIME - type. If you want to register an output format, you must - use a MIME type (or make one up using the x- syntax), although - the concrete MIME syntax is not enforced by the schema. - - For more detailed specification, an IVORN may be used. - - - - - - - - The MIME type of this format. - - - The format of this string is specified by RFC 2045. - The service has to accept this string as a - value of the FORMAT parameter. - - - - - - - - Other values of FORMAT ("shorthands") that make the service return - documents with the MIME type. - - - - - - - - - - An optional IVORN of the output format. - - - When the MIME type does not uniquely define the - format (or a generic MIME like application/octet-stream or - text/plain is given), the IVORN can point to a key - or StandardsRegExt document defining the format more - precisely. To see values defined in TAPRegExt, - retrieve the ivo://ivoa.net/std/TAPRegExt - resource record and look for keys starting with "output-". - - - - - - - - - - An upload method as defined by IVOA. - - - Upload methods are always identified by an IVORN. - Descriptions can be obtained by dereferencing this - IVORN. To see values defined in TAPRegExt, - retrieve the ivo://ivoa.net/std/TAPRegExt - resource record and look for keys starting with "upload-". - - - You can register custom upload methods, but you must use the - standard IVORNs for the upload methods defined in the TAP - specification. - - - - - - - - - The IVORN of the upload method. - - - - - - - - - - - - Time-valued limits, all values given in seconds. - - - - - - - - The value of this limit for newly-created jobs, given in seconds. - - - - - - - The value this limit cannot be raised above, given in seconds. - - - - - - - - - - Limits on data sizes, given in rows or bytes. - - - - - - - - The value of this limit for newly-created jobs. - - - - - - - The value this limit cannot be raised above. - - - - - - - - - - A limit on some data size, either in rows or in bytes. - - - - - - - - - The unit of the limit specified. - - - - - - - - - - - - - - diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py new file mode 100644 index 0000000..b859a8c --- /dev/null +++ b/tests/tapregext/tapregext_models_test.py @@ -0,0 +1,380 @@ +"""Tests for TAPRegExt models.""" + +from unittest import TestCase +from xml.etree.ElementTree import canonicalize + +from lxml import etree + +from vo_models.tapregext import ( + DataLimit, + DataLimits, + DataModelType, + Language, + LanguageFeature, + LanguageFeatureList, + OutputFormat, + TableAccess, + TAPCapRestriction, + TimeLimits, + UploadMethod, + Version, +) +from vo_models.voresource import Validation + +TAPREGEXT_NAMESPACE_HEADER = """xmlns:xs="http://www.w3.org/2001/XMLSchema" +xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" +xmlns:vm="http://www.ivoa.net/xml/VOMetadata/v0.1" +xmlns="http://www.ivoa.net/xml/TAPRegExt/v1.0" +""" + +with open("tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd") as schema_file: + tapregext_schema = etree.XMLSchema(etree.parse(schema_file)) + + +class TestVersion(TestCase): + """Tests the Version model.""" + + test_version_model = Version(value="1.0", ivo_id="ivo://ivoa.net/std/TAP") + test_version_xml = f'1.0' + + def test_read_from_xml(self): + """Test reading a Version element from XML.""" + version = Version.from_xml(self.test_version_xml) + self.assertEqual(version.value, "1.0") + self.assertEqual(version.ivo_id, "ivo://ivoa.net/std/TAP") + + def test_write_xml(self): + """Test we can write a Version element to XML.""" + version_xml = self.test_version_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_version_xml))), + canonicalize( + etree.tostring(etree.fromstring(version_xml)), + ), + ) + + +class TestLanguageFeature(TestCase): + """Tests the LanguageFeature model.""" + + test_language_feature_model = LanguageFeature(form="Formal notation", description="A description") + test_language_feature_xml = f"
Formal notation
A description
" + + def test_read_from_xml(self): + """Test reading a LanguageFeature element from XML.""" + language_feature = LanguageFeature.from_xml(self.test_language_feature_xml) + self.assertEqual(language_feature.form, "Formal notation") + self.assertEqual(language_feature.description, "A description") + + def test_write_xml(self): + """Test we can write a LanguageFeature element to XML.""" + language_feature_xml = self.test_language_feature_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_language_feature_xml))), + canonicalize( + etree.tostring(etree.fromstring(language_feature_xml)), + ), + ) + + +class TestOutputFormat(TestCase): + """Tests the OutputFormat model.""" + + test_output_format_model = OutputFormat( + mime="application/x-votable+xml", + alias=["VOTABLE"], + ) + test_output_format_xml = ( + f"" + "application/x-votable+xml" + "VOTABLE" + "" + ) + + def test_read_from_xml(self): + """Test reading an OutputFormat element from XML.""" + output_format = OutputFormat.from_xml(self.test_output_format_xml) + self.assertEqual(output_format.mime, "application/x-votable+xml") + self.assertEqual(output_format.alias[0], "VOTABLE") + + def test_write_xml(self): + """Test we can write an OutputFormat element to XML.""" + output_format_xml = self.test_output_format_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_output_format_xml))), + canonicalize( + etree.tostring(etree.fromstring(output_format_xml)), + ), + ) + + +class TestUploadMethod(TestCase): + """Tests the UploadMethod model.""" + + test_upload_method_model = UploadMethod( + ivo_id="ivo://ivoa.net/std/TAP", + ) + test_upload_method_xml = f'' + + def test_read_from_xml(self): + """Test reading an UploadMethod element from XML.""" + upload_method = UploadMethod.from_xml(self.test_upload_method_xml) + self.assertEqual(upload_method.ivo_id, "ivo://ivoa.net/std/TAP") + + def test_write_xml(self): + """Test we can write an UploadMethod element to XML.""" + upload_method_xml = self.test_upload_method_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_upload_method_xml))), + canonicalize( + etree.tostring(etree.fromstring(upload_method_xml)), + ), + ) + + +class TestTimeLimitsElement(TestCase): + """Tests the TimeLimits model.""" + + test_time_limits_model = TimeLimits(default=10, hard=100) + test_time_limits_xml = ( + f"10100" + ) + + def test_read_from_xml(self): + """Test reading a TimeLimits element from XML.""" + time_limits = TimeLimits.from_xml(self.test_time_limits_xml) + self.assertEqual(time_limits.default, 10) + self.assertEqual(time_limits.hard, 100) + + def test_write_xml(self): + """Test we can write a TimeLimits element to XML.""" + time_limits_xml = self.test_time_limits_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_time_limits_xml))), + canonicalize( + etree.tostring(etree.fromstring(time_limits_xml)), + ), + ) + + +class TestDataLimitsElement(TestCase): + """Tests the DataLimits model.""" + + test_data_limits_model = DataLimits( + default={"value": 10, "unit": "row"}, + hard={"value": 100, "unit": "row"}, + ) + test_data_limits_xml = ( + f"" + '10' + '100' + "" + ) + + def test_read_from_xml(self): + """Test reading a DataLimits element from XML.""" + data_limits = DataLimits.from_xml(self.test_data_limits_xml) + self.assertEqual(data_limits.default.value, 10) + self.assertEqual(data_limits.hard.value, 100) + + def test_write_xml(self): + """Test we can write a DataLimits element to XML.""" + data_limits_xml = self.test_data_limits_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_data_limits_xml))), + canonicalize( + etree.tostring(etree.fromstring(data_limits_xml)), + ), + ) + + +class TestDataLimitElement(TestCase): + """Tests the DataLimit model.""" + + test_data_limit_model = DataLimit(value=10, unit="byte") + test_data_limit_xml = f'10' + + def test_read_from_xml(self): + """Test reading a DataLimit element from XML.""" + data_limit = DataLimit.from_xml(self.test_data_limit_xml) + self.assertEqual(data_limit.value, 10) + self.assertEqual(data_limit.unit, "byte") + + def test_write_xml(self): + """Test we can write a DataLimit element to XML.""" + data_limit_xml = self.test_data_limit_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_data_limit_xml))), + canonicalize( + etree.tostring(etree.fromstring(data_limit_xml)), + ), + ) + + +class TestLanguageFeatureList(TestCase): + """Tests the LanguageFeatureList model.""" + + test_language_feature_list_model = LanguageFeatureList( + feature=[ + LanguageFeature(form="Formal notation", description="A description"), + LanguageFeature(form="Informal notation", description="Another description"), + ], + type="adql-some-feature", + ) + test_language_feature_list_xml = ( + f'' + "
Formal notation
A description
" + "
Informal notation
Another description
" + "
" + ) + + def test_read_from_xml(self): + """Test reading a LanguageFeatureList element from XML.""" + language_feature_list = LanguageFeatureList.from_xml(self.test_language_feature_list_xml) + self.assertEqual(language_feature_list.feature[0].form, "Formal notation") + self.assertEqual(language_feature_list.feature[0].description, "A description") + self.assertEqual(language_feature_list.feature[1].form, "Informal notation") + self.assertEqual(language_feature_list.feature[1].description, "Another description") + + def test_write_xml(self): + """Test we can write a LanguageFeatureList element to XML.""" + language_feature_list_xml = self.test_language_feature_list_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_language_feature_list_xml))), + canonicalize( + etree.tostring(etree.fromstring(language_feature_list_xml)), + ), + ) + + +class TestLanguage(TestCase): + """Tests the Language model.""" + + test_language_model = Language( + name="ADQL", + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL")], + description="Astronomical Data Query Language", + language_features=[ + LanguageFeatureList( + feature=[ + LanguageFeature(form="Formal notation", description="A description"), + LanguageFeature(form="Informal notation", description="Another description"), + ], + type="adql-some-feature", + ) + ], + ) + test_language_xml = ( + f"" + "ADQL" + "2.0" + "Astronomical Data Query Language" + '' + "
Formal notation
A description
" + "
Informal notation
Another description
" + "
" + "
" + ) + + def test_read_from_xml(self): + """Test reading a Language element from XML.""" + language = Language.from_xml(self.test_language_xml) + self.assertEqual(language.name, "ADQL") + self.assertEqual(language.version[0].value, "2.0") + self.assertEqual(language.description, "Astronomical Data Query Language") + self.assertEqual(language.language_features[0].feature[0].form, "Formal notation") + self.assertEqual(language.language_features[0].feature[0].description, "A description") + self.assertEqual(language.language_features[0].feature[1].form, "Informal notation") + self.assertEqual(language.language_features[0].feature[1].description, "Another description") + + def test_write_xml(self): + """Test we can write a Language element to XML.""" + language_xml = self.test_language_model.to_xml() + self.assertEqual( + canonicalize(etree.tostring(etree.fromstring(self.test_language_xml))), + canonicalize( + etree.tostring(etree.fromstring(language_xml)), + ), + ) + + +class TestTableAccess(TestCase): + """Tests the TableAccess model.""" + + test_table_access_model = TableAccess( + data_model=[DataModelType(value="VOTable", ivo_id="ivo://ivoa.net/std/VOTable")], + language=[ + Language( + name="ADQL", + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL")], + description="Astronomical Data Query Language", + language_features=[ + LanguageFeatureList( + feature=[ + LanguageFeature(form="Formal notation", description="A description"), + LanguageFeature(form="Informal notation", description="Another description"), + ], + type="adql-some-feature", + ) + ], + ) + ], + output_format=[ + OutputFormat( + mime="application/x-votable+xml", + alias=["VOTABLE"], + ) + ], + upload_method=[ + UploadMethod( + value="HTTP", + ivo_id="ivo://ivoa.net/std/TAP", + ) + ], + retention_period=TimeLimits(default=10, hard=100), + output_limit=DataLimits( + default={"value": 10, "unit": "row"}, + hard={"value": 100, "unit": "row"}, + ), + ) + test_table_access_xml = ( + f"" + "VOTable" + "" + "ADQL" + "2.0" + "Astronomical Data Query Language" + "" + "
Formal notation
A description
" + "
Informal notation
Another description
" + "
" + "
" + "application/x-votable+xml;content=datalink" + "" + "10100" + "" + '10' + '100' + "" + "
" + ) + + def test_read_from_xml(self): + """Test reading a TableAccess element from XML.""" + table_access = TableAccess.from_xml(self.test_table_access_xml) + self.assertEqual(table_access.data_model[0].value, "VOTable") + self.assertEqual(table_access.data_model[0].ivo_id, "ivo://ivoa.net/std/VOTable") + self.assertEqual(table_access.language[0].name, "ADQL") + self.assertEqual(table_access.language[0].version[0].value, "2.0") + self.assertEqual(table_access.language[0].description, "Astronomical Data Query Language") + self.assertEqual(table_access.language[0].language_features[0].feature[0].form, "Formal notation") + self.assertEqual(table_access.language[0].language_features[0].feature[0].description, "A description") + self.assertEqual(table_access.language[0].language_features[0].feature[1].form, "Informal notation") + self.assertEqual(table_access.language[0].language_features[0].feature[1].description, "Another description") + self.assertEqual(table_access.output_format[0].mime, "application/x-votable+xml") + self.assertEqual(table_access.output_format[0].alias[0], "VOTABLE") + self.assertEqual(table_access.upload_method[0].ivo_id, "ivo://ivoa.net/std/TAP") + self.assertEqual(table_access.retention_period.default, 10) + self.assertEqual(table_access.retention_period.hard, 100) + self.assertEqual(table_access.output_limit.default.value, 10) + self.assertEqual(table_access.output_limit.hard.value, 100) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py new file mode 100644 index 0000000..b390046 --- /dev/null +++ b/tests/voresource/voresource_models_test.py @@ -0,0 +1,29 @@ +"""Tests for VOResource models.""" + +from unittest import TestCase +from xml.etree.ElementTree import canonicalize + +from lxml import etree + +from vo_models.voresource import ( + AccessURL, + Capability, + Contact, + Content, + Creator, + Curation, + Date, + Interface, + MirrorURL, + Organisation, + Relationship, + Resource, + ResourceName, + Rights, + SecurityMethod, + Service, + Source, + Validation, + WebBrowser, + WebService, +) diff --git a/vo_models/tapregext/__init__.py b/vo_models/tapregext/__init__.py index 94de027..8346993 100644 --- a/vo_models/tapregext/__init__.py +++ b/vo_models/tapregext/__init__.py @@ -3,3 +3,18 @@ IVOA UWS Spec: https://ivoa.net/documents/TAPRegExt/20120827/REC-TAPRegExt-1.0.html """ + +from vo_models.tapregext.models import ( + DataLimit, + DataLimits, + DataModelType, + Language, + LanguageFeature, + LanguageFeatureList, + OutputFormat, + TableAccess, + TAPCapRestriction, + TimeLimits, + UploadMethod, + Version, +) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index aeb7f4d..1e42feb 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -11,21 +11,21 @@ "xs": "http://www.w3.org/2001/XMLSchema", "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", - "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "": "http://www.ivoa.net/xml/TAPRegExt/v1.0", } class TAPCapRestriction(Capability, nsmap=NSMAP): """An abstract capability that fixes the standardID to the IVOA ID for the TAP standard.""" - validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=[]) + validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) description: Optional[str] = element(tag="description", default=None) - interface: Optional[list[Interface]] = element(tag="interface", default_factory=[]) + interface: Optional[list[Interface]] = element(tag="interface", default_factory=list) - standard_id: IdentifierURI = attr(tag="standardID", default="ivo://ivoa.net/std/TAP") + standard_id: IdentifierURI = attr(name="standardID", default="ivo://ivoa.net/std/TAP") -class DataModelType(BaseXmlModel, nsmap=NSMAP): +class DataModelType(BaseXmlModel, tag="dataModel", nsmap=NSMAP): """IVOA defined data model, identified by an IVORN. Parameters: @@ -36,10 +36,10 @@ class DataModelType(BaseXmlModel, nsmap=NSMAP): """ value: str - ivo_id: str = attr(tag="ivo-id") + ivo_id: str = attr(name="ivo-id") -class Version(BaseXmlModel, nsmap=NSMAP): +class Version(BaseXmlModel, tag="version", nsmap=NSMAP): """One version of the language supported by the service. Parameters: @@ -50,10 +50,10 @@ class Version(BaseXmlModel, nsmap=NSMAP): """ value: str - ivo_id: Optional[str] = attr(tag="ivo-id", default=None) + ivo_id: Optional[str] = attr(name="ivo-id", default=None) -class LanguageFeature(BaseXmlModel, nsmap=NSMAP): +class LanguageFeature(BaseXmlModel, tag="languageFeature", nsmap=NSMAP): """A non-standard or non-mandatory feature implemented by the language. Parameters: @@ -67,7 +67,7 @@ class LanguageFeature(BaseXmlModel, nsmap=NSMAP): description: Optional[str] = element(tag="description", default=None) -class OutputFormat(BaseXmlModel, nsmap=NSMAP): +class OutputFormat(BaseXmlModel, tag="outputFormat", nsmap=NSMAP): """An output format supported by the service. Parameters: @@ -78,10 +78,10 @@ class OutputFormat(BaseXmlModel, nsmap=NSMAP): """ mime: str = element(tag="mime") - alias: Optional[list[str]] = element(tag="alias", default_factory=[]) + alias: Optional[list[str]] = element(tag="alias", default_factory=list) -class UploadMethod(BaseXmlModel, nsmap=NSMAP): +class UploadMethod(BaseXmlModel, tag="uploadMethod", nsmap=NSMAP): """An upload method as defined by IVOA. Parameters: @@ -89,10 +89,10 @@ class UploadMethod(BaseXmlModel, nsmap=NSMAP): (attribute) - The IVORN of the upload method. """ - ivo_id: str = attr(tag="ivo-id") + ivo_id: str = attr(name="ivo-id") -class TimeLimits(BaseXmlModel, nsmap=NSMAP): +class TimeLimits(BaseXmlModel, tag="timeLimits", nsmap=NSMAP): """Time-valued limits, all values given in seconds. Parameters: @@ -106,21 +106,7 @@ class TimeLimits(BaseXmlModel, nsmap=NSMAP): hard: Optional[int] = element(tag="hard", default=None) -class DataLimits(BaseXmlModel, nsmap=NSMAP): - """Limits on data sizes, given in rows or bytes. - - Parameters: - default: - (element) - The value of this limit for newly-created jobs. - hard: - (element) - The value this limit cannot be raised above. - """ - - default: Optional[int] = element(tag="default", default=None) - hard: Optional[int] = element(tag="hard", default=None) - - -class DataLimit(BaseXmlModel, nsmap=NSMAP): +class DataLimit(BaseXmlModel, tag="dataLimit", nsmap=NSMAP): """A limit on some data size, either in rows or in bytes. Parameters: @@ -131,10 +117,24 @@ class DataLimit(BaseXmlModel, nsmap=NSMAP): """ value: int - unit: Literal["byte", "row"] = attr(tag="unit") + unit: Literal["byte", "row"] = attr(name="unit") + + +class DataLimits(BaseXmlModel, tag="dataLimits", nsmap=NSMAP): + """Limits on data sizes, given in rows or bytes. + + Parameters: + default: + (element) - The value of this limit for newly-created jobs. + hard: + (element) - The value this limit cannot be raised above. + """ + + default: Optional[DataLimit] = element(tag="default", default=None) + hard: Optional[DataLimit] = element(tag="hard", default=None) -class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): +class LanguageFeatureList(BaseXmlModel, tag="languageFeatures", nsmap=NSMAP): """An enumeration of non-standard or non-mandatory features of a specific type implemented by the language. Parameters: @@ -144,11 +144,11 @@ class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): (attribute) - The type of the language feature. """ - feature: Optional[list[LanguageFeature]] = element(tag="feature", default_factory=[]) - type: str = attr(tag="type") + feature: Optional[list[LanguageFeature]] = element(tag="feature", default_factory=list) + type: str = attr(name="type") -class Language(BaseXmlModel, nsmap=NSMAP): +class Language(BaseXmlModel, tag="language", nsmap=NSMAP): """A query language supported by the service. Parameters: @@ -168,7 +168,7 @@ class Language(BaseXmlModel, nsmap=NSMAP): language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) -class TableAccess(TAPCapRestriction): +class TableAccess(TAPCapRestriction, tag="capability", ns="vr", nsmap=NSMAP): """The capabilities of a TAP server. Parameters: diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 55c62dc..1b591ee 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -2,10 +2,10 @@ import datetime from typing import Literal, Optional -from pydantic import field_validator, networks, types -from pydantic_xml import BaseXmlModel, RootXmlModel, attr, element +from pydantic import field_validator, networks +from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource.types import IdentifierURI, UTCDateTime, UTCTimestamp, ValidationLevel +from vo_models.voresource.types import IdentifierURI, UTCTimestamp, ValidationLevel # pylint: disable=no-self-argument # pylint: disable=too-few-public-methods From f585153bfc9e0cd2130603c48fd3cbec9783180d Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 24 Sep 2024 11:44:47 -0400 Subject: [PATCH 05/41] update type hints, types --- vo_models/voresource/__init__.py | 11 ++- vo_models/voresource/models.py | 146 +++++++++++++++---------------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/vo_models/voresource/__init__.py b/vo_models/voresource/__init__.py index 2a86c2f..5a8f92b 100644 --- a/vo_models/voresource/__init__.py +++ b/vo_models/voresource/__init__.py @@ -18,10 +18,15 @@ SecurityMethod, Service, Source, - UTCDateTime, - UTCTimestamp, Validation, - ValidationLevel, WebBrowser, WebService, ) +from vo_models.voresource.types import ( + AuthorityID, + IdentifierURI, + ResourceKey, + UTCDateTime, + UTCTimestamp, + ValidationLevel, +) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 1b591ee..533f70e 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -19,11 +19,11 @@ } -class Validation(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Validation(BaseXmlModel, tag="validation", ns="vr", nsmap=NSMAP): """A validation stamp combining a validation level and the ID of the validator. Parameters: - validated_by: (networks.AnyUrl): + validated_by: (attr) - The IVOA ID of the registry or organisation that assigned the validation level. """ @@ -40,12 +40,12 @@ class ResourceName(BaseXmlModel, ns="vr", nsmap=NSMAP): That is, the entity referred to may have an associated identifier. Parameters: - ivo_id: (Optional[IdentifierURI]): + ivo_id: (attr) - The IVOA identifier for the resource referred to. """ value: str - ivo_id: Optional[IdentifierURI] = attr(name="ivo_id", default=None) + ivo_id: Optional[IdentifierURI] = attr(name="ivo-id", default=None) class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): @@ -59,8 +59,8 @@ class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): corresponding list of terms in the DataCite Metadata schema. Parameters: - value: (UTCTimestamp): The date and time of the event. - role: (Optional[str]): + value: The date and time of the event. + role: (attr) - A string indicating what the date refers to. """ @@ -75,8 +75,8 @@ class Source(BaseXmlModel, ns="vr", nsmap=NSMAP): """ Parameters: - value: (networks.AnyUrl): A bibliographic reference from which the present resource is derived or extracted. - format: (Optional[str]): + value: A bibliographic reference from which the present resource is derived or extracted. + format: (attr) - The reference format. Recognized values include "bibcode", referring to a standard astronomical bibcode (http://cdsweb.u-strasbg.fr/simbad/refcode.html). @@ -94,8 +94,8 @@ class Rights(BaseXmlModel, ns="vr", nsmap=NSMAP): on embargo periods is allowed. Parameters: - value: (str): The statement of usage conditions. - rights_uri: (Optional[networks.AnyUrl]): + value: The statement of usage conditions. + rights_uri: (attr) - A URI identifier for a license """ @@ -107,8 +107,8 @@ class AccessURL(BaseXmlModel, ns="vr", nsmap=NSMAP): """The URL (or base URL) that a client uses to access the service. Parameters: - value: (networks.AnyUrl): The URL (or base URL) that a client uses to access the service. - use: (Literal["full", "base", "dir"]): + value: The URL (or base URL) that a client uses to access the service. + use: (attr) - A flag indicating whether this should be interpreted as a base URL, a full URL, or a URL to a directory that will produce a listing of files. Allowed values are: "full" - Assume a full URL--that is, one that can be invoked directly without alteration. @@ -125,8 +125,8 @@ class MirrorURL(BaseXmlModel, ns="vr", nsmap=NSMAP): """A URL of a mirror (i.e., a functionally identical additional service interface) to Parameters: - value: (networks.AnyUrl): A URL of a mirror - title: (Optional[str]): + value: A URL of a mirror + title: (attr) - A terse, human-readable phrase indicating the function or location of this mirror, e.g., "Primary Backup" or "European Mirror". """ @@ -139,22 +139,22 @@ class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): """Information allowing establishing contact, e.g., for purposes of support. Parameters: - ivo_id: (Optional[Type]): + ivo_id: (attr) - An IVOA identifier for the contact (typically when it is an organization). - name: (ResourceName): + name: (element) - The name or title of the contact person. This can be a person's name, e.g. “John P. Jones” or a group, “Archive Support Team”. - address: (Optional[str]): + address: (element) - The contact mailing address All components of the mailing address are given in one string, e.g. “3700 San Martin Drive, Baltimore, MD 21218 USA”. - email: (Optional[str]): + email: (element) - The contact email address - telephone: (Optional[str]): + telephone: (element) - The contact telephone number Complete international dialing codes should be given, e.g. “+1-410-338-1234”. - alt_identifier: (Optional[list[networks.AnyUrl]]): + alt_identifier: (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. Always use a URI form including a scheme here. """ @@ -172,17 +172,17 @@ class Creator(BaseXmlModel, ns="vr", nsmap=NSMAP): """The entity (e.g. person or organisation) primarily responsible for creating something Parameters: - ivo_id: (Optional[IdentifierURI]): + ivo_id: (attr) - An IVOA identifier for the creator (typically when it is an organization). - name: (ResourceName): + name: (element) - The name or title of the creating person or organisation Users of the creation should use this name in subsequent credits and acknowledgements. This should be exactly one name, preferably last name first (as in "van der Waals, Johannes Diderik"). - logo: (Optional[networks.AnyUrl]): + logo: (element) - URL pointing to a graphical logo, which may be used to help identify the information source. - alt_identifier: (Optional[list[networks.AnyUrl]]): + alt_identifier: (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. Always use a URI form including a scheme here. """ @@ -198,11 +198,11 @@ class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): """A description of the relationship between one resource and one or more other resources. Parameters: - relationship_type: (str): + relationship_type: (element) - The named type of relationship The value of relationshipType should be taken from the vocabulary at http://www.ivoa.net/rdf/voresource/relationship_type. - related_resource: (list[ResourceName]): + related_resource: (element) - the name of resource that this resource is related to. """ @@ -217,7 +217,7 @@ class SecurityMethod(BaseXmlModel, ns="vr", nsmap=NSMAP): This type only allows one to refer to the mechanism via a URI. Derived types would allow for more metadata. Parameters: - standard_id: (Optional[networks.AnyUrl]): + standard_id: (attr) - A URI identifier for a standard security mechanism. This provides a unique way to refer to a security specification standard. The use of an IVOA identifier here implies that a VOResource description of the standard is registered and accessible. @@ -230,21 +230,21 @@ class Curation(BaseXmlModel, ns="vr", nsmap=NSMAP): """Information regarding the general curation of a resource Parameters: - publisher: (ResourceName): + publisher: (element) - Entity (e.g. person or organisation) responsible for making the resource available - creator: (Optional[list[Creator]]): + creator: (element) - The entity/ies (e.g. person(s) or organisation) primarily responsible for creating the content or constitution of the resource. This is the equivalent of the author of a publication. - contributor: (Optional[list[ResourceName]]): + contributor: (element) - Entity responsible for contributions to the content of the resource - date: (Optional[list[Date]]): + date: (element) - Date associated with an event in the life cycle of the resource. This will typically be associated with the creation or availability (i.e., most recent release or version) of the resource. Use the role attribute to clarify. - version: (Optional[str]): + version: (element) - Label associated with creation or availablilty of a version of a resource. - contact: (list[Contact]): + contact: (element) - Information that can be used for contacting someone with regard to this resource. """ @@ -260,26 +260,26 @@ class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): """Information regarding the general content of a resource Parameters: - subject: (list[str]): + subject: (element) - A topic, object type, or other descriptive keywords about the resource. Terms for Subject should be drawn from the Unified Astronomy Thesaurus (http://astrothesaurus.org). - description: (str): + description: (element) - An account of the nature of the resource The description may include but is not limited to an abstract, table of contents, reference to a graphical representation of content or a free-text account of the content. - source: (Optional[Source]): + source: (element) - A bibliographic reference from which the present resource is derived or extracted. This is intended to point to an article in the published literature. An ADS Bibcode is recommended as a value when available. - reference_url: (networks.AnyUrl): + reference_url: (element) - URL pointing to a human-readable document describing this resource. - type: (Optional[list[str]]): + type: (element) - Nature or genre of the content of the resource. Values for type should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_type - content_level: (Optional[list[str]]): + content_level: (element) - Description of the content level or intended audience. Values for contentLevel should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_level. - relationship: (Optional[list[Relationship]]): + relationship: (element) - a description of a relationship to another resource. """ @@ -300,23 +300,23 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): Additional interface subtypes (beyond WebService and WebBrowser) are defined in the VODataService schema. Parameters: - version: (Optional[str]): + version: (attr) - The version of a standard interface specification that this interface complies with. Most VO standards indicate the version in the standardID attribute of the capability. For these standards, the version attribute should not be used. - role: (Optional[str]): + role: (attr) - A tag name that identifies the role the interface plays in the particular capability. If the value is equal to 'std' or begins with 'std:', then the interface refers to a standard interface defined by the standard referred to by the capability's standardID attribute. - access_url: (list[AccessURL]): + access_url: (element) - The URL (or base URL) that a client uses to access the service. How this URL is to be interpreted and used depends on the specific Interface subclass - mirror_url: (Optional[list[MirrorURL]]): + mirror_url: (element) - A (base) URL of a mirror of this interface. As with accessURL, how this URL is to be interpreted and used depends on the specific Interface subclass - security_method: (Optional[list[SecurityMethod]]): + security_method: (element) - The mechanism the client must employ to authenticate to the service. - test_querystring: (Optional[str]): + test_querystring: (element) - Test data for exercising the service. """ @@ -339,7 +339,7 @@ class WebService(Interface, ns="vr", nsmap=NSMAP): The accessURL element gives the Web Service's endpoint URL. Parameters: - wsdl_url: (Optional[list[networks.AnyUrl]]): + wsdl_url: (element) - The location of the WSDL that describes this Web Service. If not provided, the location is assumed to be the accessURL with "?wsdl" appended. Multiple occurrences should represent mirror copies of the same WSDL file. @@ -353,40 +353,40 @@ class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): identifiable by an IVOA Identifier. Parameters: - created: (UTCTimestamp): + created: (attr) - The UTC date and time this resource metadata description was created. This timestamp must not be in the future. This time is not required to be accurate; it should be at least accurate to the day. Any non-significant time fields should be set to zero. - updated: (UTCTimestamp): + updated: (attr) - The UTC date this resource metadata description was last updated. This timestamp must not be in the future. This time is not required to be accurate; it should be at least accurate to the day. Any non-significant time fields should be set to zero. - status: (Literal["active", "inactive", "deleted"]): + status: (attr) - A tag indicating whether this resource is believed to be still actively maintained. "active" - Resource is believed to be currently maintained, and its description is up to date (default) "inactive" - Resource is apparently not being maintained at the present "deleted" - Resource publisher has explicitly deleted the resource. - version: (Optional[str]): + version: (attr) - The VOResource XML schema version against which this instance was written. Implementors should set this to the value of the version attribute of their schema's root (xs:schema) element. Clients may assume version 1.0 if this attribute is missing. - validation_level: (Optional[list[Validation]]): + validation_level: (element) - A numeric grade describing the quality of the resource description, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research study. - title: (str): + title: (element) - The full name given to the resource - short_name: (Optional[str]): + short_name: (element) - A short name or abbreviation given to the resource. One word or a few letters is recommended. No more than sixteen characters are allowed. - identifier: (networks.AnyUrl): + identifier: (element) - Unambiguous reference to the resource conforming to the IVOA standard for identifiers - alt_identifier: (Optional[list[networks.AnyUrl]]): + alt_identifier: (element) - A reference to this resource in a non-IVOA identifier scheme, e.g., DOI or bibcode. Always use the an URI scheme here, e.g., bibcode:2008ivoa.spec.0222P. - curation: (Curation): + curation: (element) - Information regarding the general curation of the resource - content: (Content): + content: (element) - Information regarding the general content of the resource """ @@ -421,19 +421,19 @@ class Organisation(Resource, ns="vr", nsmap=NSMAP): """A named group of one or more persons brought together to pursue participation in VO applications. Parameters: - created: (UTCTimestamp): + created: (attr) - The UTC date and time this resource metadata description was created. - updated: (UTCTimestamp): + updated: (attr) - The UTC date this resource metadata description was last updated. - status: (str): + status: (attr) - a tag indicating whether this resource is believed to be still actively maintained. - version: (Optional[str]): + version: (attr) - The VOResource XML schema version against which this instance was written. Implementors should set this to the value of the version attribute of their schema's root (xs:schema) element. Clients may assume version 1.0 if this attribute is missing. - facility: (Optional[list[ResourceName]]): + facility: (element) - The observatory or facility used to collect the data contained or managed by this resource. - instrument: Optional[list[ResourceName]]): + instrument: (element) - The instrument used to collect the data contained or managed by a resource. """ @@ -452,21 +452,21 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): (in terms of an interface) Parameters: - standard_id: (Optional[networks.AnyUrl]): + standard_id: (attr) - A URI identifier for a standard service. This provides a unique way to refer to a service specification standard, such as a Simple Image Access service. The use of an IVOA identifier here implies that a VOResource description of the standard is registered and accessible. - validation_level: (Optional[list[Validation]]): + validation_level: (element) - A numeric grade describing the quality of the capability description and interface, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research study. See ValidationLevel for an explanation of the allowed levels. - description: (Optional[str]): + description: (element) - A human-readable description of what this capability provides as part of the over-all service. Use of this optional element is especially encouraged when this capability is non-standard and is one of several capabilities listed. - interface: (Optional[list[Interface]]): + interface: (element) - A description of how to call the service to access this capability. Since the Interface type is abstract, one must describe the interface using a subclass of Interface, denoting it via xsi:type. @@ -483,22 +483,22 @@ class Service(Resource, ns="vr", nsmap=NSMAP): """A resource that can be invoked by a client to perform some action on its behalf. Parameters: - created: (UTCTimestamp): + created: (attr) - The UTC date and time this resource metadata description was created. - updated: (UTCTimestamp): + updated: (attr) - The UTC date this resource metadata description was last updated. status: (str): (attr) - A tag indicating whether this resource is believed to be still actively maintained. - version: (Optional[str]): + version: (attr) - The VOResource XML schema version against which this instance was written. Implementors should set this to the value of the version attribute of their schema's root (xs:schema) element. Clients may assume version 1.0 if this attribute is missing. - rights: (Optional[list[Rights]]): + rights: (element) - Information about rights held in and over the resource. Mainly for compatibility with DataCite, this elementis repeatable. Resource record authors are advised that within the Virtual Observatory clients will typically only display and/or use the rights element occurring first and ignore later elements. - capability: (Optional[list[Capability]]): + capability: (element) - A description of a general capability of the service and how to use it. This describes a general function of the service, usually in terms of a standard service protocol (e.g. SIA), but not necessarily so. From 42ae9ed5fa375bd4ed99d21404451a44085f459f Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 24 Sep 2024 15:08:47 -0400 Subject: [PATCH 06/41] more tests - not at all run --- tests/voresource/voresource_models_test.py | 375 +++++++++++++++++++++ 1 file changed, 375 insertions(+) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index b390046..35d42a0 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -27,3 +27,378 @@ WebBrowser, WebService, ) + +VORESOURCE_NAMESPACE_HEADER = """ + xmlns:xml="http://www.w3.org/XML/1998/namespace", + xmlns="http://www.w3.org/2001/XMLSchema", + xmlns:xs="http://www.w3.org/2001/XMLSchema", + xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0", + xmlns:vm="http://www.ivoa.net/xml/VOMetadata/v0.1", +""" + + +class TestValidation(TestCase): + """Test VOResource Validation model.""" + + test_validation_model = Validation(value=0, validated_by="https://example.edu") + test_validation_xml = '0' + + def test_read_from_xml(self): + """Test reading from XML.""" + validation = Validation.from_xml(self.test_validation_xml) + self.assertEqual(validation.value, 0) + self.assertEqual(validation.validated_by, "https://example.edu") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_validation_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_validation_xml), strip_text=True, strip_comments=True), + ) + + +class TestResourceName(TestCase): + """Test VOResource ResourceName model.""" + + test_resource_name_model = ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource") + test_resource_name_xml = 'Example Resource' + + def test_read_from_xml(self): + """Test reading from XML.""" + resource_name = ResourceName.from_xml(self.test_resource_name_xml) + self.assertEqual(resource_name.value, "Example Resource") + self.assertEqual(resource_name.ivo_id, "ivo://example.edu/resource") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_resource_name_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_resource_name_xml), strip_text=True, strip_comments=True), + ) + + +class TestDate(TestCase): + """Test VOResource Date model""" + + test_date_model = Date(value="2021-01-01T00:00:00Z", role="update") + test_date_xml = ( + '2021-01-01T00:00:00Z' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + date = Date.from_xml(self.test_date_xml) + self.assertEqual(date.value, "2021-01-01T00:00:00Z") + self.assertEqual(date.role, "update") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_date_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_date_xml), strip_text=True, strip_comments=True), + ) + + +class TestSource(TestCase): + """Test VOResource Source model""" + + test_source_model = Source(value="https://example.edu", format="bibcode") + test_source_xml = ( + 'https://example.edu' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + source = Source.from_xml(self.test_source_xml) + self.assertEqual(source.value, "https://example.edu") + self.assertEqual(source.format, "bibcode") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_source_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_source_xml), strip_text=True, strip_comments=True), + ) + + +class TestRights(TestCase): + """Test VOResource Rights model""" + + test_rights_model = Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/") + test_rights_xml = 'CC BY 4.0' + + def test_read_from_xml(self): + """Test reading from XML.""" + rights = Rights.from_xml(self.test_rights_xml) + self.assertEqual(rights.value, "CC BY 4.0") + self.assertEqual(rights.rights_uri, "https://creativecommons.org/licenses/by/4.0/") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_rights_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_rights_xml), strip_text=True, strip_comments=True), + ) + + +class TestAccessURL(TestCase): + """Test VOResource AccessURL model""" + + test_access_url_model = AccessURL(value="https://example.edu", use="full") + test_access_url_xml = ( + 'https://example.edu' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + access_url = AccessURL.from_xml(self.test_access_url_xml) + self.assertEqual(access_url.value, "https://example.edu") + self.assertEqual(access_url.use, "full") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_access_url_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_access_url_xml), strip_text=True, strip_comments=True), + ) + + +class TestMirrorURL(TestCase): + """Test VOResource MirrorURL model""" + + test_mirror_url_model = MirrorURL(value="https://example.edu", title="Mirror") + test_mirror_url_xml = 'https://example.edu' + + def test_read_from_xml(self): + """Test reading from XML.""" + mirror_url = MirrorURL.from_xml(self.test_mirror_url_xml) + self.assertEqual(mirror_url.value, "https://example.edu") + self.assertEqual(mirror_url.title, "Mirror") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_mirror_url_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_mirror_url_xml), strip_text=True, strip_comments=True), + ) + + +class TestContact(TestCase): + """Test VOResource Contact model""" + + test_contact_model = Contact( + name="John Doe", + address="1234 Example St.", + email="jdoe@mail.com", + telephone="555-555-5555", + alt_identifier=["http://orcid.org/0000-0001-9718-6515"], + ) + test_contact_xml = ( + '' + "John Doe" + "
1234 Example St.
" + "jdoe@mail.com" + "555-555-5555" + "http://orcid.org/0000-0001-9718-6515" + "
" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + contact = Contact.from_xml(self.test_contact_xml) + self.assertEqual(contact.name, "John Doe") + self.assertEqual(contact.address, "1234 Example St.") + self.assertEqual(contact.email, "jdoe@mail.com") + self.assertEqual(contact.telephone, "555-555-5555") + self.assertEqual(contact.alt_identifier, ["http://orcid.org/0000-0001-9718-6515"]) + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_contact_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_contact_xml), strip_text=True, strip_comments=True), + ) + + +class TestCreator(TestCase): + """Test VOResource Creator model""" + + test_creator_model = Creator(name="Doe, J.", logo="https://example.edu/logo.png") + test_creator_xml = ( + '' + "Doe, J." + "https://example.edu/logo.png" + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + creator = Creator.from_xml(self.test_creator_xml) + self.assertEqual(creator.name, "Doe, J.") + self.assertEqual(creator.logo, "https://example.edu/logo.png") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_creator_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_creator_xml), strip_text=True, strip_comments=True), + ) + + +class TestRelationship(TestCase): + """Test VOResource Relationship model""" + + test_relationship_model = Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + test_relationship_xml = ( + '' + "isPartOf" + 'Example Resource' + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + relationship = Relationship.from_xml(self.test_relationship_xml) + self.assertEqual(relationship.relationship_type, "isPartOf") + self.assertEqual(relationship.related_resource[0].value, "Example Resource") + self.assertEqual(relationship.related_resource[0].ivo_id, "ivo://example.edu/resource") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_relationship_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_relationship_xml), strip_text=True, strip_comments=True), + ) + + +class TestSecurityMethod(TestCase): + """Test VOResource SecurityMethod model""" + + test_security_method_model = SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic") + test_security_method_xml = '' + + def test_read_from_xml(self): + """Test reading from XML.""" + security_method = SecurityMethod.from_xml(self.test_security_method_xml) + self.assertEqual(security_method.standard_id, "ivo://ivoa.net/std/Security#basic") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_security_method_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_security_method_xml), strip_text=True, strip_comments=True), + ) + + +class TestCuration(TestCase): + """Test VOResource Curation model""" + + test_curation_model = Curation( + publisher=ResourceName(value="STScI"), + creator=[Creator(name="Doe, J.")], + contributor=[ResourceName(value="Example Resource")], + date=[Date(value="2021-01-01T00:00:00Z", role="update")], + version="1.0", + contact=[Contact(name="John Doe")], + ) + + test_curation_xml = ( + '' + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00Z' + "1.0" + "John Doe" + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + curation = Curation.from_xml(self.test_curation_xml) + self.assertEqual(curation.publisher.value, "STScI") + self.assertEqual(curation.creator[0].name, "Doe, J.") + self.assertEqual(curation.contributor[0].value, "Example Resource") + self.assertEqual(curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(curation.date[0].role, "update") + self.assertEqual(curation.version, "1.0") + self.assertEqual(curation.contact[0].name, "John Doe") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_curation_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_curation_xml), strip_text=True, strip_comments=True), + ) + + +class TestContent(TestCase): + """Test VOResource Content model""" + + test_content_model = Content( + subject="Astronomy", + description="Example description", + source=[Source(value="https://example.edu", format="bibcode")], + reference_url="https://example.edu", + type="Education", + content_level="General", + relationship=[ + Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + ], + ) + + test_content_xml = ( + '' + "Astronomy" + "Example description" + 'https://example.edu' + "https://example.edu" + "Education" + "General" + '' + "isPartOf" + 'Example Resource' + "" + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + content = Content.from_xml(self.test_content_xml) + self.assertEqual(content.subject, "Astronomy") + self.assertEqual(content.description, "Example description") + self.assertEqual(content.source[0].value, "https://example.edu") + self.assertEqual(content.source[0].format, "bibcode") + self.assertEqual(content.reference_url, "https://example.edu") + self.assertEqual(content.type, "Education") + self.assertEqual(content.content_level, "General") + self.assertEqual(content.relationship[0].relationship_type, "isPartOf") + self.assertEqual(content.relationship[0].related_resource[0].value, "Example Resource") + self.assertEqual(content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_content_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_content_xml), strip_text=True, strip_comments=True), + ) + From c3fed542ad4fcc6cc08875ddaa3b6d51b42fdbe0 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Tue, 24 Sep 2024 16:46:38 -0400 Subject: [PATCH 07/41] finish test coverage --- tests/voresource/voresource_models_test.py | 368 ++++++++++++++++++++- 1 file changed, 367 insertions(+), 1 deletion(-) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 35d42a0..5c69240 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -24,9 +24,9 @@ Service, Source, Validation, - WebBrowser, WebService, ) +from vo_models.voresource.types import UTCDateTime VORESOURCE_NAMESPACE_HEADER = """ xmlns:xml="http://www.w3.org/XML/1998/namespace", @@ -402,3 +402,369 @@ def test_write_to_xml(self): canonicalize(etree.fromstring(self.test_content_xml), strip_text=True, strip_comments=True), ) + +class TestInterface(TestCase): + """Test the VOResource Interface model.""" + + test_interface_model = Interface( + version="1.0", + role="std", + access_url=[AccessURL(value="https://example.edu", use="full")], + mirror_url=[MirrorURL(value="https://example.edu", title="Mirror")], + security_method=[SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic")], + test_querystring="test", + ) + test_interface_xml = ( + '' + 'https://example.edu' + 'https://example.edu' + '' + "test" + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + interface = Interface.from_xml(self.test_interface_xml) + self.assertEqual(interface.version, "1.0") + self.assertEqual(interface.role, "std") + self.assertEqual(interface.access_url[0].value, "https://example.edu") + self.assertEqual(interface.access_url[0].use, "full") + self.assertEqual(interface.mirror_url[0].value, "https://example.edu") + self.assertEqual(interface.mirror_url[0].title, "Mirror") + self.assertEqual(interface.security_method[0].standard_id, "ivo://ivoa.net/std/Security#basic") + self.assertEqual(interface.test_querystring, "test") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_interface_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_interface_xml), strip_text=True, strip_comments=True), + ) + + +class TestWebService(TestCase): + """Test the VOResource WebService model.""" + + test_web_service_model = WebService( + wsdl_url="https://example.edu/wsdl", + access_url="https://example.edu", + ) + test_web_service_xml = ( + '' + "https://example.edu/wsdl" + "https://example.edu" + "" + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + web_service = WebService.from_xml(self.test_web_service_xml) + self.assertEqual(web_service.wsdl_url, "https://example.edu/wsdl") + self.assertEqual(web_service.access_url, "https://example.edu") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_web_service_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_web_service_xml), strip_text=True, strip_comments=True), + ) + +class TestResource(TestCase): + """Test the VOResource Resource model.""" + + test_resource_model = Resource( + created = UTCDateTime("1996-03-11T19:00:00Z"), + updated = UTCDateTime("1996-03-11T19:00:00Z"), + status = "active", + version = "1.0", + validation_level = [Validation(value=0, validated_by="https://example.edu")], + title = "Example Resource", + short_name = "example", + identifier = "https://example.edu", + alt_identifier = ["bibcode:2008ivoa.spec.0222P"], + curation = Curation( + publisher=ResourceName(value="STScI"), + creator=[Creator(name="Doe, J.")], + contributor=[ResourceName(value="Example Resource")], + date=[Date(value="2021-01-01T00:00:00Z", role="update")], + version="1.0", + contact=[Contact(name="John Doe")], + ), + content = Content( + subject="Astronomy", + description="Example description", + source=[Source(value="https://example.edu", format="bibcode")], + reference_url="https://example.edu", + type="Education", + content_level="General", + relationship=[ + Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + ], + ), + ) + + test_resource_xml = ( + '' + '1996-03-11T19:00:00Z' + '1996-03-11T19:00:00Z' + '0' + 'Example Resource' + 'example' + 'https://example.edu' + 'bibcode:2008ivoa.spec.0222P' + '' + 'STScI' + 'Doe, J.' + 'Example Resource' + '2021-01-01T00:00:00Z' + '1.0' + 'John Doe' + '' + '' + 'Astronomy' + 'Example description' + 'https://example.edu' + 'https://example.edu' + 'Education' + 'General' + '' + 'isPartOf' + 'Example Resource' + '' + '' + '' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + resource = Resource.from_xml(self.test_resource_xml) + self.assertEqual(resource.created, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(resource.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(resource.status, "active") + self.assertEqual(resource.version, "1.0") + self.assertEqual(resource.validation_level[0].value, 0) + self.assertEqual(resource.validation_level[0].validated_by, "https://example.edu") + self.assertEqual(resource.title, "Example Resource") + self.assertEqual(resource.short_name, "example") + self.assertEqual(resource.identifier, "https://example.edu") + self.assertEqual(resource.alt_identifier, ["bibcode:2008ivoa.spec.0222P"]) + self.assertEqual(resource.curation.publisher.value, "STScI") + self.assertEqual(resource.curation.creator[0].name, "Doe, J.") + self.assertEqual(resource.curation.contributor[0].value, "Example Resource") + self.assertEqual(resource.curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(resource.curation.date[0].role, "update") + self.assertEqual(resource.curation.version, "1.0") + self.assertEqual(resource.curation.contact[0].name, "John Doe") + self.assertEqual(resource.content.subject, "Astronomy") + self.assertEqual(resource.content.description, "Example description") + self.assertEqual(resource.content.source[0].value, "https://example.edu") + self.assertEqual(resource.content.source[0].format, "bibcode") + self.assertEqual(resource.content.reference_url, "https://example.edu") + self.assertEqual(resource.content.type, "Education") + self.assertEqual(resource.content.content_level, "General") + self.assertEqual(resource.content.relationship[0].relationship_type, "isPartOf") + self.assertEqual(resource.content.relationship[0].related_resource[0].value, "Example Resource") + self.assertEqual(resource.content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_resource_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_resource_xml), strip_text=True, strip_comments=True), + ) + +class TestOrganization(TestCase): + """Test the VOResource Organization model.""" + + test_organization_model = Organisation( + created = UTCDateTime("1996-03-11T19:00:00Z"), + updated = UTCDateTime("1996-03-11T19:00:00Z"), + status = "active", + version = "1.0", + facility = [ResourceName(value="Example Facility", ivo_id="ivo://example.edu/facility")], + instrument = [ResourceName(value="Example Instrument", ivo_id="ivo://example.edu/instrument")], + ) + + test_organization_xml = ( + '' + '1996-03-11T19:00:00Z' + '1996-03-11T19:00:00Z' + 'Example Facility' + 'Example Instrument' + '' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + organization = Organisation.from_xml(self.test_organization_xml) + self.assertEqual(organization.created, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(organization.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(organization.status, "active") + self.assertEqual(organization.version, "1.0") + self.assertEqual(organization.facility[0].value, "Example Facility") + self.assertEqual(organization.facility[0].ivo_id, "ivo://example.edu/facility") + self.assertEqual(organization.instrument[0].value, "Example Instrument") + self.assertEqual(organization.instrument[0].ivo_id, "ivo://example.edu/instrument") + + def test_write_to_xml(self): + """Test writing to XML.""" + + xml = self.test_organization_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_organization_xml), strip_text=True, strip_comments=True), + ) + +class TestCapability(TestCase): + """Test the VOResource Capability model.""" + + test_capability_model = Capability( + standard_id = "ivo://ivoa.net/std/TAP", + validation_level = [Validation(value=0, validated_by="https://example.edu")], + description = "Example description", + interface = [Interface(version="1.0", role="std")], + ) + + test_capability_xml = ( + '' + '0' + 'Example description' + '' + '' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + capability = Capability.from_xml(self.test_capability_xml) + self.assertEqual(capability.standard_id, "ivo://ivoa.net/std/TAP") + self.assertEqual(capability.validation_level[0].value, 0) + self.assertEqual(capability.validation_level[0].validated_by, "https://example.edu") + self.assertEqual(capability.description, "Example description") + self.assertEqual(capability.interface[0].version, "1.0") + self.assertEqual(capability.interface[0].role, "std") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_capability_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_capability_xml), strip_text=True, strip_comments=True), + ) + +class TestService(TestCase): + """Test the VOResource Service model.""" + + test_service_model = Service( + created = UTCDateTime("1996-03-11T19:00:00Z"), + updated = UTCDateTime("1996-03-11T19:00:00Z"), + status = "active", + version = "1.0", + capability = [Capability(standard_id="ivo://ivoa.net/std/TAP")], + interface = [Interface(version="1.0", role="std")], + title = "Example Service", + short_name = "example", + identifier = "https://example.edu", + alt_identifier = ["bibcode:2008ivoa.spec.0222P"], + curation = Curation( + publisher=ResourceName(value="STScI"), + creator=[Creator(name="Doe, J.")], + contributor=[ResourceName(value="Example Resource")], + date=[Date(value="2021-01-01T00:00:00Z", role="update")], + version="1.0", + contact=[Contact(name="John Doe")], + ), + content = Content( + subject="Astronomy", + description="Example description", + source=[Source(value="https://example.edu", format="bibcode")], + reference_url="https://example.edu", + type="Education", + content_level="General", + relationship=[ + Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + ], + ), + ) + + test_service_xml = ( + '' + '1996-03-11T19:00:00Z' + '1996-03-11T19:00:00Z' + '' + '' + 'Example Service' + 'example' + 'https://example.edu' + 'bibcode:2008ivoa.spec.0222P' + '' + 'STScI' + 'Doe, J.' + 'Example Resource' + '2021-01-01T00:00:00Z' + '1.0' + 'John Doe' + '' + '' + 'Astronomy' + 'Example description' + 'https://example.edu' + 'https://example.edu' + 'Education' + 'General' + '' + 'isPartOf' + 'Example Resource' + '' + '' + '' + ) + + def test_read_from_xml(self): + """Test reading from XML.""" + service = Service.from_xml(self.test_service_xml) + self.assertEqual(service.created, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(service.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(service.status, "active") + self.assertEqual(service.version, "1.0") + self.assertEqual(service.capability[0].standard_id, "ivo://ivoa.net/std/TAP") + self.assertEqual(service.interface[0].version, "1.0") + self.assertEqual(service.interface[0].role, "std") + self.assertEqual(service.title, "Example Service") + self.assertEqual(service.short_name, "example") + self.assertEqual(service.identifier, "https://example.edu") + self.assertEqual(service.alt_identifier, ["bibcode:2008ivoa.spec.0222P"]) + self.assertEqual(service.curation.publisher.value, "STScI") + self.assertEqual(service.curation.creator[0].name, "Doe, J.") + self.assertEqual(service.curation.contributor[0].value, "Example Resource") + self.assertEqual(service.curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(service.curation.date[0].role, "update") + self.assertEqual(service.curation.version, "1.0") + self.assertEqual(service.curation.contact[0].name, "John Doe") + self.assertEqual(service.content.subject, "Astronomy") + self.assertEqual(service.content.description, "Example description") + self.assertEqual(service.content.source[0].value, "https://example.edu") + self.assertEqual(service.content.source[0].format, "bibcode") + self.assertEqual(service.content.reference_url, "https://example.edu") + self.assertEqual(service.content.type, "Education") + self.assertEqual(service.content.content_level, "General") + self.assertEqual(service.content.relationship[0].relationship_type, "isPartOf") + self.assertEqual(service.content.relationship[0].related_resource[0].value, "Example Resource") + self.assertEqual(service.content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") + + def test_write_to_xml(self): + """Test writing to XML.""" + xml = self.test_service_model.to_xml() + self.assertEqual( + canonicalize(xml, strip_text=True, strip_comments=True), + canonicalize(etree.fromstring(self.test_service_xml), strip_text=True, strip_comments=True), + ) From b354ab60b6a25ce38be349bd8b22f950bd19bebe Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 10:43:23 -0400 Subject: [PATCH 08/41] test updates --- tests/tapregext/tapregext_models_test.py | 4 +- tests/voresource/voresource_models_test.py | 178 +++++++++++---------- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py index b859a8c..a835721 100644 --- a/tests/tapregext/tapregext_models_test.py +++ b/tests/tapregext/tapregext_models_test.py @@ -5,7 +5,7 @@ from lxml import etree -from vo_models.tapregext import ( +from vo_models.tapregext.models import ( DataLimit, DataLimits, DataModelType, @@ -14,12 +14,10 @@ LanguageFeatureList, OutputFormat, TableAccess, - TAPCapRestriction, TimeLimits, UploadMethod, Version, ) -from vo_models.voresource import Validation TAPREGEXT_NAMESPACE_HEADER = """xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 5c69240..156b5b1 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -5,7 +5,7 @@ from lxml import etree -from vo_models.voresource import ( +from vo_models.voresource.models import ( AccessURL, Capability, Contact, @@ -472,20 +472,21 @@ def test_write_to_xml(self): canonicalize(etree.fromstring(self.test_web_service_xml), strip_text=True, strip_comments=True), ) + class TestResource(TestCase): """Test the VOResource Resource model.""" test_resource_model = Resource( - created = UTCDateTime("1996-03-11T19:00:00Z"), - updated = UTCDateTime("1996-03-11T19:00:00Z"), - status = "active", - version = "1.0", - validation_level = [Validation(value=0, validated_by="https://example.edu")], - title = "Example Resource", - short_name = "example", - identifier = "https://example.edu", - alt_identifier = ["bibcode:2008ivoa.spec.0222P"], - curation = Curation( + created=UTCDateTime("1996-03-11T19:00:00Z"), + updated=UTCDateTime("1996-03-11T19:00:00Z"), + status="active", + version="1.0", + validation_level=[Validation(value=0, validated_by="https://example.edu")], + title="Example Resource", + short_name="example", + identifier="https://example.edu", + alt_identifier=["bibcode:2008ivoa.spec.0222P"], + curation=Curation( publisher=ResourceName(value="STScI"), creator=[Creator(name="Doe, J.")], contributor=[ResourceName(value="Example Resource")], @@ -493,7 +494,7 @@ class TestResource(TestCase): version="1.0", contact=[Contact(name="John Doe")], ), - content = Content( + content=Content( subject="Astronomy", description="Example description", source=[Source(value="https://example.edu", format="bibcode")], @@ -511,34 +512,34 @@ class TestResource(TestCase): test_resource_xml = ( '' - '1996-03-11T19:00:00Z' - '1996-03-11T19:00:00Z' + "1996-03-11T19:00:00Z" + "1996-03-11T19:00:00Z" '0' - 'Example Resource' - 'example' - 'https://example.edu' - 'bibcode:2008ivoa.spec.0222P' - '' - 'STScI' - 'Doe, J.' - 'Example Resource' + "Example Resource" + "example" + "https://example.edu" + "bibcode:2008ivoa.spec.0222P" + "" + "STScI" + "Doe, J." + "Example Resource" '2021-01-01T00:00:00Z' - '1.0' - 'John Doe' - '' - '' - 'Astronomy' - 'Example description' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" 'https://example.edu' - 'https://example.edu' - 'Education' - 'General' - '' - 'isPartOf' + "https://example.edu" + "Education" + "General" + "" + "isPartOf" 'Example Resource' - '' - '' - '' + "" + "" + "" ) def test_read_from_xml(self): @@ -580,25 +581,26 @@ def test_write_to_xml(self): canonicalize(etree.fromstring(self.test_resource_xml), strip_text=True, strip_comments=True), ) + class TestOrganization(TestCase): """Test the VOResource Organization model.""" test_organization_model = Organisation( - created = UTCDateTime("1996-03-11T19:00:00Z"), - updated = UTCDateTime("1996-03-11T19:00:00Z"), - status = "active", - version = "1.0", - facility = [ResourceName(value="Example Facility", ivo_id="ivo://example.edu/facility")], - instrument = [ResourceName(value="Example Instrument", ivo_id="ivo://example.edu/instrument")], + created=UTCDateTime("1996-03-11T19:00:00Z"), + updated=UTCDateTime("1996-03-11T19:00:00Z"), + status="active", + version="1.0", + facility=[ResourceName(value="Example Facility", ivo_id="ivo://example.edu/facility")], + instrument=[ResourceName(value="Example Instrument", ivo_id="ivo://example.edu/instrument")], ) test_organization_xml = ( '' - '1996-03-11T19:00:00Z' - '1996-03-11T19:00:00Z' + "1996-03-11T19:00:00Z" + "1996-03-11T19:00:00Z" 'Example Facility' 'Example Instrument' - '' + "" ) def test_read_from_xml(self): @@ -622,22 +624,23 @@ def test_write_to_xml(self): canonicalize(etree.fromstring(self.test_organization_xml), strip_text=True, strip_comments=True), ) + class TestCapability(TestCase): """Test the VOResource Capability model.""" test_capability_model = Capability( - standard_id = "ivo://ivoa.net/std/TAP", - validation_level = [Validation(value=0, validated_by="https://example.edu")], - description = "Example description", - interface = [Interface(version="1.0", role="std")], + standard_id="ivo://ivoa.net/std/TAP", + validation_level=[Validation(value=0, validated_by="https://example.edu")], + description="Example description", + interface=[Interface(version="1.0", role="std")], ) test_capability_xml = ( '' '0' - 'Example description' + "Example description" '' - '' + "" ) def test_read_from_xml(self): @@ -658,21 +661,22 @@ def test_write_to_xml(self): canonicalize(etree.fromstring(self.test_capability_xml), strip_text=True, strip_comments=True), ) + class TestService(TestCase): """Test the VOResource Service model.""" test_service_model = Service( - created = UTCDateTime("1996-03-11T19:00:00Z"), - updated = UTCDateTime("1996-03-11T19:00:00Z"), - status = "active", - version = "1.0", - capability = [Capability(standard_id="ivo://ivoa.net/std/TAP")], - interface = [Interface(version="1.0", role="std")], - title = "Example Service", - short_name = "example", - identifier = "https://example.edu", - alt_identifier = ["bibcode:2008ivoa.spec.0222P"], - curation = Curation( + created=UTCDateTime("1996-03-11T19:00:00Z"), + updated=UTCDateTime("1996-03-11T19:00:00Z"), + status="active", + version="1.0", + capability=[Capability(standard_id="ivo://ivoa.net/std/TAP")], + interface=[Interface(version="1.0", role="std")], + title="Example Service", + short_name="example", + identifier="https://example.edu", + alt_identifier=["bibcode:2008ivoa.spec.0222P"], + curation=Curation( publisher=ResourceName(value="STScI"), creator=[Creator(name="Doe, J.")], contributor=[ResourceName(value="Example Resource")], @@ -680,7 +684,7 @@ class TestService(TestCase): version="1.0", contact=[Contact(name="John Doe")], ), - content = Content( + content=Content( subject="Astronomy", description="Example description", source=[Source(value="https://example.edu", format="bibcode")], @@ -698,35 +702,35 @@ class TestService(TestCase): test_service_xml = ( '' - '1996-03-11T19:00:00Z' - '1996-03-11T19:00:00Z' + "1996-03-11T19:00:00Z" + "1996-03-11T19:00:00Z" '' '' - 'Example Service' - 'example' - 'https://example.edu' - 'bibcode:2008ivoa.spec.0222P' - '' - 'STScI' - 'Doe, J.' - 'Example Resource' + "Example Service" + "example" + "https://example.edu" + "bibcode:2008ivoa.spec.0222P" + "" + "STScI" + "Doe, J." + "Example Resource" '2021-01-01T00:00:00Z' - '1.0' - 'John Doe' - '' - '' - 'Astronomy' - 'Example description' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" 'https://example.edu' - 'https://example.edu' - 'Education' - 'General' - '' - 'isPartOf' + "https://example.edu" + "Education" + "General" + "" + "isPartOf" 'Example Resource' - '' - '' - '' + "" + "" + "" ) def test_read_from_xml(self): From 3911c443cecb435fcdb2714cd4fa8f562a7d1339 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 12:00:03 -0400 Subject: [PATCH 09/41] explicit imports, fix defaults --- vo_models/tapregext/models.py | 6 +++--- vo_models/uws/models.py | 10 +++------- vo_models/vodataservice/models.py | 8 ++++---- vo_models/voresource/models.py | 11 +++++------ vo_models/vosi/availability/models.py | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 1e42feb..97590ef 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -4,7 +4,7 @@ from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource import Capability, Interface, Validation +from vo_models.voresource.models import Capability, Interface, Validation from vo_models.voresource.types import IdentifierURI NSMAP = { @@ -190,10 +190,10 @@ class TableAccess(TAPCapRestriction, tag="capability", ns="vr", nsmap=NSMAP): (element) - Limits on the size of uploaded data. """ - data_model: Optional[list[DataModelType]] = element(tag="dataModel", default_factory=[]) + data_model: Optional[list[DataModelType]] = element(tag="dataModel", default_factory=list) language: list[Language] = element(tag="language") output_format: list[OutputFormat] = element(tag="outputFormat") - upload_method: Optional[list[UploadMethod]] = element(tag="uploadMethod", default_factory=[]) + upload_method: Optional[list[UploadMethod]] = element(tag="uploadMethod", default_factory=list) retention_period: Optional[TimeLimits] = element(tag="retentionPeriod", default=None) execution_duration: Optional[TimeLimits] = element(tag="executionDuration", default=None) output_limit: Optional[DataLimits] = element(tag="outputLimit", default=None) diff --git a/vo_models/uws/models.py b/vo_models/uws/models.py index 21e2368..fe708b2 100644 --- a/vo_models/uws/models.py +++ b/vo_models/uws/models.py @@ -1,10 +1,7 @@ """UWS Job Schema using Pydantic-XML models""" from typing import Annotated, Dict, Generic, Optional, TypeAlias, TypeVar - -from pydantic import BeforeValidator -from pydantic import ConfigDict - +from pydantic import BeforeValidator, ConfigDict from pydantic_xml import BaseXmlModel, attr, element from vo_models.uws.types import ErrorType, ExecutionPhase, UWSVersion @@ -46,8 +43,7 @@ class Parameter(BaseXmlModel, tag="parameter", ns="uws", nsmap=NSMAP): MultiValuedParameter: TypeAlias = Annotated[ - list[Parameter], - BeforeValidator(lambda v: v if isinstance(v, list) else [v]) + list[Parameter], BeforeValidator(lambda v: v if isinstance(v, list) else [v]) ] """Type for a multi-valued parameter. @@ -276,7 +272,7 @@ class JobSummary(BaseXmlModel, Generic[ParametersType], tag="job", ns="uws", nsm parameters: Optional[ParametersType] = element(tag="parameters", default=None) results: Optional[Results] = element(tag="results", default=Results()) error_summary: Optional[ErrorSummary] = element(tag="errorSummary", default=None) - job_info: Optional[list[str]] = element(tag="jobInfo", default=[]) + job_info: Optional[list[str]] = element(tag="jobInfo", default_factory=list) version: Optional[UWSVersion] = attr(default=UWSVersion.V1_1) diff --git a/vo_models/vodataservice/models.py b/vo_models/vodataservice/models.py index 709e009..56a89d9 100644 --- a/vo_models/vodataservice/models.py +++ b/vo_models/vodataservice/models.py @@ -128,7 +128,7 @@ class TableParam(BaseXmlModel, ns="", tag="column"): utype: Optional[str] = element(tag="utype", default=None) xtype: Optional[str] = element(tag="xtype", default=None) datatype: Optional[DataType] = element(tag="dataType", default=None) - flag: Optional[list[str]] = element(tag="flag", default=None) + flag: Optional[list[str]] = element(tag="flag", default_factory=list) def __init__(__pydantic_self__, **data: Any) -> None: data["datatype"] = __pydantic_self__.__make_datatype_element(data) @@ -225,8 +225,8 @@ class Table(BaseXmlModel, tag="table", ns="", skip_empty=True): description: Optional[str] = element(tag="description", ns="", default=None) utype: Optional[str] = element(tag="utype", ns="", default=None) nrows: Optional[int] = element(tag="nrows", gte=0, ns="", default=None) - column: Optional[list[TableParam]] = element(tag="column", ns="", default=None) - foreign_key: Optional[list[ForeignKey]] = element(tag="foreignKey", ns="", default=None) + column: Optional[list[TableParam]] = element(tag="column", ns="", default_factory=list) + foreign_key: Optional[list[ForeignKey]] = element(tag="foreignKey", ns="", default_factory=list) def __init__(__pydantic_self__, **data: Any) -> None: """Escape any keys that are passed in.""" @@ -269,7 +269,7 @@ class TableSchema(BaseXmlModel, tag="schema", ns="", skip_empty=True): title: Optional[str] = element(tag="title", default=None) description: Optional[str] = element(tag="description", default=None) utype: Optional[str] = element(tag="utype", default=None) - table: Optional[list[Table]] = element(tag="table", default=None) + table: Optional[list[Table]] = element(tag="table", default_factory=list) def __init__(__pydantic_self__, **data: Any) -> None: """Escape any keys that are passed in.""" diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 533f70e..e85a1c0 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -159,7 +159,7 @@ class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): including a scheme here. """ - ivo_id: Optional[IdentifierURI] = attr(name="ivo_id") + ivo_id: Optional[IdentifierURI] = attr(name="ivo_id", default=None) name: ResourceName = element(tag="name") address: Optional[str] = element(tag="address", default=None) @@ -287,7 +287,7 @@ class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): description: str = element(tag="description") source: Optional[Source] = element(tag="source", default=None) reference_url: networks.AnyUrl = element(tag="referenceURL") - type: Optional[list[str]] = element(tag="type", default=None) + type: Optional[list[str]] = element(tag="type", default_factory=list) content_level: Optional[list[str]] = element(tag="contentLevel", default_factory=list) relationship: Optional[list[Relationship]] = element(tag="relationship", default_factory=list) @@ -406,14 +406,13 @@ class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): @field_validator("created", "updated") def _validate_timestamps(cls, values): """Ensure that the created and updated timestamps are not in the future""" - for timestamp in ("created", "updated"): - if values[timestamp] > datetime.datetime.utcnow(): - raise ValueError(f"{timestamp} timestamp must not be in the future") + if values > datetime.datetime.now(datetime.timezone.utc): + raise ValueError(f"{values} timestamp must not be in the future") @field_validator("short_name") def _validate_short_name(cls, values): """Ensure that the short name is no more than 16 characters""" - if values["short_name"] and len(values["short_name"]) > 16: + if values and len(values) > 16: raise ValueError("Short name must be no more than 16 characters") diff --git a/vo_models/vosi/availability/models.py b/vo_models/vosi/availability/models.py index c9f3f2d..b79e707 100644 --- a/vo_models/vosi/availability/models.py +++ b/vo_models/vosi/availability/models.py @@ -34,4 +34,4 @@ class Availability(BaseXmlModel, tag="availability", nsmap=NSMAP, skip_empty=Tru up_since: Optional[UTCTimestamp] = element(tag="upSince", default=None) down_at: Optional[UTCTimestamp] = element(tag="downAt", default=None) back_at: Optional[UTCTimestamp] = element(tag="backAt", default=None) - note: Optional[list[str]] = element(tag="note", default=None) + note: Optional[list[str]] = element(tag="note", default_factory=list) From bca654a91f7d5fa2a20c2dd8e3243f75d7395635 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 12:00:11 -0400 Subject: [PATCH 10/41] fix tests --- tests/voresource/voresource_models_test.py | 81 +++++++++++++++------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 156b5b1..88e575f 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -194,7 +194,7 @@ class TestContact(TestCase): """Test VOResource Contact model""" test_contact_model = Contact( - name="John Doe", + name=ResourceName(value="John Doe"), address="1234 Example St.", email="jdoe@mail.com", telephone="555-555-5555", @@ -231,7 +231,7 @@ def test_write_to_xml(self): class TestCreator(TestCase): """Test VOResource Creator model""" - test_creator_model = Creator(name="Doe, J.", logo="https://example.edu/logo.png") + test_creator_model = Creator(name=ResourceName(value="Doe, J."), logo="https://example.edu/logo.png") test_creator_xml = ( '' "Doe, J." @@ -309,11 +309,11 @@ class TestCuration(TestCase): test_curation_model = Curation( publisher=ResourceName(value="STScI"), - creator=[Creator(name="Doe, J.")], + creator=[Creator(name=ResourceName(value="Doe, J."))], contributor=[ResourceName(value="Example Resource")], date=[Date(value="2021-01-01T00:00:00Z", role="update")], version="1.0", - contact=[Contact(name="John Doe")], + contact=[Contact(name=ResourceName(value="John Doe"))], ) test_curation_xml = ( @@ -351,12 +351,12 @@ class TestContent(TestCase): """Test VOResource Content model""" test_content_model = Content( - subject="Astronomy", + subject=["Astronomy"], description="Example description", - source=[Source(value="https://example.edu", format="bibcode")], + source=Source(value="https://example.edu", format="bibcode"), reference_url="https://example.edu", - type="Education", - content_level="General", + type=["Education"], + content_level=["General"], relationship=[ Relationship( relationship_type="isPartOf", @@ -448,8 +448,8 @@ class TestWebService(TestCase): """Test the VOResource WebService model.""" test_web_service_model = WebService( - wsdl_url="https://example.edu/wsdl", - access_url="https://example.edu", + wsdl_url=["https://example.edu/wsdl"], + access_url=[AccessURL(value="https://example.edu", use="full")], ) test_web_service_xml = ( '' @@ -488,19 +488,19 @@ class TestResource(TestCase): alt_identifier=["bibcode:2008ivoa.spec.0222P"], curation=Curation( publisher=ResourceName(value="STScI"), - creator=[Creator(name="Doe, J.")], + creator=[Creator(name=ResourceName(value="Doe, J."))], contributor=[ResourceName(value="Example Resource")], date=[Date(value="2021-01-01T00:00:00Z", role="update")], version="1.0", - contact=[Contact(name="John Doe")], + contact=[Contact(name=ResourceName(value="John Doe"))], ), content=Content( - subject="Astronomy", + subject=["Astronomy"], description="Example description", - source=[Source(value="https://example.edu", format="bibcode")], + source=Source(value="https://example.edu", format="bibcode"), reference_url="https://example.edu", - type="Education", - content_level="General", + type=["Education"], + content_level=["General"], relationship=[ Relationship( relationship_type="isPartOf", @@ -586,6 +586,16 @@ class TestOrganization(TestCase): """Test the VOResource Organization model.""" test_organization_model = Organisation( + title="Example Organization", + identifier="https://example.edu", + curation=Curation( + publisher=ResourceName(value="Example Publisher"), contact=[Contact(name=ResourceName(value="John Doe"))] + ), + content=Content( + subject=["Astronomy"], + description="Example description", + reference_url="https://example.edu", + ), created=UTCDateTime("1996-03-11T19:00:00Z"), updated=UTCDateTime("1996-03-11T19:00:00Z"), status="active", @@ -598,6 +608,17 @@ class TestOrganization(TestCase): '' "1996-03-11T19:00:00Z" "1996-03-11T19:00:00Z" + "Example Organization" + "https://example.edu" + "" + "Example Publisher" + "John Doe" + "" + "" + "Astronomy" + "Example description" + "https://example.edu" + "" 'Example Facility' 'Example Instrument' "" @@ -606,6 +627,13 @@ class TestOrganization(TestCase): def test_read_from_xml(self): """Test reading from XML.""" organization = Organisation.from_xml(self.test_organization_xml) + self.assertEqual(organization.title, "Example Organization") + self.assertEqual(organization.identifier, "https://example.edu") + self.assertEqual(organization.curation.publisher.value, "Example Publisher") + self.assertEqual(organization.curation.contact[0].name, "John Doe") + self.assertEqual(organization.content.subject, "Astronomy") + self.assertEqual(organization.content.description, "Example description") + self.assertEqual(organization.content.reference_url, "https://example.edu") self.assertEqual(organization.created, UTCDateTime("1996-03-11T19:00:00Z")) self.assertEqual(organization.updated, UTCDateTime("1996-03-11T19:00:00Z")) self.assertEqual(organization.status, "active") @@ -617,7 +645,6 @@ def test_read_from_xml(self): def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_organization_model.to_xml() self.assertEqual( canonicalize(xml, strip_text=True, strip_comments=True), @@ -632,7 +659,9 @@ class TestCapability(TestCase): standard_id="ivo://ivoa.net/std/TAP", validation_level=[Validation(value=0, validated_by="https://example.edu")], description="Example description", - interface=[Interface(version="1.0", role="std")], + interface=[ + Interface(version="1.0", role="std", access_url=[AccessURL(value="https://example.edu", use="full")]) + ], ) test_capability_xml = ( @@ -671,26 +700,28 @@ class TestService(TestCase): status="active", version="1.0", capability=[Capability(standard_id="ivo://ivoa.net/std/TAP")], - interface=[Interface(version="1.0", role="std")], + interface=[ + Interface(version="1.0", role="std", access_url=[AccessURL(value="https://example.edu", use="full")]) + ], title="Example Service", short_name="example", identifier="https://example.edu", alt_identifier=["bibcode:2008ivoa.spec.0222P"], curation=Curation( publisher=ResourceName(value="STScI"), - creator=[Creator(name="Doe, J.")], + creator=[Creator(name=ResourceName(value="Doe, J."))], contributor=[ResourceName(value="Example Resource")], date=[Date(value="2021-01-01T00:00:00Z", role="update")], version="1.0", - contact=[Contact(name="John Doe")], + contact=[Contact(name=ResourceName(value="John Doe"))], ), content=Content( - subject="Astronomy", + subject=["Astronomy"], description="Example description", - source=[Source(value="https://example.edu", format="bibcode")], + source=Source(value="https://example.edu", format="bibcode"), reference_url="https://example.edu", - type="Education", - content_level="General", + type=["Education"], + content_level=["General"], relationship=[ Relationship( relationship_type="isPartOf", From 0fe74b033d301a63e795554fddb0b09402b06cb5 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 12:08:53 -0400 Subject: [PATCH 11/41] add handling for resourcename in contact/creator --- tests/voresource/voresource_models_test.py | 2 +- vo_models/voresource/models.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 88e575f..f53d281 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -194,7 +194,7 @@ class TestContact(TestCase): """Test VOResource Contact model""" test_contact_model = Contact( - name=ResourceName(value="John Doe"), + name="John Doe", address="1234 Example St.", email="jdoe@mail.com", telephone="555-555-5555", diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index e85a1c0..439c957 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -167,6 +167,13 @@ class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): telephone: Optional[str] = element(tag="telephone", default=None) alt_identifier: Optional[list[networks.AnyUrl]] = element(tag="altIdentifier", default_factory=list) + @field_validator("name", mode="before") + def _validate_name(cls, values): + """Ensure name is a ResourceName instance""" + if isinstance(values, str): + return ResourceName(value=values) + return values + class Creator(BaseXmlModel, ns="vr", nsmap=NSMAP): """The entity (e.g. person or organisation) primarily responsible for creating something @@ -193,6 +200,13 @@ class Creator(BaseXmlModel, ns="vr", nsmap=NSMAP): logo: Optional[networks.AnyUrl] = element(tag="logo", default=None) alt_identifier: Optional[list[networks.AnyUrl]] = element(tag="altIdentifier", default_factory=list) + @field_validator("name", mode="before") + def _validate_name(cls, values): + """Ensure name is a ResourceName instance""" + if isinstance(values, str): + return ResourceName(value=values) + return values + class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): """A description of the relationship between one resource and one or more other resources. From e27bd5ace0ba35b1134192a25360cf7d9cecdf13 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 13:55:54 -0400 Subject: [PATCH 12/41] voresource tests passing --- tests/voresource/voresource_models_test.py | 538 ++++++++++----------- 1 file changed, 260 insertions(+), 278 deletions(-) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index f53d281..9e733a8 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -1,9 +1,10 @@ """Tests for VOResource models.""" +from datetime import timezone as tz from unittest import TestCase from xml.etree.ElementTree import canonicalize -from lxml import etree +from pydantic.networks import AnyUrl from vo_models.voresource.models import ( AccessURL, @@ -26,7 +27,7 @@ Validation, WebService, ) -from vo_models.voresource.types import UTCDateTime +from vo_models.voresource.types import UTCDateTime, UTCTimestamp, ValidationLevel VORESOURCE_NAMESPACE_HEADER = """ xmlns:xml="http://www.w3.org/XML/1998/namespace", @@ -41,20 +42,20 @@ class TestValidation(TestCase): """Test VOResource Validation model.""" test_validation_model = Validation(value=0, validated_by="https://example.edu") - test_validation_xml = '0' + test_validation_xml = '0' def test_read_from_xml(self): """Test reading from XML.""" validation = Validation.from_xml(self.test_validation_xml) - self.assertEqual(validation.value, 0) - self.assertEqual(validation.validated_by, "https://example.edu") + self.assertEqual(validation.value, ValidationLevel(0)) + self.assertEqual(validation.validated_by, AnyUrl("https://example.edu")) def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_validation_model.to_xml() + test_xml = self.test_validation_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_validation_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_validation_xml, strip_text=True), ) @@ -72,10 +73,10 @@ def test_read_from_xml(self): def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_resource_name_model.to_xml() + test_xml = self.test_resource_name_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_resource_name_xml), strip_text=True, strip_comments=True), + canonicalize(self.test_resource_name_xml, strip_text=True), + canonicalize(test_xml, strip_text=True), ) @@ -84,21 +85,21 @@ class TestDate(TestCase): test_date_model = Date(value="2021-01-01T00:00:00Z", role="update") test_date_xml = ( - '2021-01-01T00:00:00Z' + '2021-01-01T00:00:00.000Z' ) def test_read_from_xml(self): """Test reading from XML.""" date = Date.from_xml(self.test_date_xml) - self.assertEqual(date.value, "2021-01-01T00:00:00Z") + self.assertEqual(date.value.isoformat(), "2021-01-01T00:00:00.000Z") self.assertEqual(date.role, "update") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_date_model.to_xml() + test_xml = self.test_date_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_date_xml), strip_text=True, strip_comments=True), + canonicalize(self.test_date_xml, strip_text=True), + canonicalize(test_xml, strip_text=True), ) @@ -106,22 +107,20 @@ class TestSource(TestCase): """Test VOResource Source model""" test_source_model = Source(value="https://example.edu", format="bibcode") - test_source_xml = ( - 'https://example.edu' - ) + test_source_xml = 'https://example.edu/' def test_read_from_xml(self): """Test reading from XML.""" source = Source.from_xml(self.test_source_xml) - self.assertEqual(source.value, "https://example.edu") + self.assertEqual(source.value, AnyUrl("https://example.edu")) self.assertEqual(source.format, "bibcode") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_source_model.to_xml() + test_xml = self.test_source_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_source_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_source_xml, strip_text=True), ) @@ -129,20 +128,20 @@ class TestRights(TestCase): """Test VOResource Rights model""" test_rights_model = Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/") - test_rights_xml = 'CC BY 4.0' + test_rights_xml = 'CC BY 4.0' def test_read_from_xml(self): """Test reading from XML.""" rights = Rights.from_xml(self.test_rights_xml) self.assertEqual(rights.value, "CC BY 4.0") - self.assertEqual(rights.rights_uri, "https://creativecommons.org/licenses/by/4.0/") + self.assertEqual(rights.rights_uri, AnyUrl("https://creativecommons.org/licenses/by/4.0/")) def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_rights_model.to_xml() + test_xml = self.test_rights_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_rights_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_rights_xml, strip_text=True), ) @@ -150,22 +149,20 @@ class TestAccessURL(TestCase): """Test VOResource AccessURL model""" test_access_url_model = AccessURL(value="https://example.edu", use="full") - test_access_url_xml = ( - 'https://example.edu' - ) + test_access_url_xml = 'https://example.edu/' def test_read_from_xml(self): """Test reading from XML.""" access_url = AccessURL.from_xml(self.test_access_url_xml) - self.assertEqual(access_url.value, "https://example.edu") + self.assertEqual(access_url.value, AnyUrl("https://example.edu")) self.assertEqual(access_url.use, "full") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_access_url_model.to_xml() + test_xml = self.test_access_url_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_access_url_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_access_url_xml, strip_text=True), ) @@ -173,20 +170,20 @@ class TestMirrorURL(TestCase): """Test VOResource MirrorURL model""" test_mirror_url_model = MirrorURL(value="https://example.edu", title="Mirror") - test_mirror_url_xml = 'https://example.edu' + test_mirror_url_xml = 'https://example.edu/' def test_read_from_xml(self): """Test reading from XML.""" mirror_url = MirrorURL.from_xml(self.test_mirror_url_xml) - self.assertEqual(mirror_url.value, "https://example.edu") + self.assertEqual(mirror_url.value, AnyUrl("https://example.edu")) self.assertEqual(mirror_url.title, "Mirror") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_mirror_url_model.to_xml() + test_xml = self.test_mirror_url_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_mirror_url_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_mirror_url_xml, strip_text=True), ) @@ -194,37 +191,37 @@ class TestContact(TestCase): """Test VOResource Contact model""" test_contact_model = Contact( - name="John Doe", + name=ResourceName(value="John Doe"), address="1234 Example St.", email="jdoe@mail.com", telephone="555-555-5555", alt_identifier=["http://orcid.org/0000-0001-9718-6515"], ) test_contact_xml = ( - '' - "John Doe" - "
1234 Example St.
" - "jdoe@mail.com" - "555-555-5555" - "http://orcid.org/0000-0001-9718-6515" - "
" + '' + "John Doe" + "1234 Example St." + "jdoe@mail.com" + "555-555-5555" + "http://orcid.org/0000-0001-9718-6515" + "" ) def test_read_from_xml(self): """Test reading from XML.""" contact = Contact.from_xml(self.test_contact_xml) - self.assertEqual(contact.name, "John Doe") + self.assertEqual(contact.name.value, "John Doe") self.assertEqual(contact.address, "1234 Example St.") self.assertEqual(contact.email, "jdoe@mail.com") self.assertEqual(contact.telephone, "555-555-5555") - self.assertEqual(contact.alt_identifier, ["http://orcid.org/0000-0001-9718-6515"]) + self.assertEqual(contact.alt_identifier, [AnyUrl("http://orcid.org/0000-0001-9718-6515")]) def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_contact_model.to_xml() + test_xml = self.test_contact_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_contact_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_contact_xml, strip_text=True), ) @@ -233,24 +230,24 @@ class TestCreator(TestCase): test_creator_model = Creator(name=ResourceName(value="Doe, J."), logo="https://example.edu/logo.png") test_creator_xml = ( - '' - "Doe, J." - "https://example.edu/logo.png" - "" + '' + "Doe, J." + "https://example.edu/logo.png" + "" ) def test_read_from_xml(self): """Test reading from XML.""" creator = Creator.from_xml(self.test_creator_xml) - self.assertEqual(creator.name, "Doe, J.") - self.assertEqual(creator.logo, "https://example.edu/logo.png") + self.assertEqual(creator.name.value, "Doe, J.") + self.assertEqual(creator.logo, AnyUrl("https://example.edu/logo.png")) def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_creator_model.to_xml() + test_xml = self.test_creator_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_creator_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_creator_xml, strip_text=True), ) @@ -262,10 +259,10 @@ class TestRelationship(TestCase): related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], ) test_relationship_xml = ( - '' - "isPartOf" - 'Example Resource' - "" + '' + "isPartOf" + 'Example Resource' + "" ) def test_read_from_xml(self): @@ -277,10 +274,10 @@ def test_read_from_xml(self): def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_relationship_model.to_xml() + test_xml = self.test_relationship_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_relationship_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_relationship_xml, strip_text=True), ) @@ -288,19 +285,19 @@ class TestSecurityMethod(TestCase): """Test VOResource SecurityMethod model""" test_security_method_model = SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic") - test_security_method_xml = '' + test_security_method_xml = '' def test_read_from_xml(self): """Test reading from XML.""" security_method = SecurityMethod.from_xml(self.test_security_method_xml) - self.assertEqual(security_method.standard_id, "ivo://ivoa.net/std/Security#basic") + self.assertEqual(security_method.standard_id, AnyUrl("ivo://ivoa.net/std/Security#basic")) def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_security_method_model.to_xml() + test_xml = self.test_security_method_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_security_method_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_security_method_xml, strip_text=True), ) @@ -317,33 +314,33 @@ class TestCuration(TestCase): ) test_curation_xml = ( - '' - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00Z' - "1.0" - "John Doe" - "" + '' + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" ) def test_read_from_xml(self): """Test reading from XML.""" curation = Curation.from_xml(self.test_curation_xml) self.assertEqual(curation.publisher.value, "STScI") - self.assertEqual(curation.creator[0].name, "Doe, J.") + self.assertEqual(curation.creator[0].name.value, "Doe, J.") self.assertEqual(curation.contributor[0].value, "Example Resource") - self.assertEqual(curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(curation.date[0].value.isoformat(), "2021-01-01T00:00:00.000Z") self.assertEqual(curation.date[0].role, "update") self.assertEqual(curation.version, "1.0") - self.assertEqual(curation.contact[0].name, "John Doe") + self.assertEqual(curation.contact[0].name.value, "John Doe") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_curation_model.to_xml() + test_xml = self.test_curation_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_curation_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_curation_xml, strip_text=True), ) @@ -366,40 +363,40 @@ class TestContent(TestCase): ) test_content_xml = ( - '' - "Astronomy" - "Example description" - 'https://example.edu' - "https://example.edu" - "Education" - "General" - '' - "isPartOf" - 'Example Resource' - "" - "" + '' + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" ) def test_read_from_xml(self): """Test reading from XML.""" content = Content.from_xml(self.test_content_xml) - self.assertEqual(content.subject, "Astronomy") + self.assertEqual(content.subject[0], "Astronomy") self.assertEqual(content.description, "Example description") - self.assertEqual(content.source[0].value, "https://example.edu") - self.assertEqual(content.source[0].format, "bibcode") - self.assertEqual(content.reference_url, "https://example.edu") - self.assertEqual(content.type, "Education") - self.assertEqual(content.content_level, "General") + self.assertEqual(content.source.value, AnyUrl("https://example.edu")) + self.assertEqual(content.source.format, "bibcode") + self.assertEqual(content.reference_url, AnyUrl("https://example.edu")) + self.assertEqual(content.type[0], "Education") + self.assertEqual(content.content_level[0], "General") self.assertEqual(content.relationship[0].relationship_type, "isPartOf") self.assertEqual(content.relationship[0].related_resource[0].value, "Example Resource") self.assertEqual(content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_content_model.to_xml() + test_xml = self.test_content_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_content_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_content_xml, strip_text=True), ) @@ -415,12 +412,12 @@ class TestInterface(TestCase): test_querystring="test", ) test_interface_xml = ( - '' - 'https://example.edu' - 'https://example.edu' - '' - "test" - "" + '' + 'https://example.edu/' + 'https://example.edu/' + '' + "test" + "" ) def test_read_from_xml(self): @@ -428,19 +425,19 @@ def test_read_from_xml(self): interface = Interface.from_xml(self.test_interface_xml) self.assertEqual(interface.version, "1.0") self.assertEqual(interface.role, "std") - self.assertEqual(interface.access_url[0].value, "https://example.edu") + self.assertEqual(interface.access_url[0].value, AnyUrl("https://example.edu")) self.assertEqual(interface.access_url[0].use, "full") - self.assertEqual(interface.mirror_url[0].value, "https://example.edu") + self.assertEqual(interface.mirror_url[0].value, AnyUrl("https://example.edu")) self.assertEqual(interface.mirror_url[0].title, "Mirror") - self.assertEqual(interface.security_method[0].standard_id, "ivo://ivoa.net/std/Security#basic") + self.assertEqual(interface.security_method[0].standard_id, AnyUrl("ivo://ivoa.net/std/Security#basic")) self.assertEqual(interface.test_querystring, "test") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_interface_model.to_xml() + test_xml = self.test_interface_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_interface_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_interface_xml, strip_text=True), ) @@ -448,28 +445,29 @@ class TestWebService(TestCase): """Test the VOResource WebService model.""" test_web_service_model = WebService( - wsdl_url=["https://example.edu/wsdl"], - access_url=[AccessURL(value="https://example.edu", use="full")], + wsdl_url=["https://example.edu/wsdl/"], + access_url=[AccessURL(value="https://example.edu/", use="full")], ) test_web_service_xml = ( - '' - "https://example.edu/wsdl" - "https://example.edu" - "" + '' + 'https://example.edu/' + "https://example.edu/wsdl/" + "" ) def test_read_from_xml(self): """Test reading from XML.""" web_service = WebService.from_xml(self.test_web_service_xml) - self.assertEqual(web_service.wsdl_url, "https://example.edu/wsdl") - self.assertEqual(web_service.access_url, "https://example.edu") + self.assertEqual(web_service.wsdl_url[0], AnyUrl("https://example.edu/wsdl/")) + self.assertEqual(web_service.access_url[0].value, AnyUrl("https://example.edu/")) + self.assertEqual(web_service.access_url[0].use, "full") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_web_service_model.to_xml() + test_xml = self.test_web_service_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_web_service_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_web_service_xml, strip_text=True), ) @@ -477,8 +475,8 @@ class TestResource(TestCase): """Test the VOResource Resource model.""" test_resource_model = Resource( - created=UTCDateTime("1996-03-11T19:00:00Z"), - updated=UTCDateTime("1996-03-11T19:00:00Z"), + created=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + updated=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), status="active", version="1.0", validation_level=[Validation(value=0, validated_by="https://example.edu")], @@ -511,74 +509,72 @@ class TestResource(TestCase): ) test_resource_xml = ( - '' - "1996-03-11T19:00:00Z" - "1996-03-11T19:00:00Z" - '0' - "Example Resource" - "example" - "https://example.edu" - "bibcode:2008ivoa.spec.0222P" - "" - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00Z' - "1.0" - "John Doe" - "" - "" - "Astronomy" - "Example description" - 'https://example.edu' - "https://example.edu" - "Education" - "General" - "" - "isPartOf" - 'Example Resource' - "" - "" - "" + '' + '0' + "Example Resource" + "example" + "https://example.edu/" + "bibcode:2008ivoa.spec.0222P" + "" + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" + "" ) def test_read_from_xml(self): """Test reading from XML.""" resource = Resource.from_xml(self.test_resource_xml) - self.assertEqual(resource.created, UTCDateTime("1996-03-11T19:00:00Z")) - self.assertEqual(resource.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(resource.created.isoformat(), UTCDateTime("1996-03-11T19:00:00.000Z")) + self.assertEqual(resource.updated.isoformat(), UTCDateTime("1996-03-11T19:00:00.000Z")) self.assertEqual(resource.status, "active") self.assertEqual(resource.version, "1.0") - self.assertEqual(resource.validation_level[0].value, 0) - self.assertEqual(resource.validation_level[0].validated_by, "https://example.edu") + self.assertEqual(resource.validation_level[0].value, ValidationLevel(0)) + self.assertEqual(resource.validation_level[0].validated_by, AnyUrl("https://example.edu")) self.assertEqual(resource.title, "Example Resource") self.assertEqual(resource.short_name, "example") - self.assertEqual(resource.identifier, "https://example.edu") - self.assertEqual(resource.alt_identifier, ["bibcode:2008ivoa.spec.0222P"]) + self.assertEqual(resource.identifier, AnyUrl("https://example.edu")) + self.assertEqual(resource.alt_identifier, [AnyUrl("bibcode:2008ivoa.spec.0222P")]) self.assertEqual(resource.curation.publisher.value, "STScI") - self.assertEqual(resource.curation.creator[0].name, "Doe, J.") + self.assertEqual(resource.curation.creator[0].name.value, "Doe, J.") self.assertEqual(resource.curation.contributor[0].value, "Example Resource") - self.assertEqual(resource.curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(resource.curation.date[0].value.isoformat(), "2021-01-01T00:00:00.000Z") self.assertEqual(resource.curation.date[0].role, "update") self.assertEqual(resource.curation.version, "1.0") - self.assertEqual(resource.curation.contact[0].name, "John Doe") - self.assertEqual(resource.content.subject, "Astronomy") + self.assertEqual(resource.curation.contact[0].name.value, "John Doe") + self.assertEqual(resource.content.subject, ["Astronomy"]) self.assertEqual(resource.content.description, "Example description") - self.assertEqual(resource.content.source[0].value, "https://example.edu") - self.assertEqual(resource.content.source[0].format, "bibcode") - self.assertEqual(resource.content.reference_url, "https://example.edu") - self.assertEqual(resource.content.type, "Education") - self.assertEqual(resource.content.content_level, "General") + self.assertEqual(resource.content.source.value, AnyUrl("https://example.edu")) + self.assertEqual(resource.content.source.format, "bibcode") + self.assertEqual(resource.content.reference_url, AnyUrl("https://example.edu")) + self.assertEqual(resource.content.type, ["Education"]) + self.assertEqual(resource.content.content_level, ["General"]) self.assertEqual(resource.content.relationship[0].relationship_type, "isPartOf") self.assertEqual(resource.content.relationship[0].related_resource[0].value, "Example Resource") self.assertEqual(resource.content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_resource_model.to_xml() + test_xml = self.test_resource_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_resource_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_resource_xml, strip_text=True), ) @@ -605,37 +601,35 @@ class TestOrganization(TestCase): ) test_organization_xml = ( - '' - "1996-03-11T19:00:00Z" - "1996-03-11T19:00:00Z" - "Example Organization" - "https://example.edu" - "" - "Example Publisher" - "John Doe" - "" - "" - "Astronomy" - "Example description" - "https://example.edu" - "" - 'Example Facility' - 'Example Instrument' - "" + '' + "Example Organization" + "https://example.edu/" + "" + "Example Publisher" + "John Doe" + "" + "" + "Astronomy" + "Example description" + "https://example.edu/" + "" + 'Example Facility' + 'Example Instrument' + "" ) def test_read_from_xml(self): """Test reading from XML.""" organization = Organisation.from_xml(self.test_organization_xml) self.assertEqual(organization.title, "Example Organization") - self.assertEqual(organization.identifier, "https://example.edu") + self.assertEqual(organization.identifier, AnyUrl("https://example.edu")) self.assertEqual(organization.curation.publisher.value, "Example Publisher") - self.assertEqual(organization.curation.contact[0].name, "John Doe") - self.assertEqual(organization.content.subject, "Astronomy") + self.assertEqual(organization.curation.contact[0].name.value, "John Doe") + self.assertEqual(organization.content.subject, ["Astronomy"]) self.assertEqual(organization.content.description, "Example description") - self.assertEqual(organization.content.reference_url, "https://example.edu") - self.assertEqual(organization.created, UTCDateTime("1996-03-11T19:00:00Z")) - self.assertEqual(organization.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(organization.content.reference_url, AnyUrl("https://example.edu")) + self.assertEqual(organization.created.isoformat(), UTCDateTime("1996-03-11T19:00:00.000Z")) + self.assertEqual(organization.updated.isoformat(), UTCDateTime("1996-03-11T19:00:00.000Z")) self.assertEqual(organization.status, "active") self.assertEqual(organization.version, "1.0") self.assertEqual(organization.facility[0].value, "Example Facility") @@ -645,10 +639,10 @@ def test_read_from_xml(self): def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_organization_model.to_xml() + test_xml = self.test_organization_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_organization_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_organization_xml, strip_text=True), ) @@ -665,29 +659,31 @@ class TestCapability(TestCase): ) test_capability_xml = ( - '' - '0' - "Example description" - '' - "" + '' + '0' + "Example description" + '' + 'https://example.edu/' + "" + "" ) def test_read_from_xml(self): """Test reading from XML.""" capability = Capability.from_xml(self.test_capability_xml) - self.assertEqual(capability.standard_id, "ivo://ivoa.net/std/TAP") - self.assertEqual(capability.validation_level[0].value, 0) - self.assertEqual(capability.validation_level[0].validated_by, "https://example.edu") + self.assertEqual(capability.standard_id, AnyUrl("ivo://ivoa.net/std/TAP")) + self.assertEqual(capability.validation_level[0].value, ValidationLevel(0)) + self.assertEqual(capability.validation_level[0].validated_by, AnyUrl("https://example.edu")) self.assertEqual(capability.description, "Example description") self.assertEqual(capability.interface[0].version, "1.0") self.assertEqual(capability.interface[0].role, "std") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_capability_model.to_xml() + test_xml = self.test_capability_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_capability_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_capability_xml, strip_text=True), ) @@ -695,18 +691,11 @@ class TestService(TestCase): """Test the VOResource Service model.""" test_service_model = Service( - created=UTCDateTime("1996-03-11T19:00:00Z"), - updated=UTCDateTime("1996-03-11T19:00:00Z"), + created=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + updated=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), status="active", - version="1.0", - capability=[Capability(standard_id="ivo://ivoa.net/std/TAP")], - interface=[ - Interface(version="1.0", role="std", access_url=[AccessURL(value="https://example.edu", use="full")]) - ], title="Example Service", - short_name="example", identifier="https://example.edu", - alt_identifier=["bibcode:2008ivoa.spec.0222P"], curation=Curation( publisher=ResourceName(value="STScI"), creator=[Creator(name=ResourceName(value="Doe, J."))], @@ -729,77 +718,70 @@ class TestService(TestCase): ) ], ), + rights=[Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/")], + capability=[Capability(standard_id="ivo://ivoa.net/std/TAP")], ) test_service_xml = ( - '' - "1996-03-11T19:00:00Z" - "1996-03-11T19:00:00Z" - '' - '' - "Example Service" - "example" - "https://example.edu" - "bibcode:2008ivoa.spec.0222P" - "" - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00Z' - "1.0" - "John Doe" - "" - "" - "Astronomy" - "Example description" - 'https://example.edu' - "https://example.edu" - "Education" - "General" - "" - "isPartOf" - 'Example Resource' - "" - "" - "" + '' + "Example Service" + "https://example.edu/" + "" + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" + "CC BY 4.0" + "" + "" ) def test_read_from_xml(self): """Test reading from XML.""" service = Service.from_xml(self.test_service_xml) - self.assertEqual(service.created, UTCDateTime("1996-03-11T19:00:00Z")) - self.assertEqual(service.updated, UTCDateTime("1996-03-11T19:00:00Z")) + self.assertEqual(service.created.isoformat(), "1996-03-11T19:00:00.000Z") + self.assertEqual(service.updated.isoformat(), "1996-03-11T19:00:00.000Z") self.assertEqual(service.status, "active") - self.assertEqual(service.version, "1.0") - self.assertEqual(service.capability[0].standard_id, "ivo://ivoa.net/std/TAP") - self.assertEqual(service.interface[0].version, "1.0") - self.assertEqual(service.interface[0].role, "std") + self.assertEqual(service.capability[0].standard_id, AnyUrl("ivo://ivoa.net/std/TAP")) self.assertEqual(service.title, "Example Service") - self.assertEqual(service.short_name, "example") - self.assertEqual(service.identifier, "https://example.edu") - self.assertEqual(service.alt_identifier, ["bibcode:2008ivoa.spec.0222P"]) + self.assertEqual(service.identifier, AnyUrl("https://example.edu")) self.assertEqual(service.curation.publisher.value, "STScI") - self.assertEqual(service.curation.creator[0].name, "Doe, J.") + self.assertEqual(service.curation.creator[0].name.value, "Doe, J.") self.assertEqual(service.curation.contributor[0].value, "Example Resource") - self.assertEqual(service.curation.date[0].value, "2021-01-01T00:00:00Z") + self.assertEqual(service.curation.date[0].value.isoformat(), "2021-01-01T00:00:00.000Z") self.assertEqual(service.curation.date[0].role, "update") self.assertEqual(service.curation.version, "1.0") - self.assertEqual(service.curation.contact[0].name, "John Doe") - self.assertEqual(service.content.subject, "Astronomy") + self.assertEqual(service.curation.contact[0].name.value, "John Doe") + self.assertEqual(service.content.subject, ["Astronomy"]) self.assertEqual(service.content.description, "Example description") - self.assertEqual(service.content.source[0].value, "https://example.edu") - self.assertEqual(service.content.source[0].format, "bibcode") - self.assertEqual(service.content.reference_url, "https://example.edu") - self.assertEqual(service.content.type, "Education") - self.assertEqual(service.content.content_level, "General") + self.assertEqual(service.content.source.value, AnyUrl("https://example.edu")) + self.assertEqual(service.content.source.format, "bibcode") + self.assertEqual(service.content.reference_url, AnyUrl("https://example.edu")) + self.assertEqual(service.content.type, ["Education"]) + self.assertEqual(service.content.content_level, ["General"]) self.assertEqual(service.content.relationship[0].relationship_type, "isPartOf") self.assertEqual(service.content.relationship[0].related_resource[0].value, "Example Resource") self.assertEqual(service.content.relationship[0].related_resource[0].ivo_id, "ivo://example.edu/resource") def test_write_to_xml(self): """Test writing to XML.""" - xml = self.test_service_model.to_xml() + test_xml = self.test_service_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(xml, strip_text=True, strip_comments=True), - canonicalize(etree.fromstring(self.test_service_xml), strip_text=True, strip_comments=True), + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_service_xml, strip_text=True), ) From 73bebea4c8095a7320982b72019af713c4070053 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 13:56:07 -0400 Subject: [PATCH 13/41] fix validators, docstrings --- vo_models/voresource/models.py | 42 +++++++++------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 439c957..1bada7d 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -19,7 +19,7 @@ } -class Validation(BaseXmlModel, tag="validation", ns="vr", nsmap=NSMAP): +class Validation(BaseXmlModel, ns="vr", nsmap=NSMAP): """A validation stamp combining a validation level and the ID of the validator. Parameters: @@ -33,6 +33,14 @@ class Validation(BaseXmlModel, tag="validation", ns="vr", nsmap=NSMAP): name="validatedBy", ) + @field_validator("value", mode="before") + def _validate_value(cls, values): + """Ensure value is a ValidationLevel instance""" + if isinstance(values, str): + if values.isdigit(): + return ValidationLevel(int(values)) + return values + class ResourceName(BaseXmlModel, ns="vr", nsmap=NSMAP): """The name of a potentially registered resource. @@ -422,28 +430,20 @@ def _validate_timestamps(cls, values): """Ensure that the created and updated timestamps are not in the future""" if values > datetime.datetime.now(datetime.timezone.utc): raise ValueError(f"{values} timestamp must not be in the future") + return values @field_validator("short_name") def _validate_short_name(cls, values): """Ensure that the short name is no more than 16 characters""" if values and len(values) > 16: raise ValueError("Short name must be no more than 16 characters") + return values class Organisation(Resource, ns="vr", nsmap=NSMAP): """A named group of one or more persons brought together to pursue participation in VO applications. Parameters: - created: - (attr) - The UTC date and time this resource metadata description was created. - updated: - (attr) - The UTC date this resource metadata description was last updated. - status: - (attr) - a tag indicating whether this resource is believed to be still actively maintained. - version: - (attr) - The VOResource XML schema version against which this instance was written. - Implementors should set this to the value of the version attribute of their schema's root (xs:schema) - element. Clients may assume version 1.0 if this attribute is missing. facility: (element) - The observatory or facility used to collect the data contained or managed by this resource. instrument: @@ -451,11 +451,6 @@ class Organisation(Resource, ns="vr", nsmap=NSMAP): """ - created: UTCTimestamp = attr(name="created") - updated: UTCTimestamp = attr(name="updated") - status: str = attr(name="status") - version: Optional[str] = attr(name="version", default=None) - facility: Optional[list[ResourceName]] = element(tag="facility", default_factory=list) instrument: Optional[list[ResourceName]] = element(tag="instrument", default_factory=list) @@ -496,16 +491,6 @@ class Service(Resource, ns="vr", nsmap=NSMAP): """A resource that can be invoked by a client to perform some action on its behalf. Parameters: - created: - (attr) - The UTC date and time this resource metadata description was created. - updated: - (attr) - The UTC date this resource metadata description was last updated. - status: (str): - (attr) - A tag indicating whether this resource is believed to be still actively maintained. - version: - (attr) - The VOResource XML schema version against which this instance was written. - Implementors should set this to the value of the version attribute of their schema's root (xs:schema) - element. Clients may assume version 1.0 if this attribute is missing. rights: (element) - Information about rights held in and over the resource. Mainly for compatibility with DataCite, this elementis repeatable. Resource record authors are advised @@ -519,10 +504,5 @@ class Service(Resource, ns="vr", nsmap=NSMAP): functionality it provides. """ - created: UTCTimestamp = attr(name="created") - updated: UTCTimestamp = attr(name="updated") - status: str = attr(name="status") - version: Optional[str] = attr(name="version", default=None) - rights: Optional[list[Rights]] = element(tag="rights", default_factory=list) capability: Optional[list[Capability]] = element(tag="capability", default_factory=list) From 655a23239f8c49c8df50b418a80a54cbf3ac2f10 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 14:27:18 -0400 Subject: [PATCH 14/41] tests passing, fixups, remove tags --- tests/tapregext/tapregext_models_test.py | 130 ++++++++++------------- vo_models/tapregext/models.py | 30 +++--- 2 files changed, 71 insertions(+), 89 deletions(-) diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py index a835721..0c181b6 100644 --- a/tests/tapregext/tapregext_models_test.py +++ b/tests/tapregext/tapregext_models_test.py @@ -33,7 +33,7 @@ class TestVersion(TestCase): """Tests the Version model.""" test_version_model = Version(value="1.0", ivo_id="ivo://ivoa.net/std/TAP") - test_version_xml = f'1.0' + test_version_xml = f'1.0' def test_read_from_xml(self): """Test reading a Version element from XML.""" @@ -43,11 +43,11 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a Version element to XML.""" - version_xml = self.test_version_model.to_xml() - self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_version_xml))), - canonicalize( - etree.tostring(etree.fromstring(version_xml)), + test_xml = self.test_version_model.to_xml(encoding=str, skip_empty=True) + ( + self.assertEqual( + canonicalize(test_xml), + canonicalize(self.test_version_xml), ), ) @@ -56,7 +56,7 @@ class TestLanguageFeature(TestCase): """Tests the LanguageFeature model.""" test_language_feature_model = LanguageFeature(form="Formal notation", description="A description") - test_language_feature_xml = f"
Formal notation
A description
" + test_language_feature_xml = f"
Formal notation
A description
" def test_read_from_xml(self): """Test reading a LanguageFeature element from XML.""" @@ -66,12 +66,10 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a LanguageFeature element to XML.""" - language_feature_xml = self.test_language_feature_model.to_xml() + test_xml = self.test_language_feature_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_language_feature_xml))), - canonicalize( - etree.tostring(etree.fromstring(language_feature_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_language_feature_xml), ) @@ -83,10 +81,10 @@ class TestOutputFormat(TestCase): alias=["VOTABLE"], ) test_output_format_xml = ( - f"" + f"" "application/x-votable+xml" "VOTABLE" - "" + "" ) def test_read_from_xml(self): @@ -97,7 +95,7 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write an OutputFormat element to XML.""" - output_format_xml = self.test_output_format_model.to_xml() + output_format_xml = self.test_output_format_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( canonicalize(etree.tostring(etree.fromstring(self.test_output_format_xml))), canonicalize( @@ -112,7 +110,7 @@ class TestUploadMethod(TestCase): test_upload_method_model = UploadMethod( ivo_id="ivo://ivoa.net/std/TAP", ) - test_upload_method_xml = f'' + test_upload_method_xml = f'' def test_read_from_xml(self): """Test reading an UploadMethod element from XML.""" @@ -121,21 +119,19 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write an UploadMethod element to XML.""" - upload_method_xml = self.test_upload_method_model.to_xml() + test_xml = self.test_upload_method_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_upload_method_xml))), - canonicalize( - etree.tostring(etree.fromstring(upload_method_xml)), - ), + canonicalize(self.test_upload_method_xml), + canonicalize(test_xml), ) -class TestTimeLimitsElement(TestCase): +class TestTimeLimits(TestCase): """Tests the TimeLimits model.""" test_time_limits_model = TimeLimits(default=10, hard=100) test_time_limits_xml = ( - f"10100" + f"10100" ) def test_read_from_xml(self): @@ -146,16 +142,14 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a TimeLimits element to XML.""" - time_limits_xml = self.test_time_limits_model.to_xml() + test_xml = self.test_time_limits_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_time_limits_xml))), - canonicalize( - etree.tostring(etree.fromstring(time_limits_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_time_limits_xml), ) -class TestDataLimitsElement(TestCase): +class TestDataLimits(TestCase): """Tests the DataLimits model.""" test_data_limits_model = DataLimits( @@ -163,10 +157,10 @@ class TestDataLimitsElement(TestCase): hard={"value": 100, "unit": "row"}, ) test_data_limits_xml = ( - f"" + f"" '10' '100' - "" + "" ) def test_read_from_xml(self): @@ -177,20 +171,18 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a DataLimits element to XML.""" - data_limits_xml = self.test_data_limits_model.to_xml() + test_xml = self.test_data_limits_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_data_limits_xml))), - canonicalize( - etree.tostring(etree.fromstring(data_limits_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_data_limits_xml), ) -class TestDataLimitElement(TestCase): +class TestDataLimit(TestCase): """Tests the DataLimit model.""" test_data_limit_model = DataLimit(value=10, unit="byte") - test_data_limit_xml = f'10' + test_data_limit_xml = f'10' def test_read_from_xml(self): """Test reading a DataLimit element from XML.""" @@ -200,12 +192,10 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a DataLimit element to XML.""" - data_limit_xml = self.test_data_limit_model.to_xml() + test_xml = self.test_data_limit_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_data_limit_xml))), - canonicalize( - etree.tostring(etree.fromstring(data_limit_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_data_limit_xml), ) @@ -220,10 +210,10 @@ class TestLanguageFeatureList(TestCase): type="adql-some-feature", ) test_language_feature_list_xml = ( - f'' + f'' "
Formal notation
A description
" "
Informal notation
Another description
" - "
" + "" ) def test_read_from_xml(self): @@ -236,12 +226,10 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a LanguageFeatureList element to XML.""" - language_feature_list_xml = self.test_language_feature_list_model.to_xml() + test_xml = self.test_language_feature_list_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_language_feature_list_xml))), - canonicalize( - etree.tostring(etree.fromstring(language_feature_list_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_language_feature_list_xml), ) @@ -263,7 +251,7 @@ class TestLanguage(TestCase): ], ) test_language_xml = ( - f"" + f"" "ADQL" "2.0" "Astronomical Data Query Language" @@ -271,7 +259,7 @@ class TestLanguage(TestCase): "
Formal notation
A description
" "
Informal notation
Another description
" "" - "
" + "
" ) def test_read_from_xml(self): @@ -287,12 +275,10 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a Language element to XML.""" - language_xml = self.test_language_model.to_xml() + test_xml = self.test_language_model.to_xml(encoding=str, skip_empty=True) self.assertEqual( - canonicalize(etree.tostring(etree.fromstring(self.test_language_xml))), - canonicalize( - etree.tostring(etree.fromstring(language_xml)), - ), + canonicalize(test_xml), + canonicalize(self.test_language_xml), ) @@ -304,7 +290,7 @@ class TestTableAccess(TestCase): language=[ Language( name="ADQL", - version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL")], + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL-2.0")], description="Astronomical Data Query Language", language_features=[ LanguageFeatureList( @@ -323,12 +309,6 @@ class TestTableAccess(TestCase): alias=["VOTABLE"], ) ], - upload_method=[ - UploadMethod( - value="HTTP", - ivo_id="ivo://ivoa.net/std/TAP", - ) - ], retention_period=TimeLimits(default=10, hard=100), output_limit=DataLimits( default={"value": 10, "unit": "row"}, @@ -336,19 +316,18 @@ class TestTableAccess(TestCase): ), ) test_table_access_xml = ( - f"" + f'' "VOTable" "" "ADQL" - "2.0" + '2.0' "Astronomical Data Query Language" - "" - "
Formal notation
A description
" - "
Informal notation
Another description
" + '' + "
Formal notation
A description
" + "
Informal notation
Another description
" "
" "
" - "application/x-votable+xml;content=datalink" - "" + "application/x-votable+xmlVOTABLE" "10100" "" '10' @@ -371,8 +350,15 @@ def test_read_from_xml(self): self.assertEqual(table_access.language[0].language_features[0].feature[1].description, "Another description") self.assertEqual(table_access.output_format[0].mime, "application/x-votable+xml") self.assertEqual(table_access.output_format[0].alias[0], "VOTABLE") - self.assertEqual(table_access.upload_method[0].ivo_id, "ivo://ivoa.net/std/TAP") self.assertEqual(table_access.retention_period.default, 10) self.assertEqual(table_access.retention_period.hard, 100) self.assertEqual(table_access.output_limit.default.value, 10) self.assertEqual(table_access.output_limit.hard.value, 100) + + def test_write_xml(self): + """Test we can write a TableAccess element to XML.""" + test_xml = self.test_table_access_model.to_xml(encoding=str, skip_empty=True) + self.assertEqual( + canonicalize(test_xml), + canonicalize(self.test_table_access_xml), + ) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 97590ef..3097d51 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -2,7 +2,7 @@ from typing import Literal, Optional -from pydantic_xml import BaseXmlModel, attr, element +from pydantic_xml import BaseXmlModel, attr, computed_attr, element from vo_models.voresource.models import Capability, Interface, Validation from vo_models.voresource.types import IdentifierURI @@ -18,14 +18,10 @@ class TAPCapRestriction(Capability, nsmap=NSMAP): """An abstract capability that fixes the standardID to the IVOA ID for the TAP standard.""" - validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) - description: Optional[str] = element(tag="description", default=None) - interface: Optional[list[Interface]] = element(tag="interface", default_factory=list) - - standard_id: IdentifierURI = attr(name="standardID", default="ivo://ivoa.net/std/TAP") + standard_id: Literal["ivo://ivoa.net/std/TAP"] = attr(name="standardID", default="ivo://ivoa.net/std/TAP") -class DataModelType(BaseXmlModel, tag="dataModel", nsmap=NSMAP): +class DataModelType(BaseXmlModel, nsmap=NSMAP): """IVOA defined data model, identified by an IVORN. Parameters: @@ -39,7 +35,7 @@ class DataModelType(BaseXmlModel, tag="dataModel", nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class Version(BaseXmlModel, tag="version", nsmap=NSMAP): +class Version(BaseXmlModel, nsmap=NSMAP): """One version of the language supported by the service. Parameters: @@ -53,7 +49,7 @@ class Version(BaseXmlModel, tag="version", nsmap=NSMAP): ivo_id: Optional[str] = attr(name="ivo-id", default=None) -class LanguageFeature(BaseXmlModel, tag="languageFeature", nsmap=NSMAP): +class LanguageFeature(BaseXmlModel, nsmap=NSMAP): """A non-standard or non-mandatory feature implemented by the language. Parameters: @@ -67,7 +63,7 @@ class LanguageFeature(BaseXmlModel, tag="languageFeature", nsmap=NSMAP): description: Optional[str] = element(tag="description", default=None) -class OutputFormat(BaseXmlModel, tag="outputFormat", nsmap=NSMAP): +class OutputFormat(BaseXmlModel, nsmap=NSMAP): """An output format supported by the service. Parameters: @@ -81,7 +77,7 @@ class OutputFormat(BaseXmlModel, tag="outputFormat", nsmap=NSMAP): alias: Optional[list[str]] = element(tag="alias", default_factory=list) -class UploadMethod(BaseXmlModel, tag="uploadMethod", nsmap=NSMAP): +class UploadMethod(BaseXmlModel, nsmap=NSMAP): """An upload method as defined by IVOA. Parameters: @@ -92,7 +88,7 @@ class UploadMethod(BaseXmlModel, tag="uploadMethod", nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class TimeLimits(BaseXmlModel, tag="timeLimits", nsmap=NSMAP): +class TimeLimits(BaseXmlModel, nsmap=NSMAP): """Time-valued limits, all values given in seconds. Parameters: @@ -106,7 +102,7 @@ class TimeLimits(BaseXmlModel, tag="timeLimits", nsmap=NSMAP): hard: Optional[int] = element(tag="hard", default=None) -class DataLimit(BaseXmlModel, tag="dataLimit", nsmap=NSMAP): +class DataLimit(BaseXmlModel, nsmap=NSMAP): """A limit on some data size, either in rows or in bytes. Parameters: @@ -120,7 +116,7 @@ class DataLimit(BaseXmlModel, tag="dataLimit", nsmap=NSMAP): unit: Literal["byte", "row"] = attr(name="unit") -class DataLimits(BaseXmlModel, tag="dataLimits", nsmap=NSMAP): +class DataLimits(BaseXmlModel, nsmap=NSMAP): """Limits on data sizes, given in rows or bytes. Parameters: @@ -134,7 +130,7 @@ class DataLimits(BaseXmlModel, tag="dataLimits", nsmap=NSMAP): hard: Optional[DataLimit] = element(tag="hard", default=None) -class LanguageFeatureList(BaseXmlModel, tag="languageFeatures", nsmap=NSMAP): +class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): """An enumeration of non-standard or non-mandatory features of a specific type implemented by the language. Parameters: @@ -148,7 +144,7 @@ class LanguageFeatureList(BaseXmlModel, tag="languageFeatures", nsmap=NSMAP): type: str = attr(name="type") -class Language(BaseXmlModel, tag="language", nsmap=NSMAP): +class Language(BaseXmlModel, nsmap=NSMAP): """A query language supported by the service. Parameters: @@ -168,7 +164,7 @@ class Language(BaseXmlModel, tag="language", nsmap=NSMAP): language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) -class TableAccess(TAPCapRestriction, tag="capability", ns="vr", nsmap=NSMAP): +class TableAccess(TAPCapRestriction, tag="capability", ns="", nsmap=NSMAP): """The capabilities of a TAP server. Parameters: From c2fe8f7658b7f1d3aa627152768c95a64f605bba Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 14:30:55 -0400 Subject: [PATCH 15/41] clean imports --- vo_models/tapregext/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 3097d51..2398aa0 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -2,10 +2,9 @@ from typing import Literal, Optional -from pydantic_xml import BaseXmlModel, attr, computed_attr, element +from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource.models import Capability, Interface, Validation -from vo_models.voresource.types import IdentifierURI +from vo_models.voresource.models import Capability NSMAP = { "xs": "http://www.w3.org/2001/XMLSchema", From 0c7091fc82e619df7969817e41b065096251368a Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 17:16:47 -0400 Subject: [PATCH 16/41] add doc snippets --- examples/snippets/tapregext/tapregext.py | 78 ++++++++ examples/snippets/voresource/voresource.py | 205 +++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 examples/snippets/tapregext/tapregext.py create mode 100644 examples/snippets/voresource/voresource.py diff --git a/examples/snippets/tapregext/tapregext.py b/examples/snippets/tapregext/tapregext.py new file mode 100644 index 0000000..2544371 --- /dev/null +++ b/examples/snippets/tapregext/tapregext.py @@ -0,0 +1,78 @@ +from vo_models.tapregext.models import ( + DataLimits, + DataModelType, + Language, + LanguageFeature, + LanguageFeatureList, + OutputFormat, + TableAccess, + TimeLimits, + Version, +) + +# [TableAccess-model-start] +table_access_model = TableAccess( + data_model=[DataModelType(value="VOTable", ivo_id="ivo://ivoa.net/std/VOTable")], + language=[ + Language( + name="ADQL", + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL-2.0")], + description="Astronomical Data Query Language", + language_features=[ + LanguageFeatureList( + feature=[ + LanguageFeature(form="Formal notation", description="A description"), + LanguageFeature(form="Informal notation", description="Another description"), + ], + type="adql-some-feature", + ) + ], + ) + ], + output_format=[ + OutputFormat( + mime="application/x-votable+xml", + alias=["VOTABLE"], + ) + ], + retention_period=TimeLimits(default=10, hard=100), + output_limit=DataLimits( + default={"value": 10, "unit": "row"}, + hard={"value": 100, "unit": "row"}, + ), +) +# [TableAccess-model-end] + +# [TableAccess-xml-start] +table_access_xml = """ + + VOTable + + ADQL + 2.0 + Astronomical Data Query Language + + +
Formal notation
+ A description +
+ +
Informal notation
+ Another description +
+
+
+ + application/x-votable+xml + VOTABLE + + + 10 + 100 + + + 10 + 100 + +
+""" # [TableAccess-xml-end] diff --git a/examples/snippets/voresource/voresource.py b/examples/snippets/voresource/voresource.py new file mode 100644 index 0000000..e4d9213 --- /dev/null +++ b/examples/snippets/voresource/voresource.py @@ -0,0 +1,205 @@ +from datetime import timezone as tz + +from vo_models.voresource.models import ( + AccessURL, + Capability, + Contact, + Content, + Creator, + Curation, + Date, + Interface, + MirrorURL, + Relationship, + Resource, + ResourceName, + Rights, + SecurityMethod, + Service, + Source, + Validation, +) +from vo_models.voresource.types import UTCTimestamp + +# [Resource-model-start] +resource = Resource( + created=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + updated=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + status="active", + version="1.0", + validation_level=[Validation(value=0, validated_by="https://example.edu")], + title="Example Resource", + short_name="example", + identifier="https://example.edu", + alt_identifier=["bibcode:2008ivoa.spec.0222P"], + curation=Curation( + publisher=ResourceName(value="STScI"), + creator=[Creator(name=ResourceName(value="Doe, J."))], + contributor=[ResourceName(value="Example Resource")], + date=[Date(value="2021-01-01T00:00:00Z", role="update")], + version="1.0", + contact=[Contact(name=ResourceName(value="John Doe"))], + ), + content=Content( + subject=["Astronomy"], + description="Example description", + source=Source(value="https://example.edu", format="bibcode"), + reference_url="https://example.edu", + type=["Education"], + content_level=["General"], + relationship=[ + Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + ], + ), +) +# [Resource-model-end] + +# [Resource-xml-start] +resource_xml = """ + + 0 + Example Resource + example + https://example.edu/ + bibcode:2008ivoa.spec.0222P + + STScI + + Doe, J. + + Example Resource + 2021-01-01T00:00:00.000Z + 1.0 + + John Doe + + + + Astronomy + Example description + https://example.edu/ + https://example.edu/ + Education + General + + isPartOf + Example Resource + + + +""" # [Resource-xml-end] + +# [Service-model-start] +service = Service( + created=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + updated=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), + status="active", + title="Example Service", + identifier="https://example.edu", + curation=Curation( + publisher=ResourceName(value="STScI"), + creator=[Creator(name=ResourceName(value="Doe, J."))], + contributor=[ResourceName(value="Example Resource")], + date=[Date(value="2021-01-01T00:00:00Z", role="update")], + version="1.0", + contact=[Contact(name=ResourceName(value="John Doe"))], + ), + content=Content( + subject=["Astronomy"], + description="Example description", + source=Source(value="https://example.edu", format="bibcode"), + reference_url="https://example.edu", + type=["Education"], + content_level=["General"], + relationship=[ + Relationship( + relationship_type="isPartOf", + related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], + ) + ], + ), + rights=[Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/")], + capability=[Capability(standard_id="ivo://ivoa.net/std/TAP")], +) +# [Service-model-end] + +# [Service-xml-start] +service_xml = """ + + Example Service + https://example.edu/ + + STScI + + Doe, J. + + Example Resource + 2021-01-01T00:00:00.000Z + 1.0 + + John Doe + + + + Astronomy + Example description + https://example.edu/ + https://example.edu/ + Education + General + + isPartOf + Example Resource + + + CC BY 4.0 + + +""" # [Service-xml-end] + +# [Capability-model-start] +capability_model = Capability( + standard_id="ivo://ivoa.net/std/TAP", + validation_level=[Validation(value=0, validated_by="https://example.edu")], + description="Example description", + interface=[Interface(version="1.0", role="std", access_url=[AccessURL(value="https://example.edu", use="full")])], +) +# [Capability-model-end] + +# [Capability-xml-start] +capability_xml = """ + + 0 + Example description + + https://example.edu/ + + +""" # [Capability-xml-end] + +# [Interface-model-start] +interface_model = Interface( + version="1.0", + role="std", + access_url=[AccessURL(value="https://example.edu", use="full")], + mirror_url=[MirrorURL(value="https://example.edu", title="Mirror")], + security_method=[SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic")], + test_querystring="test", +) +# [Interface-model-end] + +# [Interface-xml-start] +interface_xml = """ + + https://example.edu/ + https://example.edu/ + + test + +""" # [Interface-xml-end] From d366939f16359793af95313c8f36c9e8caed0a78 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 17:17:05 -0400 Subject: [PATCH 17/41] cleanup documentation --- vo_models/voresource/models.py | 93 ++++++++-------------------------- vo_models/voresource/types.py | 2 +- 2 files changed, 21 insertions(+), 74 deletions(-) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 1bada7d..f669190 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -60,11 +60,6 @@ class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): """A string indicating what the date refers to. The value of role should be taken from the vocabulary maintained at http://www.ivoa.net/rdf/voresource/date_role. - This includes the traditional and deprecated strings “creation”, indicating the date that the resource itself was - created, and “update”, indicating when the resource was updated last, and the default value, “representative”, - meaning the date is a rough representation of the time coverage of the resource. The preferred terms from that - vocabulary are the DataCite Metadata terms. It is expected that the vocabulary will be kept synchronous with the - corresponding list of terms in the DataCite Metadata schema. Parameters: value: The date and time of the event. @@ -80,10 +75,10 @@ class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): class Source(BaseXmlModel, ns="vr", nsmap=NSMAP): - """ + """A bibliographic reference from which the present resource is derived or extracted. Parameters: - value: A bibliographic reference from which the present resource is derived or extracted. + value: The bibliographic reference. format: (attr) - The reference format. Recognized values include "bibcode", referring to a standard astronomical bibcode @@ -118,10 +113,7 @@ class AccessURL(BaseXmlModel, ns="vr", nsmap=NSMAP): value: The URL (or base URL) that a client uses to access the service. use: (attr) - A flag indicating whether this should be interpreted as a base URL, a full URL, or a URL to a - directory that will produce a listing of files. Allowed values are: - "full" - Assume a full URL--that is, one that can be invoked directly without alteration. - "base" - Assume a base URL--that is, one requiring an extra portion to be appended before being invoked. - "dir" - Assume URL points to a directory that will return a listing of files. + directory that will produce a listing of files. """ value: networks.AnyUrl @@ -151,20 +143,18 @@ class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): (attr) - An IVOA identifier for the contact (typically when it is an organization). name: (element) - The name or title of the contact person. - This can be a person's name, e.g. “John P. Jones” or a group, “Archive Support Team”. + This can be a person's name, e.g. “John P. Jones” or a group, “Archive Support Team”. address: - (element) - The contact mailing address - All components of the mailing address are given in one string, e.g. - “3700 San Martin Drive, Baltimore, MD 21218 USA”. + (element) - The contact mailing address. + All components of the mailing address are given in one string, e.g. “3700 San Martin Drive, Baltimore, MD 21218 USA”. email: - (element) - The contact email address + (element) - The contact email address. telephone: - (element) - The contact telephone number - Complete international dialing codes should be given, e.g. - “+1-410-338-1234”. + (element) - The contact telephone number. + Complete international dialing codes should be given, e.g. “+1-410-338-1234”. alt_identifier: - (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. Always use a URI form - including a scheme here. + (element) - A reference to this entitiy in a non-IVOA identifier scheme, e.g., orcid. + Always use a URI form including a scheme here. """ ivo_id: Optional[IdentifierURI] = attr(name="ivo_id", default=None) @@ -222,8 +212,7 @@ class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: relationship_type: (element) - The named type of relationship - The value of relationshipType should be taken from the vocabulary at - http://www.ivoa.net/rdf/voresource/relationship_type. + The value of relationshipType should be taken from the vocabulary at http://www.ivoa.net/rdf/voresource/relationship_type. related_resource: (element) - the name of resource that this resource is related to. """ @@ -241,8 +230,6 @@ class SecurityMethod(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: standard_id: (attr) - A URI identifier for a standard security mechanism. - This provides a unique way to refer to a security specification standard. The use of an IVOA identifier - here implies that a VOResource description of the standard is registered and accessible. """ standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) @@ -257,13 +244,10 @@ class Curation(BaseXmlModel, ns="vr", nsmap=NSMAP): creator: (element) - The entity/ies (e.g. person(s) or organisation) primarily responsible for creating the content or constitution of the resource. - This is the equivalent of the author of a publication. contributor: (element) - Entity responsible for contributions to the content of the resource date: (element) - Date associated with an event in the life cycle of the resource. - This will typically be associated with the creation or availability (i.e., most recent release or - version) of the resource. Use the role attribute to clarify. version: (element) - Label associated with creation or availablilty of a version of a resource. contact: @@ -284,23 +268,19 @@ class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: subject: (element) - A topic, object type, or other descriptive keywords about the resource. - Terms for Subject should be drawn from the Unified Astronomy Thesaurus (http://astrothesaurus.org). + Terms for Subject should be drawn from the Unified Astronomy Thesaurus (http://astrothesaurus.org). description: - (element) - An account of the nature of the resource - The description may include but is not limited to an abstract, table of contents, reference to a - graphical representation of content or a free-text account of the content. + (element) - An account of the nature of the resource. source: (element) - A bibliographic reference from which the present resource is derived or extracted. - This is intended to point to an article in the published literature. An ADS Bibcode is recommended as a - value when available. reference_url: (element) - URL pointing to a human-readable document describing this resource. type: - (element) - Nature or genre of the content of the resource. Values for type should be taken from the - controlled vocabulary http://www.ivoa.net/rdf/voresource/content_type + (element) - Nature or genre of the content of the resource. + Values for type should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_type content_level: - (element) - Description of the content level or intended audience. Values for contentLevel should be taken - from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_level. + (element) - Description of the content level or intended audience. + Values for contentLevel should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_level. relationship: (element) - a description of a relationship to another resource. """ @@ -324,18 +304,12 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: version: (attr) - The version of a standard interface specification that this interface complies with. - Most VO standards indicate the version in the standardID attribute of the capability. For these standards, - the version attribute should not be used. role: (attr) - A tag name that identifies the role the interface plays in the particular capability. - If the value is equal to 'std' or begins with 'std:', then the interface refers to a standard - interface defined by the standard referred to by the capability's standardID attribute. access_url: (element) - The URL (or base URL) that a client uses to access the service. - How this URL is to be interpreted and used depends on the specific Interface subclass mirror_url: (element) - A (base) URL of a mirror of this interface. - As with accessURL, how this URL is to be interpreted and used depends on the specific Interface subclass security_method: (element) - The mechanism the client must employ to authenticate to the service. test_querystring: @@ -362,9 +336,7 @@ class WebService(Interface, ns="vr", nsmap=NSMAP): Parameters: wsdl_url: - (element) - The location of the WSDL that describes this Web Service. If not provided, the location is - assumed to be the accessURL with "?wsdl" appended. - Multiple occurrences should represent mirror copies of the same WSDL file. + (element) - The location of the WSDL that describes this Web Service. """ wsdl_url: Optional[list[networks.AnyUrl]] = element(tag="wsdlURL", default_factory=list) @@ -377,21 +349,12 @@ class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: created: (attr) - The UTC date and time this resource metadata description was created. - This timestamp must not be in the future. This time is not required to be accurate; it should be at - least accurate to the day. Any non-significant time fields should be set to zero. updated: (attr) - The UTC date this resource metadata description was last updated. - This timestamp must not be in the future. This time is not required to be accurate; it should be at - least accurate to the day. Any non-significant time fields should be set to zero. status: (attr) - A tag indicating whether this resource is believed to be still actively maintained. - "active" - Resource is believed to be currently maintained, and its description is up to date (default) - "inactive" - Resource is apparently not being maintained at the present - "deleted" - Resource publisher has explicitly deleted the resource. version: (attr) - The VOResource XML schema version against which this instance was written. - Implementors should set this to the value of the version attribute of their schema's root (xs:schema) - element. Clients may assume version 1.0 if this attribute is missing. validation_level: (element) - A numeric grade describing the quality of the resource description, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research @@ -404,8 +367,7 @@ class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): identifier: (element) - Unambiguous reference to the resource conforming to the IVOA standard for identifiers alt_identifier: - (element) - A reference to this resource in a non-IVOA identifier scheme, e.g., DOI or bibcode. Always use - the an URI scheme here, e.g., bibcode:2008ivoa.spec.0222P. + (element) - A reference to this resource in a non-IVOA identifier scheme, e.g., DOI or bibcode. curation: (element) - Information regarding the general curation of the resource content: @@ -462,22 +424,14 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: standard_id: (attr) - A URI identifier for a standard service. - This provides a unique way to refer to a service specification standard, such as a Simple Image Access - service. The use of an IVOA identifier here implies that a VOResource description of the standard is - registered and accessible. validation_level: (element) - A numeric grade describing the quality of the capability description and interface, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research study. - See ValidationLevel for an explanation of the allowed levels. description: (element) - A human-readable description of what this capability provides as part of the over-all service. - Use of this optional element is especially encouraged when this capability is non-standard and is one - of several capabilities listed. interface: (element) - A description of how to call the service to access this capability. - Since the Interface type is abstract, one must describe the interface using a subclass of Interface, - denoting it via xsi:type. """ standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) @@ -493,15 +447,8 @@ class Service(Resource, ns="vr", nsmap=NSMAP): Parameters: rights: (element) - Information about rights held in and over the resource. - Mainly for compatibility with DataCite, this elementis repeatable. Resource record authors are advised - that within the Virtual Observatory clients will typically only display and/or use the rights element - occurring first and ignore later elements. capability: (element) - A description of a general capability of the service and how to use it. - This describes a general function of the service, usually in terms of a standard service protocol - (e.g. SIA), but not necessarily so. - A service can have many capabilities associated with it, each reflecting different aspects of the - functionality it provides. """ rights: Optional[list[Rights]] = element(tag="rights", default_factory=list) diff --git a/vo_models/voresource/types.py b/vo_models/voresource/types.py index f65a867..680b5b7 100644 --- a/vo_models/voresource/types.py +++ b/vo_models/voresource/types.py @@ -3,10 +3,10 @@ import re from datetime import datetime from enum import Enum +from typing import Annotated from pydantic import Field, GetCoreSchemaHandler from pydantic_core import CoreSchema, core_schema -from typing_extensions import Annotated # pylint: disable=too-few-public-methods From f899e31688065515d67e5a2a16858a114b0b5f51 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 17:17:27 -0400 Subject: [PATCH 18/41] bump version, update readme --- README.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 73983e4..77337fa 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ The following IVOA protocols are currently supported: - TableParam - TableSchema - TableSet +- **VOResource version 1.1** +- **TAPRegExt version 1.0** You can read more about using these models in our documentation: https://vo-models.readthedocs.io/ diff --git a/pyproject.toml b/pyproject.toml index b9bbae1..e3fc39b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "vo-models" -version = "0.3.1" +version = "0.4.0" authors = [ {name = "Joshua Fraustro", email="jfraustro@stsci.edu"}, {name = "MAST Archive Developers", email="archive@stsci.edu"} From c60c5efa2cdf630b60d3f5898f042be3cedbd8a3 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 25 Sep 2024 17:17:51 -0400 Subject: [PATCH 19/41] add voresource/tapregext docs --- docs/source/pages/api/index.rst | 3 +- docs/source/pages/api/tapregext_api.rst | 12 +++ docs/source/pages/api/voresource_api.rst | 8 ++ docs/source/pages/protocols/index.rst | 4 +- docs/source/pages/protocols/tapregext.rst | 36 +++++++ docs/source/pages/protocols/voresource.rst | 105 +++++++++++++++++++++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 docs/source/pages/api/tapregext_api.rst create mode 100644 docs/source/pages/protocols/tapregext.rst create mode 100644 docs/source/pages/protocols/voresource.rst diff --git a/docs/source/pages/api/index.rst b/docs/source/pages/api/index.rst index 1d8493b..f400991 100644 --- a/docs/source/pages/api/index.rst +++ b/docs/source/pages/api/index.rst @@ -11,4 +11,5 @@ This section contains documentation on the package's modules and classes. uws_api vosi_api voresource_api - vodataservice_api \ No newline at end of file + vodataservice_api + tapregext_api \ No newline at end of file diff --git a/docs/source/pages/api/tapregext_api.rst b/docs/source/pages/api/tapregext_api.rst new file mode 100644 index 0000000..703574f --- /dev/null +++ b/docs/source/pages/api/tapregext_api.rst @@ -0,0 +1,12 @@ +.. _tapregext_api: + +TAPRegExt API +-------------- + +Models +^^^^^^ + +.. automodule:: vo_models.tapregext.models + :members: + :no-inherited-members: + :exclude-members: model_config, model_fields \ No newline at end of file diff --git a/docs/source/pages/api/voresource_api.rst b/docs/source/pages/api/voresource_api.rst index 400e449..56dc1e9 100644 --- a/docs/source/pages/api/voresource_api.rst +++ b/docs/source/pages/api/voresource_api.rst @@ -3,6 +3,14 @@ VOResource API -------------- +Models +^^^^^^ + +.. automodule:: vo_models.voresource.models + :members: + :no-inherited-members: + :exclude-members: model_config, model_fields + Simple Types ^^^^^^^^^^^^ .. automodule:: vo_models.voresource.types diff --git a/docs/source/pages/protocols/index.rst b/docs/source/pages/protocols/index.rst index 5811eea..11e507e 100644 --- a/docs/source/pages/protocols/index.rst +++ b/docs/source/pages/protocols/index.rst @@ -10,4 +10,6 @@ The following IVOA protocols are currently supported: uws vosi - vodataservice \ No newline at end of file + vodataservice + voresource + tapregext \ No newline at end of file diff --git a/docs/source/pages/protocols/tapregext.rst b/docs/source/pages/protocols/tapregext.rst new file mode 100644 index 0000000..9203f0e --- /dev/null +++ b/docs/source/pages/protocols/tapregext.rst @@ -0,0 +1,36 @@ +.. _tapregext: + +TAPRegExt +---------- + +TAPRegExt is an IVOA XML encoding standard for describing TAP service metadata. It is used by the TAP standard to describe the capabilities of a TAP service. + +`vo-models` currently supports the full TAPRegExt v1.0 standard. The key model is the ``TableAccess`` model, which represents the capabilities of a TAP server: + +Models +^^^^^^ + +TableAccess +*********** + +This model represents the capabilities of a TAP server, used as part of the VOSI Capabilities standard. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/tapregext/tapregext.py + :language: python + :start-after: TableAccess-model-start + :end-before: TableAccess-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/tapregext/tapregext.py + :language: xml + :lines: 2- + :start-after: TableAccess-xml-start + :end-before: TableAccess-xml-end + +See the :ref:`tapregext_api` documentation for more information on the models and types available. \ No newline at end of file diff --git a/docs/source/pages/protocols/voresource.rst b/docs/source/pages/protocols/voresource.rst new file mode 100644 index 0000000..bfa1999 --- /dev/null +++ b/docs/source/pages/protocols/voresource.rst @@ -0,0 +1,105 @@ +.. _voresource: + +VOResource +---------- + +VOResource is an IVOA XML encoding standard for describing resource metadata. It is used by various IVOA standards, such as VODataService, VOSI, and TAPRegExt to describe resources and the services that provide access to them. + +`vo-models` currently supports the full VOResource v1.1 standard. Some of the key elements include: + +Models +^^^^^^ + +Resource +******** + +Any entity or component of a VO application that is describable and identifiable by an IVOA Identifier. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: python + :start-after: Resource-model-start + :end-before: Resource-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: xml + :lines: 2- + :start-after: Resource-xml-start + :end-before: Resource-xml-end + +Service +******* + +A resource that can be invoked by a client to perform some action on its behalf. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: python + :start-after: Service-model-start + :end-before: Service-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: xml + :lines: 2- + :start-after: Service-xml-start + :end-before: Service-xml-end + +Capability +********** + +A description of what the service does (in terms of context-specific behavior), and how to use it (in terms of an interface). + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: python + :start-after: Capability-model-start + :end-before: Capability-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: xml + :lines: 2- + :start-after: Capability-xml-start + :end-before: Capability-xml-end + +Interface +********* + +A description of a service interface. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: python + :start-after: Interface-model-start + :end-before: Interface-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/voresource/voresource.py + :language: xml + :lines: 2- + :start-after: Interface-xml-start + :end-before: Interface-xml-end + +See the :ref:`voresource_api` documentation for more information on the available models and types. From 915bf8f550338f9e39c444efc77933bebf6991f8 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 12:13:27 -0400 Subject: [PATCH 20/41] adding xsi:type, fixing standard_id --- vo_models/tapregext/models.py | 10 +++------- vo_models/voresource/models.py | 10 +++++++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 2398aa0..5914c70 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -14,12 +14,6 @@ } -class TAPCapRestriction(Capability, nsmap=NSMAP): - """An abstract capability that fixes the standardID to the IVOA ID for the TAP standard.""" - - standard_id: Literal["ivo://ivoa.net/std/TAP"] = attr(name="standardID", default="ivo://ivoa.net/std/TAP") - - class DataModelType(BaseXmlModel, nsmap=NSMAP): """IVOA defined data model, identified by an IVORN. @@ -163,7 +157,7 @@ class Language(BaseXmlModel, nsmap=NSMAP): language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) -class TableAccess(TAPCapRestriction, tag="capability", ns="", nsmap=NSMAP): +class TableAccess(Capability, tag="capability", nsmap=NSMAP): """The capabilities of a TAP server. Parameters: @@ -185,6 +179,8 @@ class TableAccess(TAPCapRestriction, tag="capability", ns="", nsmap=NSMAP): (element) - Limits on the size of uploaded data. """ + standard_id: Literal["ivo://ivoa.net/std/TAP"] = attr(name="standardID", default="ivo://ivoa.net/std/TAP") + data_model: Optional[list[DataModelType]] = element(tag="dataModel", default_factory=list) language: list[Language] = element(tag="language") output_format: list[OutputFormat] = element(tag="outputFormat") diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index f669190..6680631 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -297,7 +297,8 @@ class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): """A description of a service interface. - Since this type is abstract, one must use an Interface subclass to describe an actual interface. + Since this type is abstract, one must use an Interface subclass to describe an actual interface denoting + it via xsi:type. Additional interface subtypes (beyond WebService and WebBrowser) are defined in the VODataService schema. @@ -306,6 +307,8 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): (attr) - The version of a standard interface specification that this interface complies with. role: (attr) - A tag name that identifies the role the interface plays in the particular capability. + type: + (attr) - The xsi:type of the interface. access_url: (element) - The URL (or base URL) that a client uses to access the service. mirror_url: @@ -318,6 +321,7 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): version: Optional[str] = attr(name="version", default=None) role: Optional[str] = attr(name="role", default=None) + type: Optional[str] = attr(name="type", default=None, ns="xsi") access_url: list[AccessURL] = element(tag="accessURL") mirror_url: Optional[list[MirrorURL]] = element(tag="mirrorURL", default_factory=list) @@ -424,6 +428,9 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): Parameters: standard_id: (attr) - A URI identifier for a standard service. + type: + (attr) - A protocol-specific capability is included by specifying a vr:Capability sub-type via an xsi:type + attribute on this model. validation_level: (element) - A numeric grade describing the quality of the capability description and interface, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a @@ -435,6 +442,7 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): """ standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) + type: Optional[str] = attr(name="type", default=None, ns="xsi") validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) description: Optional[str] = element(tag="description", default=None) From 79ff26364540beb2e9484fb467963303d4b28a42 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 12:13:40 -0400 Subject: [PATCH 21/41] adding capabilities tests --- tests/vosi/VOSICapabilities-v1.0.xsd | 54 ++++++ tests/vosi/capabilities_test.py | 239 +++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 tests/vosi/VOSICapabilities-v1.0.xsd create mode 100644 tests/vosi/capabilities_test.py diff --git a/tests/vosi/VOSICapabilities-v1.0.xsd b/tests/vosi/VOSICapabilities-v1.0.xsd new file mode 100644 index 0000000..52862c2 --- /dev/null +++ b/tests/vosi/VOSICapabilities-v1.0.xsd @@ -0,0 +1,54 @@ + + + + + + A schema for formatting service capabilities as returned by a + capabilities resource, defined by the IVOA Support Interfaces + specification (VOSI). + See http://www.ivoa.net/Documents/latest/VOSI.html. + + + + + + + + + + A listing of capabilities supported by a service + + + + + + + + + + A capability supported by the service. + + + A protocol-specific capability is included by specifying a + vr:Capability sub-type via an xsi:type attribute on this + element. + + + + + + + + + diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py new file mode 100644 index 0000000..4e679d6 --- /dev/null +++ b/tests/vosi/capabilities_test.py @@ -0,0 +1,239 @@ +"""Tests for VOSI Capabilities models.""" + +from datetime import timezone as tz +from unittest import TestCase +from xml.etree.ElementTree import canonicalize + +from lxml import etree + +from vo_models.tapregext.models import ( + DataLimit, + DataLimits, + Language, + LanguageFeature, + LanguageFeatureList, + OutputFormat, + TableAccess, + Version, +) +from vo_models.voresource.models import AccessURL, Capability, Interface +from vo_models.vosi.capabilities.models import VOSICapabilities + +CAPABILITIES_HEADER = """xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0" +xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" +xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.0" +xmlns:tr="http://www.ivoa.net/xml/TAPRegExt/v1.0" +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +""" + + +class TestVOSICapabilities(TestCase): + """Tests the VOSI Capabilities complex type. + + This test uses a simulated capabilities document for a TAP service. + """ + + test_capabilities_xml = f""" + + + https://someservice.edu/tap + + + ADQL + 2.0 + + ADQL-2.0. Positional queries using CONTAINS with POINT, and CIRCLE are supported. + + + +
POINT
+
+ +
CIRCLE
+
+
+
+ + application/x-votable+xml + votable + + + text/csv;header=present + csv + + + 100000 + 100000 + +
+ + + + https://someservice.edu/tap/capabilities + + + + + + + https://someservice.edu/tap/availability + + + + + + + https://someservice.edu/tap/tables + + + + + + + https://someservice.edu/tap/examples + + + +
+ """ + + test_tap_capabilities = TableAccess( + type="tr:TableAccess", + interface=[ + Interface( + role="std", + type="vs:ParamHTTP", + version="1.1", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap")], + ) + ], + language=[ + Language( + name="ADQL", + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL#v2.0")], + description="ADQL-2.0. Positional queries using CONTAINS with POINT, CIRCLE, BOX, and POLYGON are supported.", + language_features=[ + LanguageFeatureList( + type="ivo://ivoa.net/std/TAPRegExt#features-adql-geo", + feature=[ + LanguageFeature(form="POINT"), + LanguageFeature(form="CIRCLE"), + LanguageFeature(form="BOX"), + LanguageFeature(form="POLYGON"), + ], + ), + ], + ) + ], + output_format=[ + OutputFormat( + mime="application/x-votable+xml", + alias=["votable"], + ivo_id="ivo://ivoa.net/std/TAPRegExt#output-votable-td", + ), + OutputFormat( + mime="text/csv;header=present", + alias=["csv"], + ), + ], + output_limit=DataLimits( + default=DataLimit(unit="row", value=100000), + hard=DataLimit(unit="row", value=100000), + ), + ) + + test_vosi_capabilities = Capability( + standard_id="ivo://ivoa.net/std/VOSI#capabilities", + interface=[ + Interface( + type="vs:ParamHTTP", + role="std", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/capabilities")], + ) + ], + ) + test_vosi_availability = Capability( + standard_id="ivo://ivoa.net/std/VOSI#availability", + interface=[ + Interface( + type="vs:ParamHTTP", + role="std", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/availability")], + ) + ], + ) + test_vosi_tables = Capability( + standard_id="ivo://ivoa.net/std/VOSI#tables", + interface=[ + Interface( + type="vs:ParamHTTP", + role="std", + version="1.1", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/tables")], + ) + ], + ) + test_dali_examples = Capability( + standard_id="ivo://ivoa.net/std/DALI#examples", + interface=[ + Interface( + type="vr:WebBrowser", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/examples")], + ) + ], + ) + test_vosi_capabilities_model = VOSICapabilities( + capability=[ + test_tap_capabilities, + # test_vosi_capabilities, + # test_vosi_availability, + # test_vosi_tables, + # test_dali_examples, + ] + ) + + def _get_capability(self, standard_id: str) -> Capability: + """Get a capability from the test capabilities.""" + for cap in self.test_vosi_capabilities_model.capability: + if cap.standard_id == standard_id: + return cap + return None + + def test_read_from_xml(self): + """Test reading VOSI Capabilities from XML.""" + capabilities = VOSICapabilities.from_xml(self.test_capabilities_xml) + + self.assertEqual(len(capabilities.capability), 5) + + # Check the TAP capability + tap_capability = self._get_capability("ivo://ivoa.net/std/TAP") + self.assertIsNotNone(tap_capability) + self.assertEqual(tap_capability, self.test_tap_capabilities) + + # Check the VOSI capabilities + vosi_capabilities = self._get_capability("ivo://ivoa.net/std/VOSI#capabilities") + self.assertIsNotNone(vosi_capabilities) + self.assertEqual(vosi_capabilities, self.test_vosi_capabilities) + + # Check the VOSI availability + vosi_availability = self._get_capability("ivo://ivoa.net/std/VOSI#availability") + self.assertIsNotNone(vosi_availability) + self.assertEqual(vosi_availability, self.test_vosi_availability) + + # Check the VOSI tables + vosi_tables = self._get_capability("ivo://ivoa.net/std/VOSI#tables") + self.assertIsNotNone(vosi_tables) + self.assertEqual(vosi_tables, self.test_vosi_tables) + + # Check the DALI examples + dali_examples = self._get_capability("ivo://ivoa.net/std/DALI#examples") + self.assertIsNotNone(dali_examples) + self.assertEqual(dali_examples, self.test_dali_examples) + + def test_write_to_xml(self): + """Test writing VOSI Capabilities to XML.""" + test_xml = self.test_vosi_capabilities_model.to_xml(encoding=str, skip_empty=True) + self.assertEqual( + canonicalize(test_xml, strip_text=True), + canonicalize(self.test_capabilities_xml, strip_text=True), + ) From 28643673910e5fcbfbddee5d00aef5352a7dc998 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 12:14:46 -0400 Subject: [PATCH 22/41] removing tapcaprestriction --- vo_models/tapregext/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vo_models/tapregext/__init__.py b/vo_models/tapregext/__init__.py index 8346993..b9a301b 100644 --- a/vo_models/tapregext/__init__.py +++ b/vo_models/tapregext/__init__.py @@ -13,7 +13,6 @@ LanguageFeatureList, OutputFormat, TableAccess, - TAPCapRestriction, TimeLimits, UploadMethod, Version, From 286bcb2b36c8b79dfa3556ff662f9fabbc14154d Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 12:15:24 -0400 Subject: [PATCH 23/41] added vosicapabilities model --- vo_models/vosi/capabilities/__init__.py | 0 vo_models/vosi/capabilities/models.py | 29 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 vo_models/vosi/capabilities/__init__.py create mode 100644 vo_models/vosi/capabilities/models.py diff --git a/vo_models/vosi/capabilities/__init__.py b/vo_models/vosi/capabilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py new file mode 100644 index 0000000..22934c1 --- /dev/null +++ b/vo_models/vosi/capabilities/models.py @@ -0,0 +1,29 @@ +"""VOSICapabilities pydantic-xml models.""" + +from typing import Optional, Union + +from pydantic_xml import BaseXmlModel, element + +from vo_models.tapregext.models import TableAccess +from vo_models.voresource.models import Capability + +NSMAP = { + "vosi": "http://www.ivoa.net/xml/VOSICapabilities/v1.0", + "": "http://www.ivoa.net/xml/VOResource/v1.0", + "vs": "http://www.ivoa.net/xml/VODataService/v1.0", + "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "xsi": "http://www.w3.org/2001/XMLSchema-instance", +} + + +class VOSICapabilities(BaseXmlModel, tag="capabilities", ns="vosi", nsmap=NSMAP): + """A listing of capabilities supported by a service + + Parameters: + capability: + (element) - A capability supported by the service. + A protocol-specific capability is included by specifying a vr:Capability sub-type via an xsi:type + attribute on this element. + """ + + capability: Optional[list[Union[Capability | TableAccess]]] = element(tag="capability", default_factory=list) From 289f175097bc3b2db898ae737ea4e30224a06954 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 12:25:21 -0400 Subject: [PATCH 24/41] fix schema file --- tests/voresource/{VOResource-v1.1.xsd.xml => VOResource-v1.1.xsd} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/voresource/{VOResource-v1.1.xsd.xml => VOResource-v1.1.xsd} (100%) diff --git a/tests/voresource/VOResource-v1.1.xsd.xml b/tests/voresource/VOResource-v1.1.xsd similarity index 100% rename from tests/voresource/VOResource-v1.1.xsd.xml rename to tests/voresource/VOResource-v1.1.xsd From 85e17dfa8ba67a782c6d4a7b45585ecd25c4e794 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 13:04:44 -0400 Subject: [PATCH 25/41] namespace woes --- tests/vosi/capabilities_test.py | 12 ++++++------ vo_models/tapregext/models.py | 24 ++++++++++++------------ vo_models/voresource/models.py | 9 ++++----- vo_models/vosi/capabilities/models.py | 6 +++--- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py index 4e679d6..4c46495 100644 --- a/tests/vosi/capabilities_test.py +++ b/tests/vosi/capabilities_test.py @@ -20,7 +20,7 @@ from vo_models.vosi.capabilities.models import VOSICapabilities CAPABILITIES_HEADER = """xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0" -xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" +xmlns="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.0" xmlns:tr="http://www.ivoa.net/xml/TAPRegExt/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" @@ -185,17 +185,17 @@ class TestVOSICapabilities(TestCase): test_vosi_capabilities_model = VOSICapabilities( capability=[ test_tap_capabilities, - # test_vosi_capabilities, - # test_vosi_availability, - # test_vosi_tables, - # test_dali_examples, + test_vosi_capabilities, + test_vosi_availability, + test_vosi_tables, + test_dali_examples, ] ) def _get_capability(self, standard_id: str) -> Capability: """Get a capability from the test capabilities.""" for cap in self.test_vosi_capabilities_model.capability: - if cap.standard_id == standard_id: + if str(cap.standard_id) == standard_id: return cap return None diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 5914c70..885863a 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -10,11 +10,11 @@ "xs": "http://www.w3.org/2001/XMLSchema", "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", - "": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", } -class DataModelType(BaseXmlModel, nsmap=NSMAP): +class DataModelType(BaseXmlModel, ns="tr", nsmap=NSMAP): """IVOA defined data model, identified by an IVORN. Parameters: @@ -28,7 +28,7 @@ class DataModelType(BaseXmlModel, nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class Version(BaseXmlModel, nsmap=NSMAP): +class Version(BaseXmlModel, ns="tr", nsmap=NSMAP): """One version of the language supported by the service. Parameters: @@ -42,7 +42,7 @@ class Version(BaseXmlModel, nsmap=NSMAP): ivo_id: Optional[str] = attr(name="ivo-id", default=None) -class LanguageFeature(BaseXmlModel, nsmap=NSMAP): +class LanguageFeature(BaseXmlModel, ns="tr", nsmap=NSMAP): """A non-standard or non-mandatory feature implemented by the language. Parameters: @@ -56,7 +56,7 @@ class LanguageFeature(BaseXmlModel, nsmap=NSMAP): description: Optional[str] = element(tag="description", default=None) -class OutputFormat(BaseXmlModel, nsmap=NSMAP): +class OutputFormat(BaseXmlModel, ns="tr", nsmap=NSMAP): """An output format supported by the service. Parameters: @@ -70,7 +70,7 @@ class OutputFormat(BaseXmlModel, nsmap=NSMAP): alias: Optional[list[str]] = element(tag="alias", default_factory=list) -class UploadMethod(BaseXmlModel, nsmap=NSMAP): +class UploadMethod(BaseXmlModel, ns="tr", nsmap=NSMAP): """An upload method as defined by IVOA. Parameters: @@ -81,7 +81,7 @@ class UploadMethod(BaseXmlModel, nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class TimeLimits(BaseXmlModel, nsmap=NSMAP): +class TimeLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): """Time-valued limits, all values given in seconds. Parameters: @@ -95,7 +95,7 @@ class TimeLimits(BaseXmlModel, nsmap=NSMAP): hard: Optional[int] = element(tag="hard", default=None) -class DataLimit(BaseXmlModel, nsmap=NSMAP): +class DataLimit(BaseXmlModel, ns="tr", nsmap=NSMAP): """A limit on some data size, either in rows or in bytes. Parameters: @@ -109,7 +109,7 @@ class DataLimit(BaseXmlModel, nsmap=NSMAP): unit: Literal["byte", "row"] = attr(name="unit") -class DataLimits(BaseXmlModel, nsmap=NSMAP): +class DataLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): """Limits on data sizes, given in rows or bytes. Parameters: @@ -123,7 +123,7 @@ class DataLimits(BaseXmlModel, nsmap=NSMAP): hard: Optional[DataLimit] = element(tag="hard", default=None) -class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): +class LanguageFeatureList(BaseXmlModel, ns="tr", nsmap=NSMAP): """An enumeration of non-standard or non-mandatory features of a specific type implemented by the language. Parameters: @@ -137,7 +137,7 @@ class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): type: str = attr(name="type") -class Language(BaseXmlModel, nsmap=NSMAP): +class Language(BaseXmlModel, ns="tr", nsmap=NSMAP): """A query language supported by the service. Parameters: @@ -157,7 +157,7 @@ class Language(BaseXmlModel, nsmap=NSMAP): language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) -class TableAccess(Capability, tag="capability", nsmap=NSMAP): +class TableAccess(Capability, ns="tr", tag="capability", nsmap=NSMAP): """The capabilities of a TAP server. Parameters: diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 6680631..251f410 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -11,7 +11,6 @@ # pylint: disable=too-few-public-methods NSMAP = { - "xml": "http://www.w3.org/XML/1998/namespace", "": "http://www.w3.org/2001/XMLSchema", "xs": "http://www.w3.org/2001/XMLSchema", "vr": "http://www.ivoa.net/xml/VOResource/v1.0", @@ -329,11 +328,11 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): test_querystring: Optional[str] = element(tag="testQueryString", default=None) -class WebBrowser(Interface, ns="vr", nsmap=NSMAP): +class WebBrowser(Interface, nsmap=NSMAP): """A (form-based) interface intended to be accesed interactively by a user via a web browser.""" -class WebService(Interface, ns="vr", nsmap=NSMAP): +class WebService(Interface, nsmap=NSMAP): """A Web Service that is describable by a WSDL document. The accessURL element gives the Web Service's endpoint URL. @@ -406,7 +405,7 @@ def _validate_short_name(cls, values): return values -class Organisation(Resource, ns="vr", nsmap=NSMAP): +class Organisation(Resource, nsmap=NSMAP): """A named group of one or more persons brought together to pursue participation in VO applications. Parameters: @@ -449,7 +448,7 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): interface: Optional[list[Interface]] = element(tag="interface", default_factory=list) -class Service(Resource, ns="vr", nsmap=NSMAP): +class Service(Resource, nsmap=NSMAP): """A resource that can be invoked by a client to perform some action on its behalf. Parameters: diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py index 22934c1..928c8b5 100644 --- a/vo_models/vosi/capabilities/models.py +++ b/vo_models/vosi/capabilities/models.py @@ -9,10 +9,10 @@ NSMAP = { "vosi": "http://www.ivoa.net/xml/VOSICapabilities/v1.0", - "": "http://www.ivoa.net/xml/VOResource/v1.0", - "vs": "http://www.ivoa.net/xml/VODataService/v1.0", - "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "vr": "http://www.ivoa.net/xml/VOResource/v1.0", + "xsd": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", + "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", } From 23a3e5e699e9d1b2c3dbe1a0cc020dbacddb0af7 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 15:36:48 -0400 Subject: [PATCH 26/41] namespacing figured out --- tests/vosi/capabilities_test.py | 22 +++++++------- vo_models/tapregext/models.py | 27 ++++++++++-------- vo_models/voresource/models.py | 41 +++++++++++++++------------ vo_models/vosi/capabilities/models.py | 13 ++++++--- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py index 4c46495..4fd490d 100644 --- a/tests/vosi/capabilities_test.py +++ b/tests/vosi/capabilities_test.py @@ -16,7 +16,7 @@ TableAccess, Version, ) -from vo_models.voresource.models import AccessURL, Capability, Interface +from vo_models.voresource.models import AccessURL, Capability, Interface, WebBrowser from vo_models.vosi.capabilities.models import VOSICapabilities CAPABILITIES_HEADER = """xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0" @@ -176,8 +176,7 @@ class TestVOSICapabilities(TestCase): test_dali_examples = Capability( standard_id="ivo://ivoa.net/std/DALI#examples", interface=[ - Interface( - type="vr:WebBrowser", + WebBrowser( access_url=[AccessURL(use="full", value="https://someservice.edu/tap/examples")], ) ], @@ -192,9 +191,9 @@ class TestVOSICapabilities(TestCase): ] ) - def _get_capability(self, standard_id: str) -> Capability: + def _get_capability(self, capabilities: VOSICapabilities, standard_id: str) -> Capability: """Get a capability from the test capabilities.""" - for cap in self.test_vosi_capabilities_model.capability: + for cap in capabilities.capability: if str(cap.standard_id) == standard_id: return cap return None @@ -206,27 +205,30 @@ def test_read_from_xml(self): self.assertEqual(len(capabilities.capability), 5) # Check the TAP capability - tap_capability = self._get_capability("ivo://ivoa.net/std/TAP") + tap_capability: TableAccess = self._get_capability(capabilities, "ivo://ivoa.net/std/TAP") self.assertIsNotNone(tap_capability) self.assertEqual(tap_capability, self.test_tap_capabilities) + self.assertEqual(len(tap_capability.interface), 1) + self.assertIsNotNone(tap_capability.output_limit) + self.assertEqual(len(tap_capability.language), 1) # Check the VOSI capabilities - vosi_capabilities = self._get_capability("ivo://ivoa.net/std/VOSI#capabilities") + vosi_capabilities = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#capabilities") self.assertIsNotNone(vosi_capabilities) self.assertEqual(vosi_capabilities, self.test_vosi_capabilities) # Check the VOSI availability - vosi_availability = self._get_capability("ivo://ivoa.net/std/VOSI#availability") + vosi_availability = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#availability") self.assertIsNotNone(vosi_availability) self.assertEqual(vosi_availability, self.test_vosi_availability) # Check the VOSI tables - vosi_tables = self._get_capability("ivo://ivoa.net/std/VOSI#tables") + vosi_tables = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#tables") self.assertIsNotNone(vosi_tables) self.assertEqual(vosi_tables, self.test_vosi_tables) # Check the DALI examples - dali_examples = self._get_capability("ivo://ivoa.net/std/DALI#examples") + dali_examples = self._get_capability(capabilities, "ivo://ivoa.net/std/DALI#examples") self.assertIsNotNone(dali_examples) self.assertEqual(dali_examples, self.test_dali_examples) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 885863a..b9eb5bd 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -4,6 +4,7 @@ from pydantic_xml import BaseXmlModel, attr, element +from vo_models.voresource.models import NSMAP as VORESOURCE_NSMAP from vo_models.voresource.models import Capability NSMAP = { @@ -11,10 +12,11 @@ "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", -} + "xsi": "http://www.w3.org/2001/XMLSchema-instance" +} | VORESOURCE_NSMAP -class DataModelType(BaseXmlModel, ns="tr", nsmap=NSMAP): +class DataModelType(BaseXmlModel, nsmap=NSMAP): """IVOA defined data model, identified by an IVORN. Parameters: @@ -28,7 +30,7 @@ class DataModelType(BaseXmlModel, ns="tr", nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class Version(BaseXmlModel, ns="tr", nsmap=NSMAP): +class Version(BaseXmlModel, nsmap=NSMAP): """One version of the language supported by the service. Parameters: @@ -42,7 +44,7 @@ class Version(BaseXmlModel, ns="tr", nsmap=NSMAP): ivo_id: Optional[str] = attr(name="ivo-id", default=None) -class LanguageFeature(BaseXmlModel, ns="tr", nsmap=NSMAP): +class LanguageFeature(BaseXmlModel, nsmap=NSMAP): """A non-standard or non-mandatory feature implemented by the language. Parameters: @@ -56,7 +58,7 @@ class LanguageFeature(BaseXmlModel, ns="tr", nsmap=NSMAP): description: Optional[str] = element(tag="description", default=None) -class OutputFormat(BaseXmlModel, ns="tr", nsmap=NSMAP): +class OutputFormat(BaseXmlModel, nsmap=NSMAP): """An output format supported by the service. Parameters: @@ -70,7 +72,7 @@ class OutputFormat(BaseXmlModel, ns="tr", nsmap=NSMAP): alias: Optional[list[str]] = element(tag="alias", default_factory=list) -class UploadMethod(BaseXmlModel, ns="tr", nsmap=NSMAP): +class UploadMethod(BaseXmlModel, nsmap=NSMAP): """An upload method as defined by IVOA. Parameters: @@ -81,7 +83,7 @@ class UploadMethod(BaseXmlModel, ns="tr", nsmap=NSMAP): ivo_id: str = attr(name="ivo-id") -class TimeLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): +class TimeLimits(BaseXmlModel, nsmap=NSMAP): """Time-valued limits, all values given in seconds. Parameters: @@ -95,7 +97,7 @@ class TimeLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): hard: Optional[int] = element(tag="hard", default=None) -class DataLimit(BaseXmlModel, ns="tr", nsmap=NSMAP): +class DataLimit(BaseXmlModel, nsmap=NSMAP): """A limit on some data size, either in rows or in bytes. Parameters: @@ -109,7 +111,7 @@ class DataLimit(BaseXmlModel, ns="tr", nsmap=NSMAP): unit: Literal["byte", "row"] = attr(name="unit") -class DataLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): +class DataLimits(BaseXmlModel, nsmap=NSMAP): """Limits on data sizes, given in rows or bytes. Parameters: @@ -123,7 +125,7 @@ class DataLimits(BaseXmlModel, ns="tr", nsmap=NSMAP): hard: Optional[DataLimit] = element(tag="hard", default=None) -class LanguageFeatureList(BaseXmlModel, ns="tr", nsmap=NSMAP): +class LanguageFeatureList(BaseXmlModel, nsmap=NSMAP): """An enumeration of non-standard or non-mandatory features of a specific type implemented by the language. Parameters: @@ -137,7 +139,7 @@ class LanguageFeatureList(BaseXmlModel, ns="tr", nsmap=NSMAP): type: str = attr(name="type") -class Language(BaseXmlModel, ns="tr", nsmap=NSMAP): +class Language(BaseXmlModel, nsmap=NSMAP): """A query language supported by the service. Parameters: @@ -157,7 +159,7 @@ class Language(BaseXmlModel, ns="tr", nsmap=NSMAP): language_features: Optional[list[LanguageFeatureList]] = element(tag="languageFeatures", default_factory=[]) -class TableAccess(Capability, ns="tr", tag="capability", nsmap=NSMAP): +class TableAccess(Capability, tag="capability", nsmap=NSMAP): """The capabilities of a TAP server. Parameters: @@ -180,6 +182,7 @@ class TableAccess(Capability, ns="tr", tag="capability", nsmap=NSMAP): """ standard_id: Literal["ivo://ivoa.net/std/TAP"] = attr(name="standardID", default="ivo://ivoa.net/std/TAP") + type: Literal["tr:TableAccess"] = attr(default="tr:TableAccess", ns="xsi") data_model: Optional[list[DataModelType]] = element(tag="dataModel", default_factory=list) language: list[Language] = element(tag="language") diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 251f410..bfec979 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -11,14 +11,15 @@ # pylint: disable=too-few-public-methods NSMAP = { - "": "http://www.w3.org/2001/XMLSchema", + "": "http://www.ivoa.net/xml/VOResource/v1.0", "xs": "http://www.w3.org/2001/XMLSchema", "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", + "xsi": "http://www.w3.org/2001/XMLSchema-instance", } -class Validation(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Validation(BaseXmlModel, nsmap=NSMAP): """A validation stamp combining a validation level and the ID of the validator. Parameters: @@ -41,7 +42,7 @@ def _validate_value(cls, values): return values -class ResourceName(BaseXmlModel, ns="vr", nsmap=NSMAP): +class ResourceName(BaseXmlModel, nsmap=NSMAP): """The name of a potentially registered resource. That is, the entity referred to may have an associated identifier. @@ -55,7 +56,7 @@ class ResourceName(BaseXmlModel, ns="vr", nsmap=NSMAP): ivo_id: Optional[IdentifierURI] = attr(name="ivo-id", default=None) -class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Date(BaseXmlModel, nsmap=NSMAP): """A string indicating what the date refers to. The value of role should be taken from the vocabulary maintained at http://www.ivoa.net/rdf/voresource/date_role. @@ -73,7 +74,7 @@ class Date(BaseXmlModel, ns="vr", nsmap=NSMAP): ) -class Source(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Source(BaseXmlModel, nsmap=NSMAP): """A bibliographic reference from which the present resource is derived or extracted. Parameters: @@ -88,7 +89,7 @@ class Source(BaseXmlModel, ns="vr", nsmap=NSMAP): format: Optional[str] = attr(name="format", default=None) -class Rights(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Rights(BaseXmlModel, nsmap=NSMAP): """A statement of usage conditions. This will typically include a license, which should be given as a full string @@ -105,7 +106,7 @@ class Rights(BaseXmlModel, ns="vr", nsmap=NSMAP): rights_uri: Optional[networks.AnyUrl] = attr(name="rightsURI", default=None) -class AccessURL(BaseXmlModel, ns="vr", nsmap=NSMAP): +class AccessURL(BaseXmlModel, nsmap=NSMAP): """The URL (or base URL) that a client uses to access the service. Parameters: @@ -120,7 +121,7 @@ class AccessURL(BaseXmlModel, ns="vr", nsmap=NSMAP): use: Literal["full", "base", "dir"] = attr(name="use") -class MirrorURL(BaseXmlModel, ns="vr", nsmap=NSMAP): +class MirrorURL(BaseXmlModel, nsmap=NSMAP): """A URL of a mirror (i.e., a functionally identical additional service interface) to Parameters: @@ -134,7 +135,7 @@ class MirrorURL(BaseXmlModel, ns="vr", nsmap=NSMAP): title: Optional[str] = attr(name="title", default=None) -class Contact(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Contact(BaseXmlModel, nsmap=NSMAP): """Information allowing establishing contact, e.g., for purposes of support. Parameters: @@ -172,7 +173,7 @@ def _validate_name(cls, values): return values -class Creator(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Creator(BaseXmlModel, nsmap=NSMAP): """The entity (e.g. person or organisation) primarily responsible for creating something Parameters: @@ -205,7 +206,7 @@ def _validate_name(cls, values): return values -class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Relationship(BaseXmlModel, nsmap=NSMAP): """A description of the relationship between one resource and one or more other resources. Parameters: @@ -221,7 +222,7 @@ class Relationship(BaseXmlModel, ns="vr", nsmap=NSMAP): related_resource: list[ResourceName] = element(tag="relatedResource") -class SecurityMethod(BaseXmlModel, ns="vr", nsmap=NSMAP): +class SecurityMethod(BaseXmlModel, nsmap=NSMAP): """A description of a security mechanism. This type only allows one to refer to the mechanism via a URI. Derived types would allow for more metadata. @@ -234,7 +235,7 @@ class SecurityMethod(BaseXmlModel, ns="vr", nsmap=NSMAP): standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) -class Curation(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Curation(BaseXmlModel, nsmap=NSMAP): """Information regarding the general curation of a resource Parameters: @@ -261,7 +262,7 @@ class Curation(BaseXmlModel, ns="vr", nsmap=NSMAP): contact: list[Contact] = element(tag="contact") -class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Content(BaseXmlModel, nsmap=NSMAP): """Information regarding the general content of a resource Parameters: @@ -293,7 +294,7 @@ class Content(BaseXmlModel, ns="vr", nsmap=NSMAP): relationship: Optional[list[Relationship]] = element(tag="relationship", default_factory=list) -class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Interface(BaseXmlModel, nsmap=NSMAP): """A description of a service interface. Since this type is abstract, one must use an Interface subclass to describe an actual interface denoting @@ -331,6 +332,8 @@ class Interface(BaseXmlModel, ns="vr", nsmap=NSMAP): class WebBrowser(Interface, nsmap=NSMAP): """A (form-based) interface intended to be accesed interactively by a user via a web browser.""" + type: Literal["tr:WebBrowser"] = attr(name="type", default="tr:WebBrowser", ns="xsi") + class WebService(Interface, nsmap=NSMAP): """A Web Service that is describable by a WSDL document. @@ -342,10 +345,12 @@ class WebService(Interface, nsmap=NSMAP): (element) - The location of the WSDL that describes this Web Service. """ + type: Literal["tr:WebService"] = attr(name="type", default="tr:WebService", ns="xsi") + wsdl_url: Optional[list[networks.AnyUrl]] = element(tag="wsdlURL", default_factory=list) -class Resource(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Resource(BaseXmlModel, nsmap=NSMAP): """Any entity or component of a VO application that is describable and identifiable by an IVOA Identifier. @@ -420,7 +425,7 @@ class Organisation(Resource, nsmap=NSMAP): instrument: Optional[list[ResourceName]] = element(tag="instrument", default_factory=list) -class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): +class Capability(BaseXmlModel, tag="capability", nsmap=NSMAP): """A description of what the service does (in terms of context-specific behavior), and how to use it (in terms of an interface) @@ -440,7 +445,7 @@ class Capability(BaseXmlModel, ns="vr", nsmap=NSMAP): (element) - A description of how to call the service to access this capability. """ - standard_id: Optional[networks.AnyUrl] = attr(name="standardID", default=None) + standard_id: networks.AnyUrl = attr(name="standardID") type: Optional[str] = attr(name="type", default=None, ns="xsi") validation_level: Optional[list[Validation]] = element(tag="validationLevel", default_factory=list) diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py index 928c8b5..608f8aa 100644 --- a/vo_models/vosi/capabilities/models.py +++ b/vo_models/vosi/capabilities/models.py @@ -1,19 +1,19 @@ """VOSICapabilities pydantic-xml models.""" -from typing import Optional, Union +from typing import Any, Union from pydantic_xml import BaseXmlModel, element from vo_models.tapregext.models import TableAccess +from vo_models.voresource.models import NSMAP as VORESOURCE_NSMAP from vo_models.voresource.models import Capability NSMAP = { "vosi": "http://www.ivoa.net/xml/VOSICapabilities/v1.0", - "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "xsd": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", -} +} | VORESOURCE_NSMAP class VOSICapabilities(BaseXmlModel, tag="capabilities", ns="vosi", nsmap=NSMAP): @@ -26,4 +26,9 @@ class VOSICapabilities(BaseXmlModel, tag="capabilities", ns="vosi", nsmap=NSMAP) attribute on this element. """ - capability: Optional[list[Union[Capability | TableAccess]]] = element(tag="capability", default_factory=list) + capability: list[Union[TableAccess, Capability]] = element( + tag="capability", + ns="", + nsmap=NSMAP, + default=[], + ) From f62be4b9b17a1a325af9348c3473f7048512ed45 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 16:13:58 -0400 Subject: [PATCH 27/41] fix outputformat --- vo_models/tapregext/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index b9eb5bd..0e79973 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -12,7 +12,7 @@ "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", - "xsi": "http://www.w3.org/2001/XMLSchema-instance" + "xsi": "http://www.w3.org/2001/XMLSchema-instance", } | VORESOURCE_NSMAP @@ -66,10 +66,13 @@ class OutputFormat(BaseXmlModel, nsmap=NSMAP): (element) - The MIME type of this format. alias: (element) - Other values of FORMAT that make the service return documents with this MIME type. + ivo_id: + (attr) - An optional IVORN of the output format. """ mime: str = element(tag="mime") alias: Optional[list[str]] = element(tag="alias", default_factory=list) + ivo_id: Optional[str] = attr(name="ivo-id", default=None) class UploadMethod(BaseXmlModel, nsmap=NSMAP): From 365a05f66e786d5521ca27b1b9a689e49ba6b706 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 16:14:32 -0400 Subject: [PATCH 28/41] add baseparam,inputparam,paramhttp --- tests/vosi/capabilities_test.py | 43 ++++++++++-------- vo_models/vodataservice/models.py | 74 ++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py index 4fd490d..94ad6b4 100644 --- a/tests/vosi/capabilities_test.py +++ b/tests/vosi/capabilities_test.py @@ -4,8 +4,6 @@ from unittest import TestCase from xml.etree.ElementTree import canonicalize -from lxml import etree - from vo_models.tapregext.models import ( DataLimit, DataLimits, @@ -16,6 +14,7 @@ TableAccess, Version, ) +from vo_models.vodataservice.models import ParamHTTP from vo_models.voresource.models import AccessURL, Capability, Interface, WebBrowser from vo_models.vosi.capabilities.models import VOSICapabilities @@ -42,7 +41,7 @@ class TestVOSICapabilities(TestCase): ADQL 2.0 - ADQL-2.0. Positional queries using CONTAINS with POINT, and CIRCLE are supported. + ADQL-2.0. Positional queries using CONTAINS with POINT and CIRCLE are supported. @@ -100,9 +99,8 @@ class TestVOSICapabilities(TestCase): test_tap_capabilities = TableAccess( type="tr:TableAccess", interface=[ - Interface( + ParamHTTP( role="std", - type="vs:ParamHTTP", version="1.1", access_url=[AccessURL(use="full", value="https://someservice.edu/tap")], ) @@ -111,15 +109,13 @@ class TestVOSICapabilities(TestCase): Language( name="ADQL", version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL#v2.0")], - description="ADQL-2.0. Positional queries using CONTAINS with POINT, CIRCLE, BOX, and POLYGON are supported.", + description="ADQL-2.0. Positional queries using CONTAINS with POINT and CIRCLE are supported.", language_features=[ LanguageFeatureList( type="ivo://ivoa.net/std/TAPRegExt#features-adql-geo", feature=[ LanguageFeature(form="POINT"), LanguageFeature(form="CIRCLE"), - LanguageFeature(form="BOX"), - LanguageFeature(form="POLYGON"), ], ), ], @@ -145,8 +141,7 @@ class TestVOSICapabilities(TestCase): test_vosi_capabilities = Capability( standard_id="ivo://ivoa.net/std/VOSI#capabilities", interface=[ - Interface( - type="vs:ParamHTTP", + ParamHTTP( role="std", access_url=[AccessURL(use="full", value="https://someservice.edu/tap/capabilities")], ) @@ -155,8 +150,7 @@ class TestVOSICapabilities(TestCase): test_vosi_availability = Capability( standard_id="ivo://ivoa.net/std/VOSI#availability", interface=[ - Interface( - type="vs:ParamHTTP", + ParamHTTP( role="std", access_url=[AccessURL(use="full", value="https://someservice.edu/tap/availability")], ) @@ -165,8 +159,7 @@ class TestVOSICapabilities(TestCase): test_vosi_tables = Capability( standard_id="ivo://ivoa.net/std/VOSI#tables", interface=[ - Interface( - type="vs:ParamHTTP", + ParamHTTP( role="std", version="1.1", access_url=[AccessURL(use="full", value="https://someservice.edu/tap/tables")], @@ -207,30 +200,42 @@ def test_read_from_xml(self): # Check the TAP capability tap_capability: TableAccess = self._get_capability(capabilities, "ivo://ivoa.net/std/TAP") self.assertIsNotNone(tap_capability) - self.assertEqual(tap_capability, self.test_tap_capabilities) self.assertEqual(len(tap_capability.interface), 1) + self.assertIsInstance(tap_capability.interface[0], Interface) + self.assertEqual(tap_capability.interface[0].type, "vs:ParamHTTP") self.assertIsNotNone(tap_capability.output_limit) + self.assertEqual(tap_capability.output_limit.default.value, 100000) self.assertEqual(len(tap_capability.language), 1) + self.assertEqual(len(tap_capability.language[0].language_features), 1) + self.assertEqual(len(tap_capability.output_format), 2) # Check the VOSI capabilities vosi_capabilities = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#capabilities") self.assertIsNotNone(vosi_capabilities) - self.assertEqual(vosi_capabilities, self.test_vosi_capabilities) + self.assertEqual(len(vosi_capabilities.interface), 1) + self.assertIsInstance(vosi_capabilities.interface[0], Interface) + self.assertEqual(vosi_capabilities.interface[0].type, "vs:ParamHTTP") # Check the VOSI availability vosi_availability = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#availability") self.assertIsNotNone(vosi_availability) - self.assertEqual(vosi_availability, self.test_vosi_availability) + self.assertEqual(len(vosi_availability.interface), 1) + self.assertIsInstance(vosi_availability.interface[0], Interface) + self.assertEqual(vosi_availability.interface[0].type, "vs:ParamHTTP") # Check the VOSI tables vosi_tables = self._get_capability(capabilities, "ivo://ivoa.net/std/VOSI#tables") self.assertIsNotNone(vosi_tables) - self.assertEqual(vosi_tables, self.test_vosi_tables) + self.assertEqual(len(vosi_tables.interface), 1) + self.assertIsInstance(vosi_tables.interface[0], Interface) + self.assertEqual(vosi_tables.interface[0].type, "vs:ParamHTTP") # Check the DALI examples dali_examples = self._get_capability(capabilities, "ivo://ivoa.net/std/DALI#examples") self.assertIsNotNone(dali_examples) - self.assertEqual(dali_examples, self.test_dali_examples) + self.assertEqual(len(dali_examples.interface), 1) + self.assertIsInstance(dali_examples.interface[0], Interface) + self.assertEqual(dali_examples.interface[0].type, "vr:WebBrowser") def test_write_to_xml(self): """Test writing VOSI Capabilities to XML.""" diff --git a/vo_models/vodataservice/models.py b/vo_models/vodataservice/models.py index 56a89d9..054fd24 100644 --- a/vo_models/vodataservice/models.py +++ b/vo_models/vodataservice/models.py @@ -3,13 +3,14 @@ TODO: This is an incomplete spec, covering only elements needed for VOSITables https://github.com/spacetelescope/vo-models/issues/17 """ -from typing import Any, Optional +from typing import Any, Literal, Optional from xml.sax.saxutils import escape from pydantic import field_validator from pydantic_xml import BaseXmlModel, attr, element from vo_models.adql.misc import ADQL_SQL_KEYWORDS +from vo_models.voresource.models import Interface # pylint: disable=no-self-argument @@ -309,3 +310,74 @@ def validate_tableset_schema(cls, value): if not isinstance(value, list): value = [value] return value + + +class BaseParam(BaseXmlModel): + """A description of a parameter that places no restriction on the parameter's data type. + + TODO: Set as base for TableParam when implementing VODataservice fully. + + Parameters: + name: + (elem) - The name of the parameter. + description: + (elem) - A free-text description of the parameter's contents. + unit: + (elem) - The unit associated with the values in the parameter. + ucd: + (elem) - The name of a unified content descriptor that describes the scientific content of the parameter. + utype: + (elem) - An identifier for a concept in a data model that the data in this parameter represent. + """ + + description: Optional[str] = element(tag="description", default=None) + unit: Optional[str] = element(tag="unit", default=None) + ucd: Optional[str] = element(tag="ucd", default=None) + utype: Optional[str] = element(tag="utype", default=None) + + +ParamUse = Literal["required", "optional", "ignored"] + + +class InputParam(BaseParam): + """A description of a service or function parameter having a fixed data type. + + Parameters: + datatype: + (elem) - A type of data contained in the parameter. + use: + (attr) - An indication of whether this parameter is required to be provided for the application or service + to work properly. + std: + (attr) - If true, the meaning and behavior of this parameter is reserved and defined by a standard interface. + """ + + datatype: Optional[DataType] = element(tag="dataType", default=None) + use: ParamUse = attr(name="use", default="optional") + std: Optional[bool] = attr(name="std", default=True) + + +HTTPQueryType = Literal["GET", "POST"] + + +class ParamHTTP(Interface): + """A service invoked via an HTTP Query (either Get or Post) with a set of arguments consisting of keyword + name-value pairs. + + Parameters: + queryType: + (element) - The type of HTTP request, either 'GET' or 'POST'. Max occurs 2. + resultType: + (element) - The MIME media type of a document returned in the HTTP response. + param: + (element) - A description of a input parameter that can be provided as a name=value argument. + testQuery: + (element) - An ampersand-delimited list of arguments that can be used to test this service interface. + """ + + type: Literal["vs:ParamHTTP"] = attr(name="type", default="vs:ParamHTTP", ns="xsi") + + query_type: Optional[list[HTTPQueryType]] = element(tag="queryType", max_length=2, default=None) + result_type: Optional[str] = element(tag="resultType", default=None) + param: Optional[list[InputParam]] = element(tag="param", default_factory=list) + test_query: Optional[str] = element(tag="testQuery", default=None) From 42b0b844e804e976e3915b02de914b32ea63c443 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 16:14:47 -0400 Subject: [PATCH 29/41] fix interface subclasses --- vo_models/voresource/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index bfec979..523da04 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -294,7 +294,7 @@ class Content(BaseXmlModel, nsmap=NSMAP): relationship: Optional[list[Relationship]] = element(tag="relationship", default_factory=list) -class Interface(BaseXmlModel, nsmap=NSMAP): +class Interface(BaseXmlModel, tag="interface", nsmap=NSMAP): """A description of a service interface. Since this type is abstract, one must use an Interface subclass to describe an actual interface denoting @@ -332,7 +332,7 @@ class Interface(BaseXmlModel, nsmap=NSMAP): class WebBrowser(Interface, nsmap=NSMAP): """A (form-based) interface intended to be accesed interactively by a user via a web browser.""" - type: Literal["tr:WebBrowser"] = attr(name="type", default="tr:WebBrowser", ns="xsi") + type: Literal["vr:WebBrowser"] = attr(name="type", default="vr:WebBrowser", ns="xsi") class WebService(Interface, nsmap=NSMAP): @@ -345,7 +345,7 @@ class WebService(Interface, nsmap=NSMAP): (element) - The location of the WSDL that describes this Web Service. """ - type: Literal["tr:WebService"] = attr(name="type", default="tr:WebService", ns="xsi") + type: Literal["vr:WebService"] = attr(name="type", default="vr:WebService", ns="xsi") wsdl_url: Optional[list[networks.AnyUrl]] = element(tag="wsdlURL", default_factory=list) From 62e408021012dc9e6b4ee048df8edb4e0056a463 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 16:21:50 -0400 Subject: [PATCH 30/41] fixup broken voresource tests --- tests/voresource/voresource_models_test.py | 269 +++++++++++---------- 1 file changed, 140 insertions(+), 129 deletions(-) diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 9e733a8..53b6c64 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -33,7 +33,7 @@ xmlns:xml="http://www.w3.org/XML/1998/namespace", xmlns="http://www.w3.org/2001/XMLSchema", xmlns:xs="http://www.w3.org/2001/XMLSchema", - xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0", + xmlns="http://www.ivoa.net/xml/VOResource/v1.0", xmlns:vm="http://www.ivoa.net/xml/VOMetadata/v0.1", """ @@ -42,7 +42,9 @@ class TestValidation(TestCase): """Test VOResource Validation model.""" test_validation_model = Validation(value=0, validated_by="https://example.edu") - test_validation_xml = '0' + test_validation_xml = ( + '0' + ) def test_read_from_xml(self): """Test reading from XML.""" @@ -63,7 +65,7 @@ class TestResourceName(TestCase): """Test VOResource ResourceName model.""" test_resource_name_model = ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource") - test_resource_name_xml = 'Example Resource' + test_resource_name_xml = 'Example Resource' def test_read_from_xml(self): """Test reading from XML.""" @@ -85,7 +87,7 @@ class TestDate(TestCase): test_date_model = Date(value="2021-01-01T00:00:00Z", role="update") test_date_xml = ( - '2021-01-01T00:00:00.000Z' + '2021-01-01T00:00:00.000Z' ) def test_read_from_xml(self): @@ -107,7 +109,9 @@ class TestSource(TestCase): """Test VOResource Source model""" test_source_model = Source(value="https://example.edu", format="bibcode") - test_source_xml = 'https://example.edu/' + test_source_xml = ( + 'https://example.edu/' + ) def test_read_from_xml(self): """Test reading from XML.""" @@ -128,7 +132,7 @@ class TestRights(TestCase): """Test VOResource Rights model""" test_rights_model = Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/") - test_rights_xml = 'CC BY 4.0' + test_rights_xml = 'CC BY 4.0' def test_read_from_xml(self): """Test reading from XML.""" @@ -149,7 +153,9 @@ class TestAccessURL(TestCase): """Test VOResource AccessURL model""" test_access_url_model = AccessURL(value="https://example.edu", use="full") - test_access_url_xml = 'https://example.edu/' + test_access_url_xml = ( + 'https://example.edu/' + ) def test_read_from_xml(self): """Test reading from XML.""" @@ -170,7 +176,9 @@ class TestMirrorURL(TestCase): """Test VOResource MirrorURL model""" test_mirror_url_model = MirrorURL(value="https://example.edu", title="Mirror") - test_mirror_url_xml = 'https://example.edu/' + test_mirror_url_xml = ( + 'https://example.edu/' + ) def test_read_from_xml(self): """Test reading from XML.""" @@ -198,13 +206,13 @@ class TestContact(TestCase): alt_identifier=["http://orcid.org/0000-0001-9718-6515"], ) test_contact_xml = ( - '' - "John Doe" - "1234 Example St." - "jdoe@mail.com" - "555-555-5555" - "http://orcid.org/0000-0001-9718-6515" - "" + '' + "John Doe" + "
1234 Example St.
" + "jdoe@mail.com" + "555-555-5555" + "http://orcid.org/0000-0001-9718-6515" + "
" ) def test_read_from_xml(self): @@ -230,10 +238,10 @@ class TestCreator(TestCase): test_creator_model = Creator(name=ResourceName(value="Doe, J."), logo="https://example.edu/logo.png") test_creator_xml = ( - '' - "Doe, J." - "https://example.edu/logo.png" - "" + '' + "Doe, J." + "https://example.edu/logo.png" + "" ) def test_read_from_xml(self): @@ -259,10 +267,10 @@ class TestRelationship(TestCase): related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], ) test_relationship_xml = ( - '' - "isPartOf" - 'Example Resource' - "" + '' + "isPartOf" + 'Example Resource' + "" ) def test_read_from_xml(self): @@ -285,7 +293,7 @@ class TestSecurityMethod(TestCase): """Test VOResource SecurityMethod model""" test_security_method_model = SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic") - test_security_method_xml = '' + test_security_method_xml = '' def test_read_from_xml(self): """Test reading from XML.""" @@ -314,14 +322,14 @@ class TestCuration(TestCase): ) test_curation_xml = ( - '' - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00.000Z' - "1.0" - "John Doe" - "" + '' + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" ) def test_read_from_xml(self): @@ -363,18 +371,18 @@ class TestContent(TestCase): ) test_content_xml = ( - '' - "Astronomy" - "Example description" - 'https://example.edu/' - "https://example.edu/" - "Education" - "General" - "" - "isPartOf" - 'Example Resource' - "" - "" + '' + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" ) def test_read_from_xml(self): @@ -412,12 +420,12 @@ class TestInterface(TestCase): test_querystring="test", ) test_interface_xml = ( - '' - 'https://example.edu/' - 'https://example.edu/' - '' - "test" - "" + '' + 'https://example.edu/' + 'https://example.edu/' + '' + "test" + "" ) def test_read_from_xml(self): @@ -449,10 +457,12 @@ class TestWebService(TestCase): access_url=[AccessURL(value="https://example.edu/", use="full")], ) test_web_service_xml = ( - '' - 'https://example.edu/' - "https://example.edu/wsdl/" - "" + '' + 'https://example.edu/' + "https://example.edu/wsdl/" + "" ) def test_read_from_xml(self): @@ -461,6 +471,7 @@ def test_read_from_xml(self): self.assertEqual(web_service.wsdl_url[0], AnyUrl("https://example.edu/wsdl/")) self.assertEqual(web_service.access_url[0].value, AnyUrl("https://example.edu/")) self.assertEqual(web_service.access_url[0].use, "full") + self.assertEqual(web_service.type, "vr:WebService") def test_write_to_xml(self): """Test writing to XML.""" @@ -509,33 +520,33 @@ class TestResource(TestCase): ) test_resource_xml = ( - '' - '0' - "Example Resource" - "example" - "https://example.edu/" - "bibcode:2008ivoa.spec.0222P" - "" - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00.000Z' - "1.0" - "John Doe" - "" - "" - "Astronomy" - "Example description" - 'https://example.edu/' - "https://example.edu/" - "Education" - "General" - "" - "isPartOf" - 'Example Resource' - "" - "" - "" + '' + '0' + "Example Resource" + "example" + "https://example.edu/" + "bibcode:2008ivoa.spec.0222P" + "" + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" + "" ) def test_read_from_xml(self): @@ -601,21 +612,21 @@ class TestOrganization(TestCase): ) test_organization_xml = ( - '' - "Example Organization" - "https://example.edu/" - "" - "Example Publisher" - "John Doe" - "" - "" - "Astronomy" - "Example description" - "https://example.edu/" - "" - 'Example Facility' - 'Example Instrument' - "" + '' + "Example Organization" + "https://example.edu/" + "" + "Example Publisher" + "John Doe" + "" + "" + "Astronomy" + "Example description" + "https://example.edu/" + "" + 'Example Facility' + 'Example Instrument' + "" ) def test_read_from_xml(self): @@ -659,13 +670,13 @@ class TestCapability(TestCase): ) test_capability_xml = ( - '' - '0' - "Example description" - '' - 'https://example.edu/' - "" - "" + '' + '0' + "Example description" + '' + 'https://example.edu/' + "" + "" ) def test_read_from_xml(self): @@ -723,32 +734,32 @@ class TestService(TestCase): ) test_service_xml = ( - '' - "Example Service" - "https://example.edu/" - "" - "STScI" - "Doe, J." - "Example Resource" - '2021-01-01T00:00:00.000Z' - "1.0" - "John Doe" - "" - "" - "Astronomy" - "Example description" - 'https://example.edu/' - "https://example.edu/" - "Education" - "General" - "" - "isPartOf" - 'Example Resource' - "" - "" - "CC BY 4.0" - "" - "" + '' + "Example Service" + "https://example.edu/" + "" + "STScI" + "Doe, J." + "Example Resource" + '2021-01-01T00:00:00.000Z' + "1.0" + "John Doe" + "" + "" + "Astronomy" + "Example description" + 'https://example.edu/' + "https://example.edu/" + "Education" + "General" + "" + "isPartOf" + 'Example Resource' + "" + "" + "CC BY 4.0" + "" + "" ) def test_read_from_xml(self): From 9fc64c04be23e3d58c27681da34496d8b47c5c35 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Thu, 26 Sep 2024 17:57:58 -0400 Subject: [PATCH 31/41] problems discriminating capability types --- tests/tapregext/tapregext_models_test.py | 4 ++-- vo_models/tapregext/models.py | 6 ++---- vo_models/voresource/models.py | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py index 0c181b6..2b358a5 100644 --- a/tests/tapregext/tapregext_models_test.py +++ b/tests/tapregext/tapregext_models_test.py @@ -20,9 +20,9 @@ ) TAPREGEXT_NAMESPACE_HEADER = """xmlns:xs="http://www.w3.org/2001/XMLSchema" -xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:vm="http://www.ivoa.net/xml/VOMetadata/v0.1" xmlns="http://www.ivoa.net/xml/TAPRegExt/v1.0" +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" """ with open("tests/tapregext/TAPRegExt-v1.0-with-erratum1.xsd") as schema_file: @@ -316,7 +316,7 @@ class TestTableAccess(TestCase): ), ) test_table_access_xml = ( - f'' + f'' "VOTable" "" "ADQL" diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 0e79973..b518247 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -4,16 +4,14 @@ from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource.models import NSMAP as VORESOURCE_NSMAP from vo_models.voresource.models import Capability NSMAP = { "xs": "http://www.w3.org/2001/XMLSchema", - "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", - "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "": "http://www.ivoa.net/xml/TAPRegExt/v1.0", "xsi": "http://www.w3.org/2001/XMLSchema-instance", -} | VORESOURCE_NSMAP +} class DataModelType(BaseXmlModel, nsmap=NSMAP): diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 523da04..6abf80d 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -13,7 +13,6 @@ NSMAP = { "": "http://www.ivoa.net/xml/VOResource/v1.0", "xs": "http://www.w3.org/2001/XMLSchema", - "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", "xsi": "http://www.w3.org/2001/XMLSchema-instance", } From 0bcd7080344798c48dcb48777e74d28b932f1e5c Mon Sep 17 00:00:00 2001 From: jwfraustro <36318163+jwfraustro@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:04:03 -0400 Subject: [PATCH 32/41] finally good hopefully --- tests/tapregext/tapregext_models_test.py | 1 - tests/voresource/voresource_models_test.py | 38 +++++++++++----------- tests/vosi/capabilities_test.py | 2 +- vo_models/tapregext/models.py | 5 +-- vo_models/voresource/models.py | 2 +- vo_models/vosi/capabilities/models.py | 2 -- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py index 2b358a5..d3f10a4 100644 --- a/tests/tapregext/tapregext_models_test.py +++ b/tests/tapregext/tapregext_models_test.py @@ -21,7 +21,6 @@ TAPREGEXT_NAMESPACE_HEADER = """xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vm="http://www.ivoa.net/xml/VOMetadata/v0.1" -xmlns="http://www.ivoa.net/xml/TAPRegExt/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" """ diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index 53b6c64..edd10ad 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -43,7 +43,7 @@ class TestValidation(TestCase): test_validation_model = Validation(value=0, validated_by="https://example.edu") test_validation_xml = ( - '0' + '0' ) def test_read_from_xml(self): @@ -65,7 +65,7 @@ class TestResourceName(TestCase): """Test VOResource ResourceName model.""" test_resource_name_model = ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource") - test_resource_name_xml = 'Example Resource' + test_resource_name_xml = 'Example Resource' def test_read_from_xml(self): """Test reading from XML.""" @@ -87,7 +87,7 @@ class TestDate(TestCase): test_date_model = Date(value="2021-01-01T00:00:00Z", role="update") test_date_xml = ( - '2021-01-01T00:00:00.000Z' + '2021-01-01T00:00:00.000Z' ) def test_read_from_xml(self): @@ -110,7 +110,7 @@ class TestSource(TestCase): test_source_model = Source(value="https://example.edu", format="bibcode") test_source_xml = ( - 'https://example.edu/' + 'https://example.edu/' ) def test_read_from_xml(self): @@ -132,7 +132,7 @@ class TestRights(TestCase): """Test VOResource Rights model""" test_rights_model = Rights(value="CC BY 4.0", rights_uri="https://creativecommons.org/licenses/by/4.0/") - test_rights_xml = 'CC BY 4.0' + test_rights_xml = 'CC BY 4.0' def test_read_from_xml(self): """Test reading from XML.""" @@ -154,7 +154,7 @@ class TestAccessURL(TestCase): test_access_url_model = AccessURL(value="https://example.edu", use="full") test_access_url_xml = ( - 'https://example.edu/' + 'https://example.edu/' ) def test_read_from_xml(self): @@ -177,7 +177,7 @@ class TestMirrorURL(TestCase): test_mirror_url_model = MirrorURL(value="https://example.edu", title="Mirror") test_mirror_url_xml = ( - 'https://example.edu/' + 'https://example.edu/' ) def test_read_from_xml(self): @@ -206,7 +206,7 @@ class TestContact(TestCase): alt_identifier=["http://orcid.org/0000-0001-9718-6515"], ) test_contact_xml = ( - '' + '' "John Doe" "
1234 Example St.
" "jdoe@mail.com" @@ -238,7 +238,7 @@ class TestCreator(TestCase): test_creator_model = Creator(name=ResourceName(value="Doe, J."), logo="https://example.edu/logo.png") test_creator_xml = ( - '' + '' "Doe, J." "https://example.edu/logo.png" "" @@ -267,7 +267,7 @@ class TestRelationship(TestCase): related_resource=[ResourceName(value="Example Resource", ivo_id="ivo://example.edu/resource")], ) test_relationship_xml = ( - '' + '' "isPartOf" 'Example Resource' "" @@ -293,7 +293,7 @@ class TestSecurityMethod(TestCase): """Test VOResource SecurityMethod model""" test_security_method_model = SecurityMethod(standard_id="ivo://ivoa.net/std/Security#basic") - test_security_method_xml = '' + test_security_method_xml = '' def test_read_from_xml(self): """Test reading from XML.""" @@ -322,7 +322,7 @@ class TestCuration(TestCase): ) test_curation_xml = ( - '' + '' "STScI" "Doe, J." "Example Resource" @@ -371,7 +371,7 @@ class TestContent(TestCase): ) test_content_xml = ( - '' + '' "Astronomy" "Example description" 'https://example.edu/' @@ -420,7 +420,7 @@ class TestInterface(TestCase): test_querystring="test", ) test_interface_xml = ( - '' + '' 'https://example.edu/' 'https://example.edu/' '' @@ -457,7 +457,7 @@ class TestWebService(TestCase): access_url=[AccessURL(value="https://example.edu/", use="full")], ) test_web_service_xml = ( - '' 'https://example.edu/' @@ -520,7 +520,7 @@ class TestResource(TestCase): ) test_resource_xml = ( - '' + '' '0' "Example Resource" "example" @@ -612,7 +612,7 @@ class TestOrganization(TestCase): ) test_organization_xml = ( - '' + '' "Example Organization" "https://example.edu/" "" @@ -670,7 +670,7 @@ class TestCapability(TestCase): ) test_capability_xml = ( - '' + '' '0' "Example description" '' @@ -734,7 +734,7 @@ class TestService(TestCase): ) test_service_xml = ( - '' + '' "Example Service" "https://example.edu/" "" diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py index 94ad6b4..7ecee09 100644 --- a/tests/vosi/capabilities_test.py +++ b/tests/vosi/capabilities_test.py @@ -19,8 +19,8 @@ from vo_models.vosi.capabilities.models import VOSICapabilities CAPABILITIES_HEADER = """xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0" -xmlns="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.0" +xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:tr="http://www.ivoa.net/xml/TAPRegExt/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" """ diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index b518247..296abb5 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -5,13 +5,14 @@ from pydantic_xml import BaseXmlModel, attr, element from vo_models.voresource.models import Capability +from vo_models.voresource.models import NSMAP as VORESOURCE_NSMAP NSMAP = { "xs": "http://www.w3.org/2001/XMLSchema", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", - "": "http://www.ivoa.net/xml/TAPRegExt/v1.0", + "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", "xsi": "http://www.w3.org/2001/XMLSchema-instance", -} +} | VORESOURCE_NSMAP class DataModelType(BaseXmlModel, nsmap=NSMAP): diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 6abf80d..722588b 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -11,7 +11,7 @@ # pylint: disable=too-few-public-methods NSMAP = { - "": "http://www.ivoa.net/xml/VOResource/v1.0", + "vr": "http://www.ivoa.net/xml/VOResource/v1.0", "xs": "http://www.w3.org/2001/XMLSchema", "vm": "http://www.ivoa.net/xml/VOMetadata/v0.1", "xsi": "http://www.w3.org/2001/XMLSchema-instance", diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py index 608f8aa..e3447af 100644 --- a/vo_models/vosi/capabilities/models.py +++ b/vo_models/vosi/capabilities/models.py @@ -12,7 +12,6 @@ "vosi": "http://www.ivoa.net/xml/VOSICapabilities/v1.0", "xsd": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", - "tr": "http://www.ivoa.net/xml/TAPRegExt/v1.0", } | VORESOURCE_NSMAP @@ -29,6 +28,5 @@ class VOSICapabilities(BaseXmlModel, tag="capabilities", ns="vosi", nsmap=NSMAP) capability: list[Union[TableAccess, Capability]] = element( tag="capability", ns="", - nsmap=NSMAP, default=[], ) From 0b95bd451b2cde09f47e6c6586ce0ff2acc360e6 Mon Sep 17 00:00:00 2001 From: jwfraustro <36318163+jwfraustro@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:10:10 -0400 Subject: [PATCH 33/41] pylint --- examples/snippets/tapregext/tapregext.py | 3 +++ examples/snippets/voresource/voresource.py | 3 +++ tests/tapregext/tapregext_models_test.py | 13 +++++++------ tests/voresource/voresource_models_test.py | 5 +++-- tests/vosi/capabilities_test.py | 1 - vo_models/uws/models.py | 5 +++-- vo_models/vodataservice/models.py | 3 ++- vo_models/voresource/models.py | 12 ++++++++---- vo_models/vosi/capabilities/models.py | 2 +- 9 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/snippets/tapregext/tapregext.py b/examples/snippets/tapregext/tapregext.py index 2544371..1c99de3 100644 --- a/examples/snippets/tapregext/tapregext.py +++ b/examples/snippets/tapregext/tapregext.py @@ -1,3 +1,4 @@ +"""Snippets for TAPRegExt models and XML serialization.""" from vo_models.tapregext.models import ( DataLimits, DataModelType, @@ -10,6 +11,8 @@ Version, ) +# pylint: disable=invalid-name + # [TableAccess-model-start] table_access_model = TableAccess( data_model=[DataModelType(value="VOTable", ivo_id="ivo://ivoa.net/std/VOTable")], diff --git a/examples/snippets/voresource/voresource.py b/examples/snippets/voresource/voresource.py index e4d9213..11d6be0 100644 --- a/examples/snippets/voresource/voresource.py +++ b/examples/snippets/voresource/voresource.py @@ -1,3 +1,4 @@ +"""Snippets for VOResource models and XML serialization.""" from datetime import timezone as tz from vo_models.voresource.models import ( @@ -21,6 +22,8 @@ ) from vo_models.voresource.types import UTCTimestamp +# pylint: disable=invalid-name + # [Resource-model-start] resource = Resource( created=UTCTimestamp(1996, 3, 11, 19, 0, 0, tzinfo=tz.utc), diff --git a/tests/tapregext/tapregext_models_test.py b/tests/tapregext/tapregext_models_test.py index d3f10a4..923f3f1 100644 --- a/tests/tapregext/tapregext_models_test.py +++ b/tests/tapregext/tapregext_models_test.py @@ -43,11 +43,9 @@ def test_read_from_xml(self): def test_write_xml(self): """Test we can write a Version element to XML.""" test_xml = self.test_version_model.to_xml(encoding=str, skip_empty=True) - ( - self.assertEqual( - canonicalize(test_xml), - canonicalize(self.test_version_xml), - ), + self.assertEqual( + canonicalize(test_xml), + canonicalize(self.test_version_xml), ) @@ -55,7 +53,10 @@ class TestLanguageFeature(TestCase): """Tests the LanguageFeature model.""" test_language_feature_model = LanguageFeature(form="Formal notation", description="A description") - test_language_feature_xml = f"
Formal notation
A description
" + test_language_feature_xml = ( + f"
Formal notation
" + "A description
" + ) def test_read_from_xml(self): """Test reading a LanguageFeature element from XML.""" diff --git a/tests/voresource/voresource_models_test.py b/tests/voresource/voresource_models_test.py index edd10ad..387bcd1 100644 --- a/tests/voresource/voresource_models_test.py +++ b/tests/voresource/voresource_models_test.py @@ -520,7 +520,7 @@ class TestResource(TestCase): ) test_resource_xml = ( - '' + '' '0' "Example Resource" "example" @@ -612,7 +612,8 @@ class TestOrganization(TestCase): ) test_organization_xml = ( - '' + '' "Example Organization" "https://example.edu/" "" diff --git a/tests/vosi/capabilities_test.py b/tests/vosi/capabilities_test.py index 7ecee09..6ae36e7 100644 --- a/tests/vosi/capabilities_test.py +++ b/tests/vosi/capabilities_test.py @@ -1,6 +1,5 @@ """Tests for VOSI Capabilities models.""" -from datetime import timezone as tz from unittest import TestCase from xml.etree.ElementTree import canonicalize diff --git a/vo_models/uws/models.py b/vo_models/uws/models.py index fe708b2..11d741f 100644 --- a/vo_models/uws/models.py +++ b/vo_models/uws/models.py @@ -25,8 +25,9 @@ class Parameter(BaseXmlModel, tag="parameter", ns="uws", nsmap=NSMAP): Parameters: value: (content) - the value of the parameter. - by_reference: (attr) - If this attribute is true then the content of the parameter represents a URL to retrieve the - actual parameter value. + by_reference: + (attr) - If this attribute is true then the content of the parameter represents a URL to retrieve + the actual parameter value. id: (attr) - The identifier of the parameter. is_post: diff --git a/vo_models/vodataservice/models.py b/vo_models/vodataservice/models.py index 054fd24..0ac37c5 100644 --- a/vo_models/vodataservice/models.py +++ b/vo_models/vodataservice/models.py @@ -349,7 +349,8 @@ class InputParam(BaseParam): (attr) - An indication of whether this parameter is required to be provided for the application or service to work properly. std: - (attr) - If true, the meaning and behavior of this parameter is reserved and defined by a standard interface. + (attr) - If true, the meaning and behavior of this parameter is reserved and defined by a + standard interface. """ datatype: Optional[DataType] = element(tag="dataType", default=None) diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index 722588b..a6c6efe 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -145,7 +145,8 @@ class Contact(BaseXmlModel, nsmap=NSMAP): This can be a person's name, e.g. “John P. Jones” or a group, “Archive Support Team”. address: (element) - The contact mailing address. - All components of the mailing address are given in one string, e.g. “3700 San Martin Drive, Baltimore, MD 21218 USA”. + All components of the mailing address are given in one string, + e.g. “3700 San Martin Drive, Baltimore, MD 21218 USA”. email: (element) - The contact email address. telephone: @@ -211,7 +212,8 @@ class Relationship(BaseXmlModel, nsmap=NSMAP): Parameters: relationship_type: (element) - The named type of relationship - The value of relationshipType should be taken from the vocabulary at http://www.ivoa.net/rdf/voresource/relationship_type. + The value of relationshipType should be taken from the vocabulary at + http://www.ivoa.net/rdf/voresource/relationship_type. related_resource: (element) - the name of resource that this resource is related to. """ @@ -276,10 +278,12 @@ class Content(BaseXmlModel, nsmap=NSMAP): (element) - URL pointing to a human-readable document describing this resource. type: (element) - Nature or genre of the content of the resource. - Values for type should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_type + Values for type should be taken from the controlled vocabulary + http://www.ivoa.net/rdf/voresource/content_type content_level: (element) - Description of the content level or intended audience. - Values for contentLevel should be taken from the controlled vocabulary http://www.ivoa.net/rdf/voresource/content_level. + Values for contentLevel should be taken from the controlled vocabulary + http://www.ivoa.net/rdf/voresource/content_level. relationship: (element) - a description of a relationship to another resource. """ diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py index e3447af..fdd0cc8 100644 --- a/vo_models/vosi/capabilities/models.py +++ b/vo_models/vosi/capabilities/models.py @@ -1,6 +1,6 @@ """VOSICapabilities pydantic-xml models.""" -from typing import Any, Union +from typing import Union from pydantic_xml import BaseXmlModel, element From 252dd544ad4951dff428f0f61033e31ea5e42575 Mon Sep 17 00:00:00 2001 From: jwfraustro <36318163+jwfraustro@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:46:40 -0400 Subject: [PATCH 34/41] capabilities docs, cleanup --- README.md | 2 + docs/source/pages/api/vosi_api.rst | 8 ++ docs/source/pages/protocols/vosi.rst | 28 ++++- examples/snippets/vosi/capabilities.py | 166 +++++++++++++++++++++++++ vo_models/voresource/models.py | 2 + 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 examples/snippets/vosi/capabilities.py diff --git a/README.md b/README.md index 77337fa..63761de 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The following IVOA protocols are currently supported: - **VOSI (IVOA Support Interfaces) version 1.1** - VOSI Availability - VOSI Tables + - VOSI Capabilities - **VODataService version 1.2 (limited)** - DataType - FKColumn @@ -26,6 +27,7 @@ The following IVOA protocols are currently supported: - TableParam - TableSchema - TableSet + - others - **VOResource version 1.1** - **TAPRegExt version 1.0** diff --git a/docs/source/pages/api/vosi_api.rst b/docs/source/pages/api/vosi_api.rst index 2b8d574..71173bf 100644 --- a/docs/source/pages/api/vosi_api.rst +++ b/docs/source/pages/api/vosi_api.rst @@ -15,6 +15,14 @@ Tables ^^^^^^ .. automodule:: vo_models.vosi.tables.models + :members: + :no-inherited-members: + :exclude-members: model_config, model_fields, + +Capabilities +^^^^^^^^^^^^ + +.. automodule:: vo_models.vosi.capabilities.models :members: :no-inherited-members: :exclude-members: model_config, model_fields, \ No newline at end of file diff --git a/docs/source/pages/protocols/vosi.rst b/docs/source/pages/protocols/vosi.rst index abe58c8..c6aee49 100644 --- a/docs/source/pages/protocols/vosi.rst +++ b/docs/source/pages/protocols/vosi.rst @@ -8,7 +8,7 @@ VOSI (VO Support Interface) Availability ^^^^^^^^^^^^ -The Availability model is used to represent the response given by a UWS service to a +The Availability model is used to represent the response given by a service to a ``GET /availability`` request. .. grid:: 2 @@ -81,4 +81,28 @@ For requests to the ``GET /tables`` endpoint, you can use the ``TableSet`` model :language: xml :lines: 2- :start-after: tableset-xml-start - :end-before: tableset-xml-end \ No newline at end of file + :end-before: tableset-xml-end + +Capabilities +^^^^^^^^^^^^ + +The VOSICapabilities model is used to represent the response given by a service to a +``GET /capabilities`` request. Below is a relatively full example of a VOSI capabilities document for a TAP service. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/vosi/capabilities.py + :language: python + :start-after: capabilities-model-start + :end-before: capabilities-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/vosi/capabilities.py + :language: xml + :lines: 2- + :start-after: capabilities-xml-start + :end-before: capabilities-xml-end diff --git a/examples/snippets/vosi/capabilities.py b/examples/snippets/vosi/capabilities.py new file mode 100644 index 0000000..257f127 --- /dev/null +++ b/examples/snippets/vosi/capabilities.py @@ -0,0 +1,166 @@ +"""Example snippets for VOSI capabilities.""" + +from vo_models.tapregext.models import ( + DataLimit, + DataLimits, + Language, + LanguageFeature, + LanguageFeatureList, + OutputFormat, + TableAccess, + Version, +) +from vo_models.vodataservice.models import ParamHTTP +from vo_models.voresource.models import AccessURL, Capability, WebBrowser +from vo_models.vosi.capabilities.models import VOSICapabilities + +# pylint: disable=invalid-name + +# [capabilities-model-start] +vosi_capabilities_model = VOSICapabilities( + capability=[ + TableAccess( + type="tr:TableAccess", + interface=[ + ParamHTTP( + role="std", + version="1.1", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap")], + ) + ], + language=[ + Language( + name="ADQL", + version=[Version(value="2.0", ivo_id="ivo://ivoa.net/std/ADQL#v2.0")], + description="ADQL-2.0. Positional queries using CONTAINS with POINT and CIRCLE are supported.", + language_features=[ + LanguageFeatureList( + type="ivo://ivoa.net/std/TAPRegExt#features-adql-geo", + feature=[ + LanguageFeature(form="POINT"), + LanguageFeature(form="CIRCLE"), + ], + ), + ], + ) + ], + output_format=[ + OutputFormat( + mime="application/x-votable+xml", + alias=["votable"], + ivo_id="ivo://ivoa.net/std/TAPRegExt#output-votable-td", + ), + OutputFormat( + mime="text/csv;header=present", + alias=["csv"], + ), + ], + output_limit=DataLimits( + default=DataLimit(unit="row", value=100000), + hard=DataLimit(unit="row", value=100000), + ), + ), + Capability( + standard_id="ivo://ivoa.net/std/VOSI#capabilities", + interface=[ + ParamHTTP( + role="std", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/capabilities")], + ) + ], + ), + Capability( + standard_id="ivo://ivoa.net/std/VOSI#availability", + interface=[ + ParamHTTP( + role="std", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/availability")], + ) + ], + ), + Capability( + standard_id="ivo://ivoa.net/std/VOSI#tables", + interface=[ + ParamHTTP( + role="std", + version="1.1", + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/tables")], + ) + ], + ), + Capability( + standard_id="ivo://ivoa.net/std/DALI#examples", + interface=[ + WebBrowser( + access_url=[AccessURL(use="full", value="https://someservice.edu/tap/examples")], + ) + ], + ), + ] +) +# [capabilities-model-end] + +# [capabilities-xml-start] +capabilities_xml = """ + + + https://someservice.edu/tap + + + ADQL + 2.0 + + ADQL-2.0. Positional queries using CONTAINS with POINT and CIRCLE are supported. + + + +
POINT
+
+ +
CIRCLE
+
+
+
+ + application/x-votable+xml + votable + + + text/csv;header=present + csv + + + 100000 + 100000 + +
+ + + + https://someservice.edu/tap/capabilities + + + + + + + https://someservice.edu/tap/availability + + + + + + + https://someservice.edu/tap/tables + + + + + + + https://someservice.edu/tap/examples + + + +
+""" # [capabilities-xml-end] diff --git a/vo_models/voresource/models.py b/vo_models/voresource/models.py index a6c6efe..6278606 100644 --- a/vo_models/voresource/models.py +++ b/vo_models/voresource/models.py @@ -49,6 +49,8 @@ class ResourceName(BaseXmlModel, nsmap=NSMAP): Parameters: ivo_id: (attr) - The IVOA identifier for the resource referred to. + value: + (content) - The name of the resource. """ value: str From 38bd7ebf67e2179d9d05203035a1c34386e2d9cd Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Fri, 27 Sep 2024 11:59:27 -0400 Subject: [PATCH 35/41] bump version to dev release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3fc39b..d62e3fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "vo-models" -version = "0.4.0" +version = "0.4.0.dev0" authors = [ {name = "Joshua Fraustro", email="jfraustro@stsci.edu"}, {name = "MAST Archive Developers", email="archive@stsci.edu"} From 9cf467049b94f291b46a7d4ae13a9b084ba93423 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Mon, 30 Sep 2024 10:32:37 -0400 Subject: [PATCH 36/41] isort --- vo_models/tapregext/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vo_models/tapregext/models.py b/vo_models/tapregext/models.py index 296abb5..2b33f1b 100644 --- a/vo_models/tapregext/models.py +++ b/vo_models/tapregext/models.py @@ -4,8 +4,8 @@ from pydantic_xml import BaseXmlModel, attr, element -from vo_models.voresource.models import Capability from vo_models.voresource.models import NSMAP as VORESOURCE_NSMAP +from vo_models.voresource.models import Capability NSMAP = { "xs": "http://www.w3.org/2001/XMLSchema", From 0386b97a876f6fc306be6e4527651def8813b175 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Mon, 30 Sep 2024 10:33:28 -0400 Subject: [PATCH 37/41] update imports --- vo_models/vodataservice/__init__.py | 3 +++ vo_models/vosi/capabilities/__init__.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/vo_models/vodataservice/__init__.py b/vo_models/vodataservice/__init__.py index bdcd20d..fce3be9 100644 --- a/vo_models/vodataservice/__init__.py +++ b/vo_models/vodataservice/__init__.py @@ -1,8 +1,11 @@ """Module containing models and resources for IVOA VODataService objects.""" from vo_models.vodataservice.models import ( + BaseParam, DataType, FKColumn, ForeignKey, + InputParam, + ParamHTTP, Table, TableParam, TableSchema, diff --git a/vo_models/vosi/capabilities/__init__.py b/vo_models/vosi/capabilities/__init__.py index e69de29..dd46bce 100644 --- a/vo_models/vosi/capabilities/__init__.py +++ b/vo_models/vosi/capabilities/__init__.py @@ -0,0 +1,5 @@ +"""Module containing VOSI Capabilities classes.""" + +from vo_models.vosi.capabilities.models import ( + VOSICapabilities, +) From af107db65b64fd07b1d911ba7767eebbd7f1345c Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Mon, 30 Sep 2024 10:40:55 -0400 Subject: [PATCH 38/41] docs updates --- docs/source/pages/api/index.rst | 2 +- docs/source/pages/protocols/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/pages/api/index.rst b/docs/source/pages/api/index.rst index f400991..845abb1 100644 --- a/docs/source/pages/api/index.rst +++ b/docs/source/pages/api/index.rst @@ -1,6 +1,6 @@ .. _api: -Developer Documentation +API Reference ~~~~~~~~~~~~~~~~~~~~~~~ This section contains documentation on the package's modules and classes. diff --git a/docs/source/pages/protocols/index.rst b/docs/source/pages/protocols/index.rst index 11e507e..1f6a0d4 100644 --- a/docs/source/pages/protocols/index.rst +++ b/docs/source/pages/protocols/index.rst @@ -3,7 +3,7 @@ Supported Protocols ~~~~~~~~~~~~~~~~~~~ -The following IVOA protocols are currently supported: +The pages below contain some examples for each supported protocol. For a full list of all models, see the :ref:`api`. .. toctree:: :maxdepth: 3 From 5c9fdf11511923381c14b0e09258a64cccb1e1c1 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Mon, 30 Sep 2024 15:11:05 -0400 Subject: [PATCH 39/41] fix missing vodataservice namespace --- vo_models/vosi/capabilities/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vo_models/vosi/capabilities/models.py b/vo_models/vosi/capabilities/models.py index fdd0cc8..936c586 100644 --- a/vo_models/vosi/capabilities/models.py +++ b/vo_models/vosi/capabilities/models.py @@ -12,6 +12,7 @@ "vosi": "http://www.ivoa.net/xml/VOSICapabilities/v1.0", "xsd": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", + "vs": "http://www.ivoa.net/xml/VODataService/v1.1", } | VORESOURCE_NSMAP From 3921b08ee9b3d717af9f2dec1a285e3cc3fbe64b Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Mon, 30 Sep 2024 16:44:05 -0400 Subject: [PATCH 40/41] bump release for ns fix --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d62e3fa..5b3652e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "vo-models" -version = "0.4.0.dev0" +version = "0.4.0.dev1" authors = [ {name = "Joshua Fraustro", email="jfraustro@stsci.edu"}, {name = "MAST Archive Developers", email="archive@stsci.edu"} From 42e2e6a92f6c7594ceec4aa1c29deff4041ece05 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro Date: Wed, 2 Oct 2024 12:11:39 -0400 Subject: [PATCH 41/41] bump version for official release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5b3652e..e3fc39b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "vo-models" -version = "0.4.0.dev1" +version = "0.4.0" authors = [ {name = "Joshua Fraustro", email="jfraustro@stsci.edu"}, {name = "MAST Archive Developers", email="archive@stsci.edu"}