debian-zeep/src/zeep/xsd/types/builtins.py

558 lines
13 KiB
Python

import base64
import datetime
import math
import re
from decimal import Decimal as _Decimal
import isodate
import pytz
import six
from zeep.xsd.const import xsd_ns
from zeep.xsd.types.any import AnyType
from zeep.xsd.types.simple import AnySimpleType
class ParseError(ValueError):
pass
class BuiltinType(object):
def __init__(self, qname=None, is_global=False):
super(BuiltinType, self).__init__(qname, is_global=True)
def check_no_collection(func):
def _wrapper(self, value):
if isinstance(value, (list, dict, set)):
raise ValueError(
"The %s type doesn't accept collections as value" % (
self.__class__.__name__))
return func(self, value)
return _wrapper
##
# Primitive types
class String(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('string')
accepted_types = six.string_types
@check_no_collection
def xmlvalue(self, value):
if isinstance(value, bytes):
return value.decode('utf-8')
return six.text_type(value if value is not None else '')
def pythonvalue(self, value):
return value
class Boolean(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('boolean')
accepted_types = (bool,)
@check_no_collection
def xmlvalue(self, value):
return 'true' if value and value not in ('false', '0') else 'false'
def pythonvalue(self, value):
"""Return True if the 'true' or '1'. 'false' and '0' are legal false
values, but we consider everything not true as false.
"""
return value in ('true', '1')
class Decimal(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('decimal')
accepted_types = (_Decimal, float) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return _Decimal(value)
class Float(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('float')
accepted_types = (float, _Decimal) + six.string_types
def xmlvalue(self, value):
return str(value).upper()
def pythonvalue(self, value):
return float(value)
class Double(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('double')
accepted_types = (_Decimal, float) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return float(value)
class Duration(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('duration')
accepted_types = (isodate.duration.Duration,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return isodate.duration_isoformat(value)
def pythonvalue(self, value):
if value.startswith('PT-'):
value = value.replace('PT-', 'PT')
result = isodate.parse_duration(value)
return datetime.timedelta(0 - result.total_seconds())
else:
return isodate.parse_duration(value)
class DateTime(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('dateTime')
accepted_types = (datetime.datetime,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
# Bit of a hack, since datetime is a subclass of date we can't just
# test it with an isinstance(). And actually, we should not really
# care about the type, as long as it has the required attributes
if not all(hasattr(value, attr) for attr in ('hour', 'minute', 'second')):
value = datetime.datetime.combine(value, datetime.time(
getattr(value, 'hour', 0),
getattr(value, 'minute', 0),
getattr(value, 'second', 0)))
if getattr(value, 'microsecond', 0):
return isodate.isostrf.strftime(value, '%Y-%m-%dT%H:%M:%S.%f%Z')
return isodate.isostrf.strftime(value, '%Y-%m-%dT%H:%M:%S%Z')
def pythonvalue(self, value):
return isodate.parse_datetime(value)
class Time(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('time')
accepted_types = (datetime.time,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
if isinstance(value, six.string_types):
return value
if value.microsecond:
return isodate.isostrf.strftime(value, '%H:%M:%S.%f%Z')
return isodate.isostrf.strftime(value, '%H:%M:%S%Z')
def pythonvalue(self, value):
return isodate.parse_time(value)
class Date(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('date')
accepted_types = (datetime.date,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
if isinstance(value, six.string_types):
return value
return isodate.isostrf.strftime(value, '%Y-%m-%d')
def pythonvalue(self, value):
return isodate.parse_date(value)
class gYearMonth(BuiltinType, AnySimpleType):
"""gYearMonth represents a specific gregorian month in a specific gregorian
year.
Lexical representation: CCYY-MM
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gYearMonth')
_pattern = re.compile(
r'^(?P<year>-?\d{4,})-(?P<month>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
year, month, tzinfo = value
return '%04d-%02d%s' % (year, month, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (
int(group['year']), int(group['month']),
_parse_timezone(group['timezone']))
class gYear(BuiltinType, AnySimpleType):
"""gYear represents a gregorian calendar year.
Lexical representation: CCYY
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gYear')
_pattern = re.compile(r'^(?P<year>-?\d{4,})(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
year, tzinfo = value
return '%04d%s' % (year, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['year']), _parse_timezone(group['timezone']))
class gMonthDay(BuiltinType, AnySimpleType):
"""gMonthDay is a gregorian date that recurs, specifically a day of the
year such as the third of May.
Lexical representation: --MM-DD
"""
accepted_types = (datetime.date, ) + six.string_types
_default_qname = xsd_ns('gMonthDay')
_pattern = re.compile(
r'^--(?P<month>\d\d)-(?P<day>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
month, day, tzinfo = value
return '--%02d-%02d%s' % (month, day, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (
int(group['month']), int(group['day']),
_parse_timezone(group['timezone']))
class gDay(BuiltinType, AnySimpleType):
"""gDay is a gregorian day that recurs, specifically a day of the month
such as the 5th of the month
Lexical representation: ---DD
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gDay')
_pattern = re.compile(r'^---(?P<day>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
day, tzinfo = value
return '---%02d%s' % (day, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['day']), _parse_timezone(group['timezone']))
class gMonth(BuiltinType, AnySimpleType):
"""gMonth is a gregorian month that recurs every year.
Lexical representation: --MM
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gMonth')
_pattern = re.compile(r'^--(?P<month>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
month, tzinfo = value
return '--%d%s' % (month, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['month']), _parse_timezone(group['timezone']))
class HexBinary(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('hexBinary')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class Base64Binary(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('base64Binary')
@check_no_collection
def xmlvalue(self, value):
return base64.b64encode(value)
def pythonvalue(self, value):
return base64.b64decode(value)
class AnyURI(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('anyURI')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class QName(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('QName')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class Notation(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('NOTATION')
##
# Derived datatypes
class NormalizedString(String):
_default_qname = xsd_ns('normalizedString')
class Token(NormalizedString):
_default_qname = xsd_ns('token')
class Language(Token):
_default_qname = xsd_ns('language')
class NmToken(Token):
_default_qname = xsd_ns('NMTOKEN')
class NmTokens(NmToken):
_default_qname = xsd_ns('NMTOKENS')
class Name(Token):
_default_qname = xsd_ns('Name')
class NCName(Name):
_default_qname = xsd_ns('NCName')
class ID(NCName):
_default_qname = xsd_ns('ID')
class IDREF(NCName):
_default_qname = xsd_ns('IDREF')
class IDREFS(IDREF):
_default_qname = xsd_ns('IDREFS')
class Entity(NCName):
_default_qname = xsd_ns('ENTITY')
class Entities(Entity):
_default_qname = xsd_ns('ENTITIES')
class Integer(Decimal):
_default_qname = xsd_ns('integer')
accepted_types = (int, float) + six.string_types
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return int(value)
class NonPositiveInteger(Integer):
_default_qname = xsd_ns('nonPositiveInteger')
class NegativeInteger(Integer):
_default_qname = xsd_ns('negativeInteger')
class Long(Integer):
_default_qname = xsd_ns('long')
def pythonvalue(self, value):
return long(value) if six.PY2 else int(value) # noqa
class Int(Long):
_default_qname = xsd_ns('int')
class Short(Int):
_default_qname = xsd_ns('short')
class Byte(Short):
"""A signed 8-bit integer"""
_default_qname = xsd_ns('byte')
class NonNegativeInteger(Integer):
_default_qname = xsd_ns('nonNegativeInteger')
class UnsignedLong(NonNegativeInteger):
_default_qname = xsd_ns('unsignedLong')
class UnsignedInt(UnsignedLong):
_default_qname = xsd_ns('unsignedInt')
class UnsignedShort(UnsignedInt):
_default_qname = xsd_ns('unsignedShort')
class UnsignedByte(UnsignedShort):
_default_qname = xsd_ns('unsignedByte')
class PositiveInteger(NonNegativeInteger):
_default_qname = xsd_ns('positiveInteger')
##
# Other
def _parse_timezone(val):
"""Return a pytz.tzinfo object"""
if not val:
return
if val == 'Z' or val == '+00:00':
return pytz.utc
negative = val.startswith('-')
minutes = int(val[-2:])
minutes += int(val[1:3]) * 60
if negative:
minutes = 0 - minutes
return pytz.FixedOffset(minutes)
def _unparse_timezone(tzinfo):
if not tzinfo:
return ''
if tzinfo == pytz.utc:
return 'Z'
hours = math.floor(tzinfo._minutes / 60)
minutes = tzinfo._minutes % 60
if hours > 0:
return '+%02d:%02d' % (hours, minutes)
return '-%02d:%02d' % (abs(hours), minutes)
_types = [
# Primitive
String,
Boolean,
Decimal,
Float,
Double,
Duration,
DateTime,
Time,
Date,
gYearMonth,
gYear,
gMonthDay,
gDay,
gMonth,
HexBinary,
Base64Binary,
AnyURI,
QName,
Notation,
# Derived
NormalizedString,
Token,
Language,
NmToken,
NmTokens,
Name,
NCName,
ID,
IDREF,
IDREFS,
Entity,
Entities,
Integer,
NonPositiveInteger, # noqa
NegativeInteger,
Long,
Int,
Short,
Byte,
NonNegativeInteger, # noqa
UnsignedByte,
UnsignedInt,
UnsignedLong,
UnsignedShort,
PositiveInteger,
# Other
AnyType,
AnySimpleType,
]
default_types = {
cls._default_qname: cls(is_global=True) for cls in _types
}