mirror of
https://github.com/ansible/awx.git
synced 2026-05-14 21:07:39 -02:30
Vendor pywinrm (and dependencies).
https://trello.com/c/FQ9AkmRV/46-install-vendored-pywinrm-for-ansible-us e
This commit is contained in:
55
awx/lib/site-packages/isodate/__init__.py
Normal file
55
awx/lib/site-packages/isodate/__init__.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Import all essential functions and constants to re-export them here for easy
|
||||||
|
access.
|
||||||
|
|
||||||
|
This module contains also various pre-defined ISO 8601 format strings.
|
||||||
|
'''
|
||||||
|
from isodate.isodates import parse_date, date_isoformat
|
||||||
|
from isodate.isotime import parse_time, time_isoformat
|
||||||
|
from isodate.isodatetime import parse_datetime, datetime_isoformat
|
||||||
|
from isodate.isoduration import parse_duration, duration_isoformat, Duration
|
||||||
|
from isodate.isoerror import ISO8601Error
|
||||||
|
from isodate.isotzinfo import parse_tzinfo, tz_isoformat
|
||||||
|
from isodate.tzinfo import UTC, FixedOffset, LOCAL
|
||||||
|
from isodate.duration import Duration
|
||||||
|
from isodate.isostrf import strftime
|
||||||
|
from isodate.isostrf import DATE_BAS_COMPLETE, DATE_BAS_ORD_COMPLETE
|
||||||
|
from isodate.isostrf import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE
|
||||||
|
from isodate.isostrf import DATE_CENTURY, DATE_EXT_COMPLETE
|
||||||
|
from isodate.isostrf import DATE_EXT_ORD_COMPLETE, DATE_EXT_WEEK
|
||||||
|
from isodate.isostrf import DATE_EXT_WEEK_COMPLETE, DATE_MONTH, DATE_YEAR
|
||||||
|
from isodate.isostrf import TIME_BAS_COMPLETE, TIME_BAS_MINUTE
|
||||||
|
from isodate.isostrf import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
|
||||||
|
from isodate.isostrf import TIME_HOUR
|
||||||
|
from isodate.isostrf import TZ_BAS, TZ_EXT, TZ_HOUR
|
||||||
|
from isodate.isostrf import DT_BAS_COMPLETE, DT_EXT_COMPLETE
|
||||||
|
from isodate.isostrf import DT_BAS_ORD_COMPLETE, DT_EXT_ORD_COMPLETE
|
||||||
|
from isodate.isostrf import DT_BAS_WEEK_COMPLETE, DT_EXT_WEEK_COMPLETE
|
||||||
|
from isodate.isostrf import D_DEFAULT, D_WEEK, D_ALT_EXT, D_ALT_BAS
|
||||||
|
from isodate.isostrf import D_ALT_BAS_ORD, D_ALT_EXT_ORD
|
||||||
280
awx/lib/site-packages/isodate/duration.py
Normal file
280
awx/lib/site-packages/isodate/duration.py
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This module defines a Duration class.
|
||||||
|
|
||||||
|
The class Duration allows to define durations in years and months and can be
|
||||||
|
used as limited replacement for timedelta objects.
|
||||||
|
'''
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from decimal import Decimal, ROUND_FLOOR
|
||||||
|
|
||||||
|
|
||||||
|
def fquotmod(val, low, high):
|
||||||
|
'''
|
||||||
|
A divmod function with boundaries.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# assumes that all the maths is done with Decimals.
|
||||||
|
# divmod for Decimal uses truncate instead of floor as builtin divmod, so we have
|
||||||
|
# to do it manually here.
|
||||||
|
a, b = val - low, high - low
|
||||||
|
div = (a / b).to_integral(ROUND_FLOOR)
|
||||||
|
mod = a - div * b
|
||||||
|
# if we were not usig Decimal, it would look like this.
|
||||||
|
#div, mod = divmod(val - low, high - low)
|
||||||
|
mod += low
|
||||||
|
return int(div), mod
|
||||||
|
|
||||||
|
|
||||||
|
def max_days_in_month(year, month):
|
||||||
|
'''
|
||||||
|
Determines the number of days of a specific month in a specific year.
|
||||||
|
'''
|
||||||
|
if month in (1, 3, 5, 7, 8, 10, 12):
|
||||||
|
return 31
|
||||||
|
if month in (4, 6, 9, 11):
|
||||||
|
return 30
|
||||||
|
if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0):
|
||||||
|
return 29
|
||||||
|
return 28
|
||||||
|
|
||||||
|
|
||||||
|
class Duration(object):
|
||||||
|
'''
|
||||||
|
A class which represents a duration.
|
||||||
|
|
||||||
|
The difference to datetime.timedelta is, that this class handles also
|
||||||
|
differences given in years and months.
|
||||||
|
A Duration treats differences given in year, months separately from all
|
||||||
|
other components.
|
||||||
|
|
||||||
|
A Duration can be used almost like any timedelta object, however there
|
||||||
|
are some restrictions:
|
||||||
|
* It is not really possible to compare Durations, because it is unclear,
|
||||||
|
whether a duration of 1 year is bigger than 365 days or not.
|
||||||
|
* Equality is only tested between the two (year, month vs. timedelta)
|
||||||
|
basic components.
|
||||||
|
|
||||||
|
A Duration can also be converted into a datetime object, but this requires
|
||||||
|
a start date or an end date.
|
||||||
|
|
||||||
|
The algorithm to add a duration to a date is defined at
|
||||||
|
http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0,
|
||||||
|
minutes=0, hours=0, weeks=0, months=0, years=0):
|
||||||
|
'''
|
||||||
|
Initialise this Duration instance with the given parameters.
|
||||||
|
'''
|
||||||
|
if not isinstance(months, Decimal):
|
||||||
|
months = Decimal(str(months))
|
||||||
|
if not isinstance(years, Decimal):
|
||||||
|
years = Decimal(str(years))
|
||||||
|
self.months = months
|
||||||
|
self.years = years
|
||||||
|
self.tdelta = timedelta(days, seconds, microseconds, milliseconds,
|
||||||
|
minutes, hours, weeks)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
'''
|
||||||
|
Provide direct access to attributes of included timedelta instance.
|
||||||
|
'''
|
||||||
|
return getattr(self.tdelta, name)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
'''
|
||||||
|
Return a string representation of this duration similar to timedelta.
|
||||||
|
'''
|
||||||
|
params = []
|
||||||
|
if self.years:
|
||||||
|
params.append('%d years' % self.years)
|
||||||
|
if self.months:
|
||||||
|
params.append('%d months' % self.months)
|
||||||
|
params.append(str(self.tdelta))
|
||||||
|
return ', '.join(params)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
'''
|
||||||
|
Return a string suitable for repr(x) calls.
|
||||||
|
'''
|
||||||
|
return "%s.%s(%d, %d, %d, years=%d, months=%d)" % (
|
||||||
|
self.__class__.__module__, self.__class__.__name__,
|
||||||
|
self.tdelta.days, self.tdelta.seconds,
|
||||||
|
self.tdelta.microseconds, self.years, self.months)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""
|
||||||
|
A simple unary minus.
|
||||||
|
|
||||||
|
Returns a new Duration instance with all it's negated.
|
||||||
|
"""
|
||||||
|
negduration = Duration(years=-self.years, months=-self.months)
|
||||||
|
negduration.tdelta = -self.tdelta
|
||||||
|
return negduration
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
'''
|
||||||
|
Durations can be added with Duration, timedelta, date and datetime
|
||||||
|
objects.
|
||||||
|
'''
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
newduration = Duration(years=self.years, months=self.months)
|
||||||
|
newduration.tdelta = self.tdelta + other
|
||||||
|
return newduration
|
||||||
|
if isinstance(other, Duration):
|
||||||
|
newduration = Duration(years=self.years + other.years,
|
||||||
|
months=self.months + other.months)
|
||||||
|
newduration.tdelta = self.tdelta + other.tdelta
|
||||||
|
return newduration
|
||||||
|
if isinstance(other, (date, datetime)):
|
||||||
|
if (not( float(self.years).is_integer() and float(self.months).is_integer())):
|
||||||
|
raise ValueError('fractional years or months not supported for date calculations')
|
||||||
|
newmonth = other.month + self.months
|
||||||
|
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||||
|
newyear = other.year + self.years + carry
|
||||||
|
maxdays = max_days_in_month(newyear, newmonth)
|
||||||
|
if other.day > maxdays:
|
||||||
|
newday = maxdays
|
||||||
|
else:
|
||||||
|
newday = other.day
|
||||||
|
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||||
|
return self.tdelta + newdt
|
||||||
|
raise TypeError('unsupported operand type(s) for +: %s and %s' %
|
||||||
|
(self.__class__, other.__class__))
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
'''
|
||||||
|
Add durations to timedelta, date and datetime objects.
|
||||||
|
'''
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
newduration = Duration(years=self.years, months=self.months)
|
||||||
|
newduration.tdelta = self.tdelta + other
|
||||||
|
return newduration
|
||||||
|
if isinstance(other, (date, datetime)):
|
||||||
|
if (not( float(self.years).is_integer() and float(self.months).is_integer())):
|
||||||
|
raise ValueError('fractional years or months not supported for date calculations')
|
||||||
|
newmonth = other.month + self.months
|
||||||
|
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||||
|
newyear = other.year + self.years + carry
|
||||||
|
maxdays = max_days_in_month(newyear, newmonth)
|
||||||
|
if other.day > maxdays:
|
||||||
|
newday = maxdays
|
||||||
|
else:
|
||||||
|
newday = other.day
|
||||||
|
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||||
|
return newdt + self.tdelta
|
||||||
|
raise TypeError('unsupported operand type(s) for +: %s and %s' %
|
||||||
|
(other.__class__, self.__class__))
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
'''
|
||||||
|
It is possible to subtract Duration and timedelta objects from Duration
|
||||||
|
objects.
|
||||||
|
'''
|
||||||
|
if isinstance(other, Duration):
|
||||||
|
newduration = Duration(years=self.years - other.years,
|
||||||
|
months=self.months - other.months)
|
||||||
|
newduration.tdelta = self.tdelta - other.tdelta
|
||||||
|
return newduration
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
newduration = Duration(years=self.years, months=self.months)
|
||||||
|
newduration.tdelta = self.tdelta - other
|
||||||
|
return newduration
|
||||||
|
raise TypeError('unsupported operand type(s) for -: %s and %s' %
|
||||||
|
(self.__class__, other.__class__))
|
||||||
|
|
||||||
|
def __rsub__(self, other):
|
||||||
|
'''
|
||||||
|
It is possible to subtract Duration objecs from date, datetime and
|
||||||
|
timedelta objects.
|
||||||
|
'''
|
||||||
|
#print '__rsub__:', self, other
|
||||||
|
if isinstance(other, (date, datetime)):
|
||||||
|
if (not( float(self.years).is_integer() and float(self.months).is_integer())):
|
||||||
|
raise ValueError('fractional years or months not supported for date calculations')
|
||||||
|
newmonth = other.month - self.months
|
||||||
|
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||||
|
newyear = other.year - self.years + carry
|
||||||
|
maxdays = max_days_in_month(newyear, newmonth)
|
||||||
|
if other.day > maxdays:
|
||||||
|
newday = maxdays
|
||||||
|
else:
|
||||||
|
newday = other.day
|
||||||
|
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||||
|
return newdt - self.tdelta
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
tmpdur = Duration()
|
||||||
|
tmpdur.tdelta = other
|
||||||
|
return tmpdur - self
|
||||||
|
raise TypeError('unsupported operand type(s) for -: %s and %s' %
|
||||||
|
(other.__class__, self.__class__))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
'''
|
||||||
|
If the years, month part and the timedelta part are both equal, then
|
||||||
|
the two Durations are considered equal.
|
||||||
|
'''
|
||||||
|
if (isinstance(other, timedelta) and
|
||||||
|
self.years == 0 and self.months == 0):
|
||||||
|
return self.tdelta == other
|
||||||
|
if not isinstance(other, Duration):
|
||||||
|
return NotImplemented
|
||||||
|
if ((self.years * 12 + self.months) ==
|
||||||
|
(other.years * 12 + other.months) and self.tdelta == other.tdelta):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
'''
|
||||||
|
If the years, month part or the timedelta part is not equal, then
|
||||||
|
the two Durations are considered not equal.
|
||||||
|
'''
|
||||||
|
if isinstance(other, timedelta) and self.years == 0 and self.months == 0:
|
||||||
|
return self.tdelta != other
|
||||||
|
if not isinstance(other, Duration):
|
||||||
|
return NotImplemented
|
||||||
|
if ((self.years * 12 + self.months) !=
|
||||||
|
(other.years * 12 + other.months) or self.tdelta != other.tdelta):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def totimedelta(self, start=None, end=None):
|
||||||
|
'''
|
||||||
|
Convert this duration into a timedelta object.
|
||||||
|
|
||||||
|
This method requires a start datetime or end datetimem, but raises
|
||||||
|
an exception if both are given.
|
||||||
|
'''
|
||||||
|
if start is None and end is None:
|
||||||
|
raise ValueError("start or end required")
|
||||||
|
if start is not None and end is not None:
|
||||||
|
raise ValueError("only start or end allowed")
|
||||||
|
if start is not None:
|
||||||
|
return (start + self) - start
|
||||||
|
return end - (end - self)
|
||||||
201
awx/lib/site-packages/isodate/isodates.py
Normal file
201
awx/lib/site-packages/isodate/isodates.py
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This modules provides a method to parse an ISO 8601:2004 date string to a
|
||||||
|
python datetime.date instance.
|
||||||
|
|
||||||
|
It supports all basic, extended and expanded formats as described in the ISO
|
||||||
|
standard. The only limitations it has, are given by the Python datetime.date
|
||||||
|
implementation, which does not support dates before 0001-01-01.
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from isodate.isostrf import strftime, DATE_EXT_COMPLETE
|
||||||
|
from isodate.isoerror import ISO8601Error
|
||||||
|
|
||||||
|
DATE_REGEX_CACHE = {}
|
||||||
|
# A dictionary to cache pre-compiled regular expressions.
|
||||||
|
# A set of regular expressions is identified, by number of year digits allowed
|
||||||
|
# and whether a plus/minus sign is required or not. (This option is changeable
|
||||||
|
# only for 4 digit years).
|
||||||
|
|
||||||
|
def build_date_regexps(yeardigits=4, expanded=False):
|
||||||
|
'''
|
||||||
|
Compile set of regular expressions to parse ISO dates. The expressions will
|
||||||
|
be created only if they are not already in REGEX_CACHE.
|
||||||
|
|
||||||
|
It is necessary to fix the number of year digits, else it is not possible
|
||||||
|
to automatically distinguish between various ISO date formats.
|
||||||
|
|
||||||
|
ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/-
|
||||||
|
sign is required (expanded format). To support +/- sign for 4 digit years,
|
||||||
|
the expanded parameter needs to be set to True.
|
||||||
|
'''
|
||||||
|
if yeardigits != 4:
|
||||||
|
expanded = True
|
||||||
|
if (yeardigits, expanded) not in DATE_REGEX_CACHE:
|
||||||
|
cache_entry = []
|
||||||
|
# ISO 8601 expanded DATE formats allow an arbitrary number of year
|
||||||
|
# digits with a leading +/- sign.
|
||||||
|
if expanded:
|
||||||
|
sign = 1
|
||||||
|
else:
|
||||||
|
sign = 0
|
||||||
|
# 1. complete dates:
|
||||||
|
# YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# YYYYMMDD or +- YYYYYYMMDD... basic date format
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 2. complete week dates:
|
||||||
|
# YYYY-Www-D or +-YYYYYY-Www-D ... extended week date
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# YYYYWwwD or +-YYYYYYWwwD ... basic week date
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
|
||||||
|
r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 3. ordinal dates:
|
||||||
|
# YYYY-DDD or +-YYYYYY-DDD ... extended format
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"-(?P<day>[0-9]{3})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# YYYYDDD or +-YYYYYYDDD ... basic format
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"(?P<day>[0-9]{3})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 4. week dates:
|
||||||
|
# YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"-W(?P<week>[0-9]{2})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
|
||||||
|
r"(?P<week>[0-9]{2})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 5. month dates:
|
||||||
|
# YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
r"-(?P<month>[0-9]{2})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 6. year dates:
|
||||||
|
# YYYY or +-YYYYYY ... reduced accuracy specific year
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
|
||||||
|
% (sign, yeardigits)))
|
||||||
|
# 7. century dates:
|
||||||
|
# YY or +-YYYY ... reduced accuracy specific century
|
||||||
|
cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}"
|
||||||
|
r"(?P<century>[0-9]{%d})"
|
||||||
|
% (sign, yeardigits - 2)))
|
||||||
|
|
||||||
|
DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry
|
||||||
|
return DATE_REGEX_CACHE[(yeardigits, expanded)]
|
||||||
|
|
||||||
|
def parse_date(datestring, yeardigits=4, expanded=False):
|
||||||
|
'''
|
||||||
|
Parse an ISO 8601 date string into a datetime.date object.
|
||||||
|
|
||||||
|
As the datetime.date implementation is limited to dates starting from
|
||||||
|
0001-01-01, negative dates (BC) and year 0 can not be parsed by this
|
||||||
|
method.
|
||||||
|
|
||||||
|
For incomplete dates, this method chooses the first day for it. For
|
||||||
|
instance if only a century is given, this method returns the 1st of
|
||||||
|
January in year 1 of this century.
|
||||||
|
|
||||||
|
supported formats: (expanded formats are shown with 6 digits for year)
|
||||||
|
YYYYMMDD +-YYYYYYMMDD basic complete date
|
||||||
|
YYYY-MM-DD +-YYYYYY-MM-DD extended complete date
|
||||||
|
YYYYWwwD +-YYYYYYWwwD basic complete week date
|
||||||
|
YYYY-Www-D +-YYYYYY-Www-D extended complete week date
|
||||||
|
YYYYDDD +-YYYYYYDDD basic ordinal date
|
||||||
|
YYYY-DDD +-YYYYYY-DDD extended ordinal date
|
||||||
|
YYYYWww +-YYYYYYWww basic incomplete week date
|
||||||
|
YYYY-Www +-YYYYYY-Www extended incomplete week date
|
||||||
|
YYY-MM +-YYYYYY-MM incomplete month date
|
||||||
|
YYYY +-YYYYYY incomplete year date
|
||||||
|
YY +-YYYY incomplete century date
|
||||||
|
|
||||||
|
@param datestring: the ISO date string to parse
|
||||||
|
@param yeardigits: how many digits are used to represent a year
|
||||||
|
@param expanded: if True then +/- signs are allowed. This parameter
|
||||||
|
is forced to True, if yeardigits != 4
|
||||||
|
|
||||||
|
@return: a datetime.date instance represented by datestring
|
||||||
|
@raise ISO8601Error: if this function can not parse the datestring
|
||||||
|
@raise ValueError: if datestring can not be represented by datetime.date
|
||||||
|
'''
|
||||||
|
if yeardigits != 4:
|
||||||
|
expanded = True
|
||||||
|
isodates = build_date_regexps(yeardigits, expanded)
|
||||||
|
for pattern in isodates:
|
||||||
|
match = pattern.match(datestring)
|
||||||
|
if match:
|
||||||
|
groups = match.groupdict()
|
||||||
|
# sign, century, year, month, week, day,
|
||||||
|
# FIXME: negative dates not possible with python standard types
|
||||||
|
sign = (groups['sign'] == '-' and -1) or 1
|
||||||
|
if 'century' in groups:
|
||||||
|
return date(sign * (int(groups['century']) * 100 + 1), 1, 1)
|
||||||
|
if not 'month' in groups: # weekdate or ordinal date
|
||||||
|
ret = date(sign * int(groups['year']), 1, 1)
|
||||||
|
if 'week' in groups:
|
||||||
|
isotuple = ret.isocalendar()
|
||||||
|
if 'day' in groups:
|
||||||
|
days = int(groups['day'] or 1)
|
||||||
|
else:
|
||||||
|
days = 1
|
||||||
|
# if first week in year, do weeks-1
|
||||||
|
return ret + timedelta(weeks=int(groups['week']) -
|
||||||
|
(((isotuple[1] == 1) and 1) or 0),
|
||||||
|
days = -isotuple[2] + days)
|
||||||
|
elif 'day' in groups: # ordinal date
|
||||||
|
return ret + timedelta(days=int(groups['day'])-1)
|
||||||
|
else: # year date
|
||||||
|
return ret
|
||||||
|
# year-, month-, or complete date
|
||||||
|
if 'day' not in groups or groups['day'] is None:
|
||||||
|
day = 1
|
||||||
|
else:
|
||||||
|
day = int(groups['day'])
|
||||||
|
return date(sign * int(groups['year']),
|
||||||
|
int(groups['month']) or 1, day)
|
||||||
|
raise ISO8601Error('Unrecognised ISO 8601 date format: %r' % datestring)
|
||||||
|
|
||||||
|
def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4):
|
||||||
|
'''
|
||||||
|
Format date strings.
|
||||||
|
|
||||||
|
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||||
|
Date-Extended-Complete as default format.
|
||||||
|
'''
|
||||||
|
return strftime(tdate, format, yeardigits)
|
||||||
61
awx/lib/site-packages/isodate/isodatetime.py
Normal file
61
awx/lib/site-packages/isodate/isodatetime.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This module defines a method to parse an ISO 8601:2004 date time string.
|
||||||
|
|
||||||
|
For this job it uses the parse_date and parse_time methods defined in date
|
||||||
|
and time module.
|
||||||
|
'''
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from isodate.isostrf import strftime
|
||||||
|
from isodate.isostrf import DATE_EXT_COMPLETE, TIME_EXT_COMPLETE, TZ_EXT
|
||||||
|
from isodate.isodates import parse_date
|
||||||
|
from isodate.isotime import parse_time
|
||||||
|
|
||||||
|
def parse_datetime(datetimestring):
|
||||||
|
'''
|
||||||
|
Parses ISO 8601 date-times into datetime.datetime objects.
|
||||||
|
|
||||||
|
This function uses parse_date and parse_time to do the job, so it allows
|
||||||
|
more combinations of date and time representations, than the actual
|
||||||
|
ISO 8601:2004 standard allows.
|
||||||
|
'''
|
||||||
|
datestring, timestring = datetimestring.split('T')
|
||||||
|
tmpdate = parse_date(datestring)
|
||||||
|
tmptime = parse_time(timestring)
|
||||||
|
return datetime.combine(tmpdate, tmptime)
|
||||||
|
|
||||||
|
def datetime_isoformat(tdt, format=DATE_EXT_COMPLETE + 'T' +
|
||||||
|
TIME_EXT_COMPLETE + TZ_EXT):
|
||||||
|
'''
|
||||||
|
Format datetime strings.
|
||||||
|
|
||||||
|
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||||
|
Extended-Complete as default format.
|
||||||
|
'''
|
||||||
|
return strftime(tdt, format)
|
||||||
145
awx/lib/site-packages/isodate/isoduration.py
Normal file
145
awx/lib/site-packages/isodate/isoduration.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This module provides an ISO 8601:2004 duration parser.
|
||||||
|
|
||||||
|
It also provides a wrapper to strftime. This wrapper makes it easier to
|
||||||
|
format timedelta or Duration instances as ISO conforming strings.
|
||||||
|
'''
|
||||||
|
from datetime import timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
import re
|
||||||
|
|
||||||
|
from isodate.duration import Duration
|
||||||
|
from isodate.isoerror import ISO8601Error
|
||||||
|
from isodate.isodatetime import parse_datetime
|
||||||
|
from isodate.isostrf import strftime, D_DEFAULT
|
||||||
|
|
||||||
|
ISO8601_PERIOD_REGEX = re.compile(r"^(?P<sign>[+-])?"
|
||||||
|
r"P(?P<years>[0-9]+([,.][0-9]+)?Y)?"
|
||||||
|
r"(?P<months>[0-9]+([,.][0-9]+)?M)?"
|
||||||
|
r"(?P<weeks>[0-9]+([,.][0-9]+)?W)?"
|
||||||
|
r"(?P<days>[0-9]+([,.][0-9]+)?D)?"
|
||||||
|
r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?"
|
||||||
|
r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?"
|
||||||
|
r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$")
|
||||||
|
# regular expression to parse ISO duartion strings.
|
||||||
|
|
||||||
|
|
||||||
|
def parse_duration(datestring):
|
||||||
|
"""
|
||||||
|
Parses an ISO 8601 durations into datetime.timedelta or Duration objects.
|
||||||
|
|
||||||
|
If the ISO date string does not contain years or months, a timedelta
|
||||||
|
instance is returned, else a Duration instance is returned.
|
||||||
|
|
||||||
|
The following duration formats are supported:
|
||||||
|
-PnnW duration in weeks
|
||||||
|
-PnnYnnMnnDTnnHnnMnnS complete duration specification
|
||||||
|
-PYYYYMMDDThhmmss basic alternative complete date format
|
||||||
|
-PYYYY-MM-DDThh:mm:ss extended alternative complete date format
|
||||||
|
-PYYYYDDDThhmmss basic alternative ordinal date format
|
||||||
|
-PYYYY-DDDThh:mm:ss extended alternative ordinal date format
|
||||||
|
|
||||||
|
The '-' is optional.
|
||||||
|
|
||||||
|
Limitations: ISO standard defines some restrictions about where to use
|
||||||
|
fractional numbers and which component and format combinations are
|
||||||
|
allowed. This parser implementation ignores all those restrictions and
|
||||||
|
returns something when it is able to find all necessary components.
|
||||||
|
In detail:
|
||||||
|
it does not check, whether only the last component has fractions.
|
||||||
|
it allows weeks specified with all other combinations
|
||||||
|
|
||||||
|
The alternative format does not support durations with years, months or
|
||||||
|
days set to 0.
|
||||||
|
"""
|
||||||
|
if not isinstance(datestring, basestring):
|
||||||
|
raise TypeError("Expecting a string %r" % datestring)
|
||||||
|
match = ISO8601_PERIOD_REGEX.match(datestring)
|
||||||
|
if not match:
|
||||||
|
# try alternative format:
|
||||||
|
if datestring.startswith("P"):
|
||||||
|
durdt = parse_datetime(datestring[1:])
|
||||||
|
if durdt.year != 0 or durdt.month != 0:
|
||||||
|
# create Duration
|
||||||
|
ret = Duration(days=durdt.day, seconds=durdt.second,
|
||||||
|
microseconds=durdt.microsecond,
|
||||||
|
minutes=durdt.minute, hours=durdt.hour,
|
||||||
|
months=durdt.month, years=durdt.year)
|
||||||
|
else: # FIXME: currently not possible in alternative format
|
||||||
|
# create timedelta
|
||||||
|
ret = timedelta(days=durdt.day, seconds=durdt.second,
|
||||||
|
microseconds=durdt.microsecond,
|
||||||
|
minutes=durdt.minute, hours=durdt.hour)
|
||||||
|
return ret
|
||||||
|
raise ISO8601Error("Unable to parse duration string %r" % datestring)
|
||||||
|
groups = match.groupdict()
|
||||||
|
for key, val in groups.items():
|
||||||
|
if key not in ('separator', 'sign'):
|
||||||
|
if val is None:
|
||||||
|
groups[key] = "0n"
|
||||||
|
#print groups[key]
|
||||||
|
if key in ('years', 'months'):
|
||||||
|
groups[key] = Decimal(groups[key][:-1].replace(',', '.'))
|
||||||
|
else:
|
||||||
|
# these values are passed into a timedelta object, which works with floats.
|
||||||
|
groups[key] = float(groups[key][:-1].replace(',', '.'))
|
||||||
|
if groups["years"] == 0 and groups["months"] == 0:
|
||||||
|
ret = timedelta(days=groups["days"], hours=groups["hours"],
|
||||||
|
minutes=groups["minutes"], seconds=groups["seconds"],
|
||||||
|
weeks=groups["weeks"])
|
||||||
|
if groups["sign"] == '-':
|
||||||
|
ret = timedelta(0) - ret
|
||||||
|
else:
|
||||||
|
ret = Duration(years=groups["years"], months=groups["months"],
|
||||||
|
days=groups["days"], hours=groups["hours"],
|
||||||
|
minutes=groups["minutes"], seconds=groups["seconds"],
|
||||||
|
weeks=groups["weeks"])
|
||||||
|
if groups["sign"] == '-':
|
||||||
|
ret = Duration(0) - ret
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def duration_isoformat(tduration, format=D_DEFAULT):
|
||||||
|
'''
|
||||||
|
Format duration strings.
|
||||||
|
|
||||||
|
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||||
|
P%P (D_DEFAULT) as default format.
|
||||||
|
'''
|
||||||
|
# TODO: implement better decision for negative Durations.
|
||||||
|
# should be done in Duration class in consistent way with timedelta.
|
||||||
|
if ((isinstance(tduration, Duration) and (tduration.years < 0 or
|
||||||
|
tduration.months < 0 or
|
||||||
|
tduration.tdelta < timedelta(0)))
|
||||||
|
or (isinstance(tduration, timedelta) and (tduration < timedelta(0)))):
|
||||||
|
ret = '-'
|
||||||
|
else:
|
||||||
|
ret = ''
|
||||||
|
ret += strftime(tduration, format)
|
||||||
|
return ret
|
||||||
32
awx/lib/site-packages/isodate/isoerror.py
Normal file
32
awx/lib/site-packages/isodate/isoerror.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This module defines all exception classes in the whole package.
|
||||||
|
'''
|
||||||
|
|
||||||
|
class ISO8601Error(ValueError):
|
||||||
|
'''Raised when the given ISO string can not be parsed.'''
|
||||||
207
awx/lib/site-packages/isodate/isostrf.py
Normal file
207
awx/lib/site-packages/isodate/isostrf.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
"""
|
||||||
|
This module provides an alternative strftime method.
|
||||||
|
|
||||||
|
The strftime method in this module allows only a subset of Python's strftime
|
||||||
|
format codes, plus a few additional. It supports the full range of date values
|
||||||
|
possible with standard Python date/time objects. Furthermore there are several
|
||||||
|
pr-defined format strings in this module to make ease producing of ISO 8601
|
||||||
|
conforming strings.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from isodate.duration import Duration
|
||||||
|
from isodate.isotzinfo import tz_isoformat
|
||||||
|
|
||||||
|
# Date specific format strings
|
||||||
|
DATE_BAS_COMPLETE = '%Y%m%d'
|
||||||
|
DATE_EXT_COMPLETE = '%Y-%m-%d'
|
||||||
|
DATE_BAS_WEEK_COMPLETE = '%YW%W%w'
|
||||||
|
DATE_EXT_WEEK_COMPLETE = '%Y-W%W-%w'
|
||||||
|
DATE_BAS_ORD_COMPLETE = '%Y%j'
|
||||||
|
DATE_EXT_ORD_COMPLETE = '%Y-%j'
|
||||||
|
DATE_BAS_WEEK = '%YW%W'
|
||||||
|
DATE_EXT_WEEK = '%Y-W%W'
|
||||||
|
DATE_MONTH = '%Y-%m'
|
||||||
|
DATE_YEAR = '%Y'
|
||||||
|
DATE_CENTURY = '%C'
|
||||||
|
|
||||||
|
# Time specific format strings
|
||||||
|
TIME_BAS_COMPLETE = '%H%M%S'
|
||||||
|
TIME_EXT_COMPLETE = '%H:%M:%S'
|
||||||
|
TIME_BAS_MINUTE = '%H%M'
|
||||||
|
TIME_EXT_MINUTE = '%H:%M'
|
||||||
|
TIME_HOUR = '%H'
|
||||||
|
|
||||||
|
# Time zone formats
|
||||||
|
TZ_BAS = '%z'
|
||||||
|
TZ_EXT = '%Z'
|
||||||
|
TZ_HOUR = '%h'
|
||||||
|
|
||||||
|
# DateTime formats
|
||||||
|
DT_EXT_COMPLETE = DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT
|
||||||
|
DT_BAS_COMPLETE = DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS
|
||||||
|
DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT
|
||||||
|
DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS
|
||||||
|
DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_COMPLETE +\
|
||||||
|
TZ_EXT
|
||||||
|
DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_COMPLETE +\
|
||||||
|
TZ_BAS
|
||||||
|
|
||||||
|
# Duration formts
|
||||||
|
D_DEFAULT = 'P%P'
|
||||||
|
D_WEEK = 'P%p'
|
||||||
|
D_ALT_EXT = 'P' + DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE
|
||||||
|
D_ALT_BAS = 'P' + DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE
|
||||||
|
D_ALT_EXT_ORD = 'P' + DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE
|
||||||
|
D_ALT_BAS_ORD = 'P' + DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE
|
||||||
|
|
||||||
|
STRF_DT_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.day,
|
||||||
|
'%f': lambda tdt, yds: '%06d' % tdt.microsecond,
|
||||||
|
'%H': lambda tdt, yds: '%02d' % tdt.hour,
|
||||||
|
'%j': lambda tdt, yds: '%03d' % (tdt.toordinal() -
|
||||||
|
date(tdt.year, 1, 1).toordinal() +
|
||||||
|
1),
|
||||||
|
'%m': lambda tdt, yds: '%02d' % tdt.month,
|
||||||
|
'%M': lambda tdt, yds: '%02d' % tdt.minute,
|
||||||
|
'%S': lambda tdt, yds: '%02d' % tdt.second,
|
||||||
|
'%w': lambda tdt, yds: '%1d' % tdt.isoweekday(),
|
||||||
|
'%W': lambda tdt, yds: '%02d' % tdt.isocalendar()[1],
|
||||||
|
'%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +\
|
||||||
|
(('%%0%dd' % yds) % tdt.year),
|
||||||
|
'%C': lambda tdt, yds: (((yds != 4) and '+') or '') +\
|
||||||
|
(('%%0%dd' % (yds - 2)) % (tdt.year / 100)),
|
||||||
|
'%h': lambda tdt, yds: tz_isoformat(tdt, '%h'),
|
||||||
|
'%Z': lambda tdt, yds: tz_isoformat(tdt, '%Z'),
|
||||||
|
'%z': lambda tdt, yds: tz_isoformat(tdt, '%z'),
|
||||||
|
'%%': lambda tdt, yds: '%'}
|
||||||
|
|
||||||
|
STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days,
|
||||||
|
'%f': lambda tdt, yds: '%06d' % tdt.microseconds,
|
||||||
|
'%H': lambda tdt, yds: '%02d' % (tdt.seconds / 60 / 60),
|
||||||
|
'%m': lambda tdt, yds: '%02d' % tdt.months,
|
||||||
|
'%M': lambda tdt, yds: '%02d' % ((tdt.seconds / 60) % 60),
|
||||||
|
'%S': lambda tdt, yds: '%02d' % (tdt.seconds % 60),
|
||||||
|
'%W': lambda tdt, yds: '%02d' % (abs(tdt.days / 7)),
|
||||||
|
'%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +\
|
||||||
|
(('%%0%dd' % yds) % tdt.years),
|
||||||
|
'%C': lambda tdt, yds: (((yds != 4) and '+') or '') +\
|
||||||
|
(('%%0%dd' % (yds - 2)) %
|
||||||
|
(tdt.years / 100)),
|
||||||
|
'%%': lambda tdt, yds: '%'}
|
||||||
|
|
||||||
|
|
||||||
|
def _strfduration(tdt, format, yeardigits=4):
|
||||||
|
'''
|
||||||
|
this is the work method for timedelta and Duration instances.
|
||||||
|
|
||||||
|
see strftime for more details.
|
||||||
|
'''
|
||||||
|
def repl(match):
|
||||||
|
'''
|
||||||
|
lookup format command and return corresponding replacement.
|
||||||
|
'''
|
||||||
|
if match.group(0) in STRF_D_MAP:
|
||||||
|
return STRF_D_MAP[match.group(0)](tdt, yeardigits)
|
||||||
|
elif match.group(0) == '%P':
|
||||||
|
ret = []
|
||||||
|
if isinstance(tdt, Duration):
|
||||||
|
if tdt.years:
|
||||||
|
ret.append('%sY' % abs(tdt.years))
|
||||||
|
if tdt.months:
|
||||||
|
ret.append('%sM' % abs(tdt.months))
|
||||||
|
usecs = abs((tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 +
|
||||||
|
tdt.microseconds)
|
||||||
|
seconds, usecs = divmod(usecs, 1000000)
|
||||||
|
minutes, seconds = divmod(seconds, 60)
|
||||||
|
hours, minutes = divmod(minutes, 60)
|
||||||
|
days, hours = divmod(hours, 24)
|
||||||
|
if days:
|
||||||
|
ret.append('%sD' % days)
|
||||||
|
if hours or minutes or seconds or usecs:
|
||||||
|
ret.append('T')
|
||||||
|
if hours:
|
||||||
|
ret.append('%sH' % hours)
|
||||||
|
if minutes:
|
||||||
|
ret.append('%sM' % minutes)
|
||||||
|
if seconds or usecs:
|
||||||
|
if usecs:
|
||||||
|
ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0'))
|
||||||
|
else:
|
||||||
|
ret.append("%d" % seconds)
|
||||||
|
ret.append('S')
|
||||||
|
# at least one component has to be there.
|
||||||
|
return ret and ''.join(ret) or '0D'
|
||||||
|
elif match.group(0) == '%p':
|
||||||
|
return str(abs(tdt.days // 7)) + 'W'
|
||||||
|
return match.group(0)
|
||||||
|
return re.sub('%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p', repl,
|
||||||
|
format)
|
||||||
|
|
||||||
|
|
||||||
|
def _strfdt(tdt, format, yeardigits=4):
|
||||||
|
'''
|
||||||
|
this is the work method for time and date instances.
|
||||||
|
|
||||||
|
see strftime for more details.
|
||||||
|
'''
|
||||||
|
def repl(match):
|
||||||
|
'''
|
||||||
|
lookup format command and return corresponding replacement.
|
||||||
|
'''
|
||||||
|
if match.group(0) in STRF_DT_MAP:
|
||||||
|
return STRF_DT_MAP[match.group(0)](tdt, yeardigits)
|
||||||
|
return match.group(0)
|
||||||
|
return re.sub('%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%', repl,
|
||||||
|
format)
|
||||||
|
|
||||||
|
|
||||||
|
def strftime(tdt, format, yeardigits=4):
|
||||||
|
'''
|
||||||
|
Directive Meaning Notes
|
||||||
|
%d Day of the month as a decimal number [01,31].
|
||||||
|
%f Microsecond as a decimal number [0,999999], zero-padded on the left (1)
|
||||||
|
%H Hour (24-hour clock) as a decimal number [00,23].
|
||||||
|
%j Day of the year as a decimal number [001,366].
|
||||||
|
%m Month as a decimal number [01,12].
|
||||||
|
%M Minute as a decimal number [00,59].
|
||||||
|
%S Second as a decimal number [00,61]. (3)
|
||||||
|
%w Weekday as a decimal number [0(Monday),6].
|
||||||
|
%W Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. (4)
|
||||||
|
%Y Year with century as a decimal number. [0000,9999]
|
||||||
|
%C Century as a decimal number. [00,99]
|
||||||
|
%z UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive). (5)
|
||||||
|
%Z Time zone name (empty string if the object is naive).
|
||||||
|
%P ISO8601 duration format.
|
||||||
|
%p ISO8601 duration format in weeks.
|
||||||
|
%% A literal '%' character.
|
||||||
|
'''
|
||||||
|
if isinstance(tdt, (timedelta, Duration)):
|
||||||
|
return _strfduration(tdt, format, yeardigits)
|
||||||
|
return _strfdt(tdt, format, yeardigits)
|
||||||
157
awx/lib/site-packages/isodate/isotime.py
Normal file
157
awx/lib/site-packages/isodate/isotime.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This modules provides a method to parse an ISO 8601:2004 time string to a
|
||||||
|
Python datetime.time instance.
|
||||||
|
|
||||||
|
It supports all basic and extended formats including time zone specifications
|
||||||
|
as described in the ISO standard.
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import time
|
||||||
|
|
||||||
|
from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT
|
||||||
|
from isodate.isoerror import ISO8601Error
|
||||||
|
from isodate.isotzinfo import TZ_REGEX, build_tzinfo
|
||||||
|
|
||||||
|
TIME_REGEX_CACHE = []
|
||||||
|
# used to cache regular expressions to parse ISO time strings.
|
||||||
|
|
||||||
|
|
||||||
|
def build_time_regexps():
|
||||||
|
'''
|
||||||
|
Build regular expressions to parse ISO time string.
|
||||||
|
|
||||||
|
The regular expressions are compiled and stored in TIME_REGEX_CACHE
|
||||||
|
for later reuse.
|
||||||
|
'''
|
||||||
|
if not TIME_REGEX_CACHE:
|
||||||
|
# ISO 8601 time representations allow decimal fractions on least
|
||||||
|
# significant time component. Command and Full Stop are both valid
|
||||||
|
# fraction separators.
|
||||||
|
# The letter 'T' is allowed as time designator in front of a time
|
||||||
|
# expression.
|
||||||
|
# Immediately after a time expression, a time zone definition is
|
||||||
|
# allowed.
|
||||||
|
# a TZ may be missing (local time), be a 'Z' for UTC or a string of
|
||||||
|
# +-hh:mm where the ':mm' part can be skipped.
|
||||||
|
# TZ information patterns:
|
||||||
|
# ''
|
||||||
|
# Z
|
||||||
|
# +-hh:mm
|
||||||
|
# +-hhmm
|
||||||
|
# +-hh =>
|
||||||
|
# isotzinfo.TZ_REGEX
|
||||||
|
# 1. complete time:
|
||||||
|
# hh:mm:ss.ss ... extended format
|
||||||
|
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):"
|
||||||
|
r"(?P<minute>[0-9]{2}):"
|
||||||
|
r"(?P<second>[0-9]{2}([,.][0-9]+)?)"
|
||||||
|
+ TZ_REGEX))
|
||||||
|
# hhmmss.ss ... basic format
|
||||||
|
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})"
|
||||||
|
r"(?P<minute>[0-9]{2})"
|
||||||
|
r"(?P<second>[0-9]{2}([,.][0-9]+)?)"
|
||||||
|
+ TZ_REGEX))
|
||||||
|
# 2. reduced accuracy:
|
||||||
|
# hh:mm.mm ... extended format
|
||||||
|
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):"
|
||||||
|
r"(?P<minute>[0-9]{2}([,.][0-9]+)?)"
|
||||||
|
+ TZ_REGEX))
|
||||||
|
# hhmm.mm ... basic format
|
||||||
|
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})"
|
||||||
|
r"(?P<minute>[0-9]{2}([,.][0-9]+)?)"
|
||||||
|
+ TZ_REGEX))
|
||||||
|
# hh.hh ... basic format
|
||||||
|
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}([,.][0-9]+)?)"
|
||||||
|
+ TZ_REGEX))
|
||||||
|
return TIME_REGEX_CACHE
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time(timestring):
|
||||||
|
'''
|
||||||
|
Parses ISO 8601 times into datetime.time objects.
|
||||||
|
|
||||||
|
Following ISO 8601 formats are supported:
|
||||||
|
(as decimal separator a ',' or a '.' is allowed)
|
||||||
|
hhmmss.ssTZD basic complete time
|
||||||
|
hh:mm:ss.ssTZD extended compelte time
|
||||||
|
hhmm.mmTZD basic reduced accuracy time
|
||||||
|
hh:mm.mmTZD extended reduced accuracy time
|
||||||
|
hh.hhTZD basic reduced accuracy time
|
||||||
|
TZD is the time zone designator which can be in the following format:
|
||||||
|
no designator indicates local time zone
|
||||||
|
Z UTC
|
||||||
|
+-hhmm basic hours and minutes
|
||||||
|
+-hh:mm extended hours and minutes
|
||||||
|
+-hh hours
|
||||||
|
'''
|
||||||
|
isotimes = build_time_regexps()
|
||||||
|
for pattern in isotimes:
|
||||||
|
match = pattern.match(timestring)
|
||||||
|
if match:
|
||||||
|
groups = match.groupdict()
|
||||||
|
for key, value in groups.items():
|
||||||
|
if value is not None:
|
||||||
|
groups[key] = value.replace(',', '.')
|
||||||
|
tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'],
|
||||||
|
int(groups['tzhour'] or 0),
|
||||||
|
int(groups['tzmin'] or 0))
|
||||||
|
if 'second' in groups:
|
||||||
|
# round to microseconds if fractional seconds are more precise
|
||||||
|
second = Decimal(groups['second']).quantize(Decimal('.000001'))
|
||||||
|
microsecond = (second - int(second)) * long(1e6)
|
||||||
|
# int(...) ... no rounding
|
||||||
|
# to_integral() ... rounding
|
||||||
|
return time(int(groups['hour']), int(groups['minute']),
|
||||||
|
int(second), int(microsecond.to_integral()), tzinfo)
|
||||||
|
if 'minute' in groups:
|
||||||
|
minute = Decimal(groups['minute'])
|
||||||
|
second = (minute - int(minute)) * 60
|
||||||
|
microsecond = (second - int(second)) * long(1e6)
|
||||||
|
return time(int(groups['hour']), int(minute), int(second),
|
||||||
|
int(microsecond.to_integral()), tzinfo)
|
||||||
|
else:
|
||||||
|
microsecond, second, minute = 0, 0, 0
|
||||||
|
hour = Decimal(groups['hour'])
|
||||||
|
minute = (hour - int(hour)) * 60
|
||||||
|
second = (minute - int(minute)) * 60
|
||||||
|
microsecond = (second - int(second)) * long(1e6)
|
||||||
|
return time(int(hour), int(minute), int(second),
|
||||||
|
int(microsecond.to_integral()), tzinfo)
|
||||||
|
raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring)
|
||||||
|
|
||||||
|
|
||||||
|
def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT):
|
||||||
|
'''
|
||||||
|
Format time strings.
|
||||||
|
|
||||||
|
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||||
|
Time-Extended-Complete with extended time zone as default format.
|
||||||
|
'''
|
||||||
|
return strftime(ttime, format)
|
||||||
109
awx/lib/site-packages/isodate/isotzinfo.py
Normal file
109
awx/lib/site-packages/isodate/isotzinfo.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
This module provides an ISO 8601:2004 time zone info parser.
|
||||||
|
|
||||||
|
It offers a function to parse the time zone offset as specified by ISO 8601.
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
|
||||||
|
from isodate.isoerror import ISO8601Error
|
||||||
|
from isodate.tzinfo import UTC, FixedOffset, ZERO
|
||||||
|
|
||||||
|
TZ_REGEX = r"(?P<tzname>(Z|(?P<tzsign>[+-])"\
|
||||||
|
r"(?P<tzhour>[0-9]{2})(:(?P<tzmin>[0-9]{2}))?)?)"
|
||||||
|
|
||||||
|
TZ_RE = re.compile(TZ_REGEX)
|
||||||
|
|
||||||
|
def build_tzinfo(tzname, tzsign='+', tzhour=0, tzmin=0):
|
||||||
|
'''
|
||||||
|
create a tzinfo instance according to given parameters.
|
||||||
|
|
||||||
|
tzname:
|
||||||
|
'Z' ... return UTC
|
||||||
|
'' | None ... return None
|
||||||
|
other ... return FixedOffset
|
||||||
|
'''
|
||||||
|
if tzname is None or tzname == '':
|
||||||
|
return None
|
||||||
|
if tzname == 'Z':
|
||||||
|
return UTC
|
||||||
|
tzsign = ((tzsign == '-') and -1) or 1
|
||||||
|
return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)
|
||||||
|
|
||||||
|
def parse_tzinfo(tzstring):
|
||||||
|
'''
|
||||||
|
Parses ISO 8601 time zone designators to tzinfo objecs.
|
||||||
|
|
||||||
|
A time zone designator can be in the following format:
|
||||||
|
no designator indicates local time zone
|
||||||
|
Z UTC
|
||||||
|
+-hhmm basic hours and minutes
|
||||||
|
+-hh:mm extended hours and minutes
|
||||||
|
+-hh hours
|
||||||
|
'''
|
||||||
|
match = TZ_RE.match(tzstring)
|
||||||
|
if match:
|
||||||
|
groups = match.groupdict()
|
||||||
|
return build_tzinfo(groups['tzname'], groups['tzsign'],
|
||||||
|
int(groups['tzhour'] or 0),
|
||||||
|
int(groups['tzmin'] or 0))
|
||||||
|
raise ISO8601Error('%s not a valid time zone info' % tzstring)
|
||||||
|
|
||||||
|
def tz_isoformat(dt, format='%Z'):
|
||||||
|
'''
|
||||||
|
return time zone offset ISO 8601 formatted.
|
||||||
|
The various ISO formats can be chosen with the format parameter.
|
||||||
|
|
||||||
|
if tzinfo is None returns ''
|
||||||
|
if tzinfo is UTC returns 'Z'
|
||||||
|
else the offset is rendered to the given format.
|
||||||
|
format:
|
||||||
|
%h ... +-HH
|
||||||
|
%z ... +-HHMM
|
||||||
|
%Z ... +-HH:MM
|
||||||
|
'''
|
||||||
|
tzinfo = dt.tzinfo
|
||||||
|
if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
|
||||||
|
return ''
|
||||||
|
if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
|
||||||
|
return 'Z'
|
||||||
|
tdelta = tzinfo.utcoffset(dt)
|
||||||
|
seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
|
||||||
|
sign = ((seconds < 0) and '-') or '+'
|
||||||
|
seconds = abs(seconds)
|
||||||
|
minutes, seconds = divmod(seconds, 60)
|
||||||
|
hours, minutes = divmod(minutes, 60)
|
||||||
|
if hours > 99:
|
||||||
|
raise OverflowError('can not handle differences > 99 hours')
|
||||||
|
if format == '%Z':
|
||||||
|
return '%s%02d:%02d' % (sign, hours, minutes)
|
||||||
|
elif format == '%z':
|
||||||
|
return '%s%02d%02d' % (sign, hours, minutes)
|
||||||
|
elif format == '%h':
|
||||||
|
return '%s%02d' % (sign, hours)
|
||||||
|
raise ValueError('unknown format string "%s"' % format)
|
||||||
49
awx/lib/site-packages/isodate/tests/__init__.py
Normal file
49
awx/lib/site-packages/isodate/tests/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Collect all test suites into one TestSuite instance.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from isodate.tests import (test_date, test_time, test_datetime, test_duration,
|
||||||
|
test_strf, test_pickle)
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Return a new TestSuite instance consisting of all available TestSuites.
|
||||||
|
'''
|
||||||
|
return unittest.TestSuite([
|
||||||
|
test_date.test_suite(),
|
||||||
|
test_time.test_suite(),
|
||||||
|
test_datetime.test_suite(),
|
||||||
|
test_duration.test_suite(),
|
||||||
|
test_strf.test_suite(),
|
||||||
|
test_pickle.test_suite(),
|
||||||
|
])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
126
awx/lib/site-packages/isodate/tests/test_date.py
Normal file
126
awx/lib/site-packages/isodate/tests/test_date.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Test cases for the isodate module.
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
from datetime import date
|
||||||
|
from isodate import parse_date, ISO8601Error, date_isoformat
|
||||||
|
from isodate import DATE_CENTURY, DATE_YEAR, DATE_MONTH
|
||||||
|
from isodate import DATE_EXT_COMPLETE, DATE_BAS_COMPLETE
|
||||||
|
from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
|
||||||
|
from isodate import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE
|
||||||
|
from isodate import DATE_EXT_WEEK, DATE_EXT_WEEK_COMPLETE
|
||||||
|
|
||||||
|
# the following list contains tuples of ISO date strings and the expected
|
||||||
|
# result from the parse_date method. A result of None means an ISO8601Error
|
||||||
|
# is expected. The test cases are grouped into dates with 4 digit years
|
||||||
|
# and 6 digit years.
|
||||||
|
TEST_CASES = {4: [('19', date(1901, 1, 1), DATE_CENTURY),
|
||||||
|
('1985', date(1985, 1, 1), DATE_YEAR),
|
||||||
|
('1985-04', date(1985, 4, 1), DATE_MONTH),
|
||||||
|
('1985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
|
||||||
|
('19850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
|
||||||
|
('1985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
|
||||||
|
('1985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
|
||||||
|
('1985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
|
||||||
|
('1985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
|
||||||
|
('1985W15', date(1985, 4, 8), DATE_BAS_WEEK),
|
||||||
|
('1985-W15', date(1985, 4, 8), DATE_EXT_WEEK),
|
||||||
|
('1989-W15', date(1989, 4, 10), DATE_EXT_WEEK),
|
||||||
|
('1989-W15-5', date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE),
|
||||||
|
('1-W1-1', None, DATE_BAS_WEEK_COMPLETE)],
|
||||||
|
6: [('+0019', date(1901, 1, 1), DATE_CENTURY),
|
||||||
|
('+001985', date(1985, 1, 1), DATE_YEAR),
|
||||||
|
('+001985-04', date(1985, 4, 1), DATE_MONTH),
|
||||||
|
('+001985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
|
||||||
|
('+0019850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
|
||||||
|
('+001985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
|
||||||
|
('+001985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
|
||||||
|
('+001985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
|
||||||
|
('+001985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
|
||||||
|
('+001985W15', date(1985, 4, 8), DATE_BAS_WEEK),
|
||||||
|
('+001985-W15', date(1985, 4, 8), DATE_EXT_WEEK)]}
|
||||||
|
|
||||||
|
def create_testcase(yeardigits, datestring, expectation, format):
|
||||||
|
'''
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
'''
|
||||||
|
|
||||||
|
class TestDate(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to parse an ISO date string into a date
|
||||||
|
object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
'''
|
||||||
|
Parse an ISO date string and compare it to the expected value.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(ISO8601Error, parse_date, datestring,
|
||||||
|
yeardigits)
|
||||||
|
else:
|
||||||
|
result = parse_date(datestring, yeardigits)
|
||||||
|
self.assertEqual(result, expectation)
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Take date object and create ISO string from it.
|
||||||
|
This is the reverse test to test_parse.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(AttributeError,
|
||||||
|
date_isoformat, expectation, format,
|
||||||
|
yeardigits)
|
||||||
|
else:
|
||||||
|
self.assertEqual(date_isoformat(expectation, format,
|
||||||
|
yeardigits),
|
||||||
|
datestring)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestDate)
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Construct a TestSuite instance for all test cases.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
for yeardigits, tests in TEST_CASES.items():
|
||||||
|
for datestring, expectation, format in tests:
|
||||||
|
suite.addTest(create_testcase(yeardigits, datestring,
|
||||||
|
expectation, format))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
138
awx/lib/site-packages/isodate/tests/test_datetime.py
Normal file
138
awx/lib/site-packages/isodate/tests/test_datetime.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Test cases for the isodatetime module.
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from isodate import parse_datetime, UTC, FixedOffset, datetime_isoformat
|
||||||
|
from isodate import DATE_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_BAS_COMPLETE
|
||||||
|
from isodate import DATE_EXT_COMPLETE, TIME_EXT_MINUTE, TIME_EXT_COMPLETE
|
||||||
|
from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
|
||||||
|
from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
|
||||||
|
from isodate import DATE_BAS_WEEK_COMPLETE, DATE_EXT_WEEK_COMPLETE
|
||||||
|
|
||||||
|
# the following list contains tuples of ISO datetime strings and the expected
|
||||||
|
# result from the parse_datetime method. A result of None means an ISO8601Error
|
||||||
|
# is expected.
|
||||||
|
TEST_CASES = [('19850412T1015', datetime(1985, 4, 12, 10, 15),
|
||||||
|
DATE_BAS_COMPLETE + 'T' + TIME_BAS_MINUTE,
|
||||||
|
'19850412T1015'),
|
||||||
|
('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15),
|
||||||
|
DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE,
|
||||||
|
'1985-04-12T10:15'),
|
||||||
|
('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
|
||||||
|
DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
|
||||||
|
'1985102T1015Z'),
|
||||||
|
('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
|
||||||
|
DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT,
|
||||||
|
'1985-102T10:15Z'),
|
||||||
|
('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15,
|
||||||
|
tzinfo=FixedOffset(4, 0,
|
||||||
|
'+0400')),
|
||||||
|
DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
|
||||||
|
'1985W155T1015+0400'),
|
||||||
|
('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15,
|
||||||
|
tzinfo=FixedOffset(4, 0,
|
||||||
|
'+0400'),),
|
||||||
|
DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR,
|
||||||
|
'1985-W15-5T10:15+04'),
|
||||||
|
('20110410T101225.123000Z',
|
||||||
|
datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
|
||||||
|
DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS,
|
||||||
|
'20110410T101225.123000Z'),
|
||||||
|
('2012-10-12T08:29:46.069178Z',
|
||||||
|
datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC),
|
||||||
|
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||||
|
'2012-10-12T08:29:46.069178Z'),
|
||||||
|
('2012-10-12T08:29:46.691780Z',
|
||||||
|
datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC),
|
||||||
|
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||||
|
'2012-10-12T08:29:46.691780Z'),
|
||||||
|
('2012-10-30T08:55:22.1234567Z',
|
||||||
|
datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC),
|
||||||
|
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||||
|
'2012-10-30T08:55:22.123457Z'),
|
||||||
|
('2012-10-30T08:55:22.1234561Z',
|
||||||
|
datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC),
|
||||||
|
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||||
|
'2012-10-30T08:55:22.123456Z')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_testcase(datetimestring, expectation, format, output):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestDateTime(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to parse an ISO datetime string into a
|
||||||
|
datetime object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
'''
|
||||||
|
Parse an ISO datetime string and compare it to the expected value.
|
||||||
|
'''
|
||||||
|
result = parse_datetime(datetimestring)
|
||||||
|
self.assertEqual(result, expectation)
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Take datetime object and create ISO string from it.
|
||||||
|
This is the reverse test to test_parse.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(AttributeError,
|
||||||
|
datetime_isoformat, expectation, format)
|
||||||
|
else:
|
||||||
|
self.assertEqual(datetime_isoformat(expectation, format),
|
||||||
|
output)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Construct a TestSuite instance for all test cases.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
for datetimestring, expectation, format, output in TEST_CASES:
|
||||||
|
suite.addTest(create_testcase(datetimestring, expectation, format, output))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
519
awx/lib/site-packages/isodate/tests/test_duration.py
Normal file
519
awx/lib/site-packages/isodate/tests/test_duration.py
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Test cases for the isoduration module.
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
import operator
|
||||||
|
from datetime import timedelta, date, datetime
|
||||||
|
|
||||||
|
from isodate import Duration, parse_duration, ISO8601Error
|
||||||
|
from isodate import D_DEFAULT, D_WEEK, D_ALT_EXT, duration_isoformat
|
||||||
|
|
||||||
|
# the following list contains tuples of ISO duration strings and the expected
|
||||||
|
# result from the parse_duration method. A result of None means an ISO8601Error
|
||||||
|
# is expected.
|
||||||
|
PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18),
|
||||||
|
D_DEFAULT, None),
|
||||||
|
'P2W': (timedelta(weeks=2), D_WEEK, None),
|
||||||
|
'P3Y6M4DT12H30M5S': (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3),
|
||||||
|
D_DEFAULT, None),
|
||||||
|
'P23DT23H': (timedelta(hours=23, days=23),
|
||||||
|
D_DEFAULT, None),
|
||||||
|
'P4Y': (Duration(years=4), D_DEFAULT, None),
|
||||||
|
'P1M': (Duration(months=1), D_DEFAULT, None),
|
||||||
|
'PT1M': (timedelta(minutes=1), D_DEFAULT, None),
|
||||||
|
'P0.5Y': (Duration(years=0.5), D_DEFAULT, None),
|
||||||
|
'PT36H': (timedelta(hours=36), D_DEFAULT, 'P1DT12H'),
|
||||||
|
'P1DT12H': (timedelta(days=1, hours=12), D_DEFAULT, None),
|
||||||
|
'+P11D': (timedelta(days=11), D_DEFAULT, 'P11D'),
|
||||||
|
'-P2W': (timedelta(weeks=-2), D_WEEK, None),
|
||||||
|
'-P2.2W': (timedelta(weeks=-2.2), D_DEFAULT,
|
||||||
|
'-P15DT9H36M'),
|
||||||
|
'P1DT2H3M4S': (timedelta(days=1, hours=2, minutes=3,
|
||||||
|
seconds=4), D_DEFAULT, None),
|
||||||
|
'P1DT2H3M': (timedelta(days=1, hours=2, minutes=3),
|
||||||
|
D_DEFAULT, None),
|
||||||
|
'P1DT2H': (timedelta(days=1, hours=2), D_DEFAULT, None),
|
||||||
|
'PT2H': (timedelta(hours=2), D_DEFAULT, None),
|
||||||
|
'PT2.3H': (timedelta(hours=2.3), D_DEFAULT, 'PT2H18M'),
|
||||||
|
'PT2H3M4S': (timedelta(hours=2, minutes=3, seconds=4),
|
||||||
|
D_DEFAULT, None),
|
||||||
|
'PT3M4S': (timedelta(minutes=3, seconds=4), D_DEFAULT,
|
||||||
|
None),
|
||||||
|
'PT22S': (timedelta(seconds=22), D_DEFAULT, None),
|
||||||
|
'PT22.22S': (timedelta(seconds=22.22), 'PT%S.%fS',
|
||||||
|
'PT22.220000S'),
|
||||||
|
'-P2Y': (Duration(years=-2), D_DEFAULT, None),
|
||||||
|
'-P3Y6M4DT12H30M5S': (Duration(-4, -5, 0, 0, -30, -12, 0,
|
||||||
|
-6, -3), D_DEFAULT, None),
|
||||||
|
'-P1DT2H3M4S': (timedelta(days=-1, hours=-2, minutes=-3,
|
||||||
|
seconds=-4), D_DEFAULT, None),
|
||||||
|
# alternative format
|
||||||
|
'P0018-09-04T11:09:08': (Duration(4, 8, 0, 0, 9, 11, 0, 9,
|
||||||
|
18), D_ALT_EXT, None),
|
||||||
|
#'PT000022.22': timedelta(seconds=22.22),
|
||||||
|
}
|
||||||
|
|
||||||
|
# d1 d2 '+', '-', '>'
|
||||||
|
# A list of test cases to test addition and subtraction between datetime and
|
||||||
|
# Duration objects.
|
||||||
|
# each tuple contains 2 duration strings, and a result string for addition and
|
||||||
|
# one for subtraction. The last value says, if the first duration is greater
|
||||||
|
# than the second.
|
||||||
|
MATH_TEST_CASES = (('P5Y7M1DT9H45M16.72S', 'PT27M24.68S',
|
||||||
|
'P5Y7M1DT10H12M41.4S', 'P5Y7M1DT9H17M52.04S', None),
|
||||||
|
('PT28M12.73S', 'PT56M29.92S',
|
||||||
|
'PT1H24M42.65S', '-PT28M17.19S', False),
|
||||||
|
('P3Y7M23DT5H25M0.33S', 'PT1H1.95S',
|
||||||
|
'P3Y7M23DT6H25M2.28S', 'P3Y7M23DT4H24M58.38S', None),
|
||||||
|
('PT1H1.95S', 'P3Y7M23DT5H25M0.33S',
|
||||||
|
'P3Y7M23DT6H25M2.28S', '-P3Y7M23DT4H24M58.38S', None),
|
||||||
|
('P1332DT55M0.33S', 'PT1H1.95S',
|
||||||
|
'P1332DT1H55M2.28S', 'P1331DT23H54M58.38S', True),
|
||||||
|
('PT1H1.95S', 'P1332DT55M0.33S',
|
||||||
|
'P1332DT1H55M2.28S', '-P1331DT23H54M58.38S', False))
|
||||||
|
|
||||||
|
# A list of test cases to test addition and subtraction of date/datetime
|
||||||
|
# and Duration objects. They are tested against the results of an
|
||||||
|
# equal long timedelta duration.
|
||||||
|
DATE_TEST_CASES = ( (date(2008, 2, 29),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(date(2008, 1, 31),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2008, 2, 29),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2008, 1, 31),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2008, 4, 21),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2008, 5, 5),
|
||||||
|
timedelta(days=10, hours=12, minutes=20),
|
||||||
|
Duration(days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2000, 1, 1),
|
||||||
|
timedelta(hours=-33),
|
||||||
|
Duration(hours=-33)),
|
||||||
|
(datetime(2008, 5, 5),
|
||||||
|
Duration(years=1, months=1, days=10, hours=12,
|
||||||
|
minutes=20),
|
||||||
|
Duration(months=13, days=10, hours=12, minutes=20)),
|
||||||
|
(datetime(2000, 3, 30),
|
||||||
|
Duration(years=1, months=1, days=10, hours=12,
|
||||||
|
minutes=20),
|
||||||
|
Duration(months=13, days=10, hours=12, minutes=20)),
|
||||||
|
)
|
||||||
|
|
||||||
|
# A list of test cases of additon of date/datetime and Duration. The results
|
||||||
|
# are compared against a given expected result.
|
||||||
|
DATE_CALC_TEST_CASES = (
|
||||||
|
(date(2000, 2, 1),
|
||||||
|
Duration(years=1, months=1),
|
||||||
|
date(2001, 3, 1)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=1, months=1),
|
||||||
|
date(2001, 3, 29)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=1),
|
||||||
|
date(2001, 2, 28)),
|
||||||
|
(date(1996, 2, 29),
|
||||||
|
Duration(years=4),
|
||||||
|
date(2000, 2, 29)),
|
||||||
|
(date(2096, 2, 29),
|
||||||
|
Duration(years=4),
|
||||||
|
date(2100, 2, 28)),
|
||||||
|
(date(2000, 2, 1),
|
||||||
|
Duration(years=-1, months=-1),
|
||||||
|
date(1999, 1, 1)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=-1, months=-1),
|
||||||
|
date(1999, 1, 29)),
|
||||||
|
(date(2000, 2, 1),
|
||||||
|
Duration(years=1, months=1, days=1),
|
||||||
|
date(2001, 3, 2)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=1, months=1, days=1),
|
||||||
|
date(2001, 3, 30)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=1, days=1),
|
||||||
|
date(2001, 3, 1)),
|
||||||
|
(date(1996, 2, 29),
|
||||||
|
Duration(years=4, days=1),
|
||||||
|
date(2000, 3, 1)),
|
||||||
|
(date(2096, 2, 29),
|
||||||
|
Duration(years=4, days=1),
|
||||||
|
date(2100, 3, 1)),
|
||||||
|
(date(2000, 2, 1),
|
||||||
|
Duration(years=-1, months=-1, days=-1),
|
||||||
|
date(1998, 12, 31)),
|
||||||
|
(date(2000, 2, 29),
|
||||||
|
Duration(years=-1, months=-1, days=-1),
|
||||||
|
date(1999, 1, 28)),
|
||||||
|
(date(2001, 4, 1),
|
||||||
|
Duration(years=-1, months=-1, days=-1),
|
||||||
|
date(2000, 2, 29)),
|
||||||
|
(date(2000, 4, 1),
|
||||||
|
Duration(years=-1, months=-1, days=-1),
|
||||||
|
date(1999, 2, 28)),
|
||||||
|
(Duration(years=1, months=2),
|
||||||
|
Duration(years=0, months=0, days=1),
|
||||||
|
Duration(years=1, months=2, days=1)),
|
||||||
|
(Duration(years=-1, months=-1, days=-1),
|
||||||
|
date(2000, 4, 1),
|
||||||
|
date(1999, 2, 28)),
|
||||||
|
(Duration(years=1, months=1, weeks=5),
|
||||||
|
date(2000, 1, 30),
|
||||||
|
date(2001, 4, 4)),
|
||||||
|
(parse_duration("P1Y1M5W"),
|
||||||
|
date(2000, 1, 30),
|
||||||
|
date(2001, 4, 4)),
|
||||||
|
(parse_duration("P0.5Y"),
|
||||||
|
date(2000, 1, 30),
|
||||||
|
None),
|
||||||
|
(Duration(years=1, months=1, hours=3),
|
||||||
|
datetime(2000, 1, 30, 12, 15, 00),
|
||||||
|
datetime(2001, 2, 28, 15, 15, 00)),
|
||||||
|
(parse_duration("P1Y1MT3H"),
|
||||||
|
datetime(2000, 1, 30, 12, 15, 00),
|
||||||
|
datetime(2001, 2, 28, 15, 15, 00)),
|
||||||
|
(Duration(years=1, months=2),
|
||||||
|
timedelta(days=1),
|
||||||
|
Duration(years=1, months=2, days=1)),
|
||||||
|
(timedelta(days=1),
|
||||||
|
Duration(years=1, months=2),
|
||||||
|
Duration(years=1, months=2, days=1)),
|
||||||
|
(datetime(2008, 1, 1, 0, 2),
|
||||||
|
Duration(months=1),
|
||||||
|
datetime(2008, 2, 1, 0, 2)),
|
||||||
|
(datetime.strptime("200802", "%Y%M"),
|
||||||
|
parse_duration("P1M"),
|
||||||
|
datetime(2008, 2, 1, 0, 2)),
|
||||||
|
(datetime(2008, 2, 1),
|
||||||
|
Duration(months=1),
|
||||||
|
datetime(2008, 3, 1)),
|
||||||
|
(datetime.strptime("200802", "%Y%m"),
|
||||||
|
parse_duration("P1M"),
|
||||||
|
datetime(2008, 3, 1)),
|
||||||
|
# (date(2000, 1, 1),
|
||||||
|
# Duration(years=1.5),
|
||||||
|
# date(2001, 6, 1)),
|
||||||
|
# (date(2000, 1, 1),
|
||||||
|
# Duration(years=1, months=1.5),
|
||||||
|
# date(2001, 2, 14)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DurationTest(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
This class tests various other aspects of the isoduration module,
|
||||||
|
which are not covered with the test cases listed above.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_associative(self):
|
||||||
|
'''
|
||||||
|
Adding 2 durations to a date is not associative.
|
||||||
|
'''
|
||||||
|
days1 = Duration(days=1)
|
||||||
|
months1 = Duration(months=1)
|
||||||
|
start = date(2000, 3, 30)
|
||||||
|
res1 = start + days1 + months1
|
||||||
|
res2 = start + months1 + days1
|
||||||
|
self.assertNotEqual(res1, res2)
|
||||||
|
|
||||||
|
def test_typeerror(self):
|
||||||
|
'''
|
||||||
|
Test if TypError is raised with certain parameters.
|
||||||
|
'''
|
||||||
|
self.assertRaises(TypeError, parse_duration, date(2000, 1, 1))
|
||||||
|
self.assertRaises(TypeError, operator.sub, Duration(years=1),
|
||||||
|
date(2000, 1, 1))
|
||||||
|
self.assertRaises(TypeError, operator.sub, 'raise exc',
|
||||||
|
Duration(years=1))
|
||||||
|
self.assertRaises(TypeError, operator.add,
|
||||||
|
Duration(years=1, months=1, weeks=5),
|
||||||
|
'raise exception')
|
||||||
|
self.assertRaises(TypeError, operator.add, 'raise exception',
|
||||||
|
Duration(years=1, months=1, weeks=5))
|
||||||
|
|
||||||
|
def test_parseerror(self):
|
||||||
|
'''
|
||||||
|
Test for unparseable duration string.
|
||||||
|
'''
|
||||||
|
self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10')
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
'''
|
||||||
|
Test __repr__ and __str__ for Duration obqects.
|
||||||
|
'''
|
||||||
|
dur = Duration(10, 10, years=10, months=10)
|
||||||
|
self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur))
|
||||||
|
self.assertEqual('isodate.duration.Duration(10, 10, 0,'
|
||||||
|
' years=10, months=10)', repr(dur))
|
||||||
|
|
||||||
|
def test_neg(self):
|
||||||
|
'''
|
||||||
|
Test __neg__ for Duration objects.
|
||||||
|
'''
|
||||||
|
self.assertEqual(-Duration(0), Duration(0))
|
||||||
|
self.assertEqual(-Duration(years=1, months=1),
|
||||||
|
Duration(years=-1, months=-1))
|
||||||
|
self.assertEqual(-Duration(years=1, months=1), Duration(months=-13))
|
||||||
|
self.assertNotEqual(-Duration(years=1), timedelta(days=-365))
|
||||||
|
self.assertNotEqual(-timedelta(days=365), Duration(years=-1))
|
||||||
|
# FIXME: this test fails in python 3... it seems like python3
|
||||||
|
# treats a == b the same b == a
|
||||||
|
#self.assertNotEqual(-timedelta(days=10), -Duration(days=10))
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Test various other strftime combinations.
|
||||||
|
'''
|
||||||
|
self.assertEqual(duration_isoformat(Duration(0)), 'P0D')
|
||||||
|
self.assertEqual(duration_isoformat(-Duration(0)), 'P0D')
|
||||||
|
self.assertEqual(duration_isoformat(Duration(seconds=10)), 'PT10S')
|
||||||
|
self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)),
|
||||||
|
'-P1Y1M')
|
||||||
|
self.assertEqual(duration_isoformat(-Duration(years=1, months=1)),
|
||||||
|
'-P1Y1M')
|
||||||
|
self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
|
||||||
|
'P1Y1M')
|
||||||
|
self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
|
||||||
|
'P1Y1M')
|
||||||
|
dur = Duration(years=3, months=7, days=23, hours=5, minutes=25,
|
||||||
|
milliseconds=330)
|
||||||
|
self.assertEqual(duration_isoformat(dur), 'P3Y7M23DT5H25M0.33S')
|
||||||
|
self.assertEqual(duration_isoformat(-dur), '-P3Y7M23DT5H25M0.33S')
|
||||||
|
|
||||||
|
def test_equal(self):
|
||||||
|
'''
|
||||||
|
Test __eq__ and __ne__ methods.
|
||||||
|
'''
|
||||||
|
self.assertEqual(Duration(years=1, months=1),
|
||||||
|
Duration(years=1, months=1))
|
||||||
|
self.assertEqual(Duration(years=1, months=1), Duration(months=13))
|
||||||
|
self.assertNotEqual(Duration(years=1, months=2),
|
||||||
|
Duration(years=1, months=1))
|
||||||
|
self.assertNotEqual(Duration(years=1, months=1), Duration(months=14))
|
||||||
|
self.assertNotEqual(Duration(years=1), timedelta(days=365))
|
||||||
|
self.assertFalse(Duration(years=1, months=1) !=
|
||||||
|
Duration(years=1, months=1))
|
||||||
|
self.assertFalse(Duration(years=1, months=1) != Duration(months=13))
|
||||||
|
self.assertTrue(Duration(years=1, months=2) !=
|
||||||
|
Duration(years=1, months=1))
|
||||||
|
self.assertTrue(Duration(years=1, months=1) != Duration(months=14))
|
||||||
|
self.assertTrue(Duration(years=1) != timedelta(days=365))
|
||||||
|
self.assertEqual(Duration(days=1), timedelta(days=1))
|
||||||
|
# FIXME: this test fails in python 3... it seems like python3
|
||||||
|
# treats a != b the same b != a
|
||||||
|
#self.assertNotEqual(timedelta(days=1), Duration(days=1))
|
||||||
|
|
||||||
|
def test_totimedelta(self):
|
||||||
|
'''
|
||||||
|
Test conversion form Duration to timedelta.
|
||||||
|
'''
|
||||||
|
dur = Duration(years=1, months=2, days=10)
|
||||||
|
self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)), timedelta(434))
|
||||||
|
# leap year has one day more in february
|
||||||
|
self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(435))
|
||||||
|
dur = Duration(months=2)
|
||||||
|
# march is longer than february, but april is shorter than march (cause only one day difference compared to 2)
|
||||||
|
self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(60))
|
||||||
|
self.assertEqual(dur.totimedelta(datetime(2001, 2, 25)), timedelta(59))
|
||||||
|
self.assertEqual(dur.totimedelta(datetime(2001, 3, 25)), timedelta(61))
|
||||||
|
|
||||||
|
|
||||||
|
def create_parsetestcase(durationstring, expectation, format, altstr):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
PARSE_TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestParseDuration(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to parse an ISO duration string into a
|
||||||
|
timedelta or Duration object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
'''
|
||||||
|
Parse an ISO duration string and compare it to the expected value.
|
||||||
|
'''
|
||||||
|
result = parse_duration(durationstring)
|
||||||
|
self.assertEqual(result, expectation)
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Take duration/timedelta object and create ISO string from it.
|
||||||
|
This is the reverse test to test_parse.
|
||||||
|
'''
|
||||||
|
if altstr:
|
||||||
|
self.assertEqual(duration_isoformat(expectation, format),
|
||||||
|
altstr)
|
||||||
|
else:
|
||||||
|
# if durationstring == '-P2W':
|
||||||
|
# import pdb; pdb.set_trace()
|
||||||
|
self.assertEqual(duration_isoformat(expectation, format),
|
||||||
|
durationstring)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration)
|
||||||
|
|
||||||
|
|
||||||
|
def create_mathtestcase(dur1, dur2, resadd, ressub, resge):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
MATH_TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dur1 = parse_duration(dur1)
|
||||||
|
dur2 = parse_duration(dur2)
|
||||||
|
resadd = parse_duration(resadd)
|
||||||
|
ressub = parse_duration(ressub)
|
||||||
|
|
||||||
|
class TestMathDuration(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template test addition, subtraction and >
|
||||||
|
operators for Duration objects.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
'''
|
||||||
|
Test operator + (__add__, __radd__)
|
||||||
|
'''
|
||||||
|
self.assertEqual(dur1 + dur2, resadd)
|
||||||
|
|
||||||
|
def test_sub(self):
|
||||||
|
'''
|
||||||
|
Test operator - (__sub__, __rsub__)
|
||||||
|
'''
|
||||||
|
self.assertEqual(dur1 - dur2, ressub)
|
||||||
|
|
||||||
|
def test_ge(self):
|
||||||
|
'''
|
||||||
|
Test operator > and <
|
||||||
|
'''
|
||||||
|
def dogetest():
|
||||||
|
''' Test greater than.'''
|
||||||
|
return dur1 > dur2
|
||||||
|
|
||||||
|
def doletest():
|
||||||
|
''' Test less than.'''
|
||||||
|
return dur1 < dur2
|
||||||
|
if resge is None:
|
||||||
|
self.assertRaises(TypeError, dogetest)
|
||||||
|
self.assertRaises(TypeError, doletest)
|
||||||
|
else:
|
||||||
|
self.assertEqual(dogetest(), resge)
|
||||||
|
self.assertEqual(doletest(), not resge)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestMathDuration)
|
||||||
|
|
||||||
|
|
||||||
|
def create_datetestcase(start, tdelta, duration):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
DATE_TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestDateCalc(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template test addition, subtraction
|
||||||
|
operators for Duration objects.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
'''
|
||||||
|
Test operator +.
|
||||||
|
'''
|
||||||
|
self.assertEqual(start + tdelta, start + duration)
|
||||||
|
|
||||||
|
def test_sub(self):
|
||||||
|
'''
|
||||||
|
Test operator -.
|
||||||
|
'''
|
||||||
|
self.assertEqual(start - tdelta, start - duration)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
|
||||||
|
|
||||||
|
|
||||||
|
def create_datecalctestcase(start, duration, expectation):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestDateCalc(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template test addition operators for Duration objects.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_calc(self):
|
||||||
|
'''
|
||||||
|
Test operator +.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(ValueError, operator.add, start, duration)
|
||||||
|
else:
|
||||||
|
self.assertEqual(start + duration, expectation)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Return a test suite containing all test defined above.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
for durationstring, (expectation, format, altstr) in PARSE_TEST_CASES.items():
|
||||||
|
suite.addTest(create_parsetestcase(durationstring, expectation,
|
||||||
|
format, altstr))
|
||||||
|
for testdata in MATH_TEST_CASES:
|
||||||
|
suite.addTest(create_mathtestcase(*testdata))
|
||||||
|
for testdata in DATE_TEST_CASES:
|
||||||
|
suite.addTest(create_datetestcase(*testdata))
|
||||||
|
for testdata in DATE_CALC_TEST_CASES:
|
||||||
|
suite.addTest(create_datecalctestcase(*testdata))
|
||||||
|
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
35
awx/lib/site-packages/isodate/tests/test_pickle.py
Normal file
35
awx/lib/site-packages/isodate/tests/test_pickle.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import unittest
|
||||||
|
import cPickle as pickle
|
||||||
|
import isodate
|
||||||
|
|
||||||
|
|
||||||
|
class TestPickle(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to parse an ISO datetime string into a
|
||||||
|
datetime object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_pickle(self):
|
||||||
|
'''
|
||||||
|
Parse an ISO datetime string and compare it to the expected value.
|
||||||
|
'''
|
||||||
|
dti = isodate.parse_datetime('2012-10-26T09:33+00:00')
|
||||||
|
pikl = pickle.dumps(dti, 2)
|
||||||
|
dto = pickle.loads(pikl)
|
||||||
|
self.assertEqual(dti, dto)
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Construct a TestSuite instance for all test cases.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
130
awx/lib/site-packages/isodate/tests/test_strf.py
Normal file
130
awx/lib/site-packages/isodate/tests/test_strf.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Test cases for the isodate module.
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from isodate import strftime
|
||||||
|
from isodate import LOCAL
|
||||||
|
from isodate import DT_EXT_COMPLETE
|
||||||
|
from isodate import tzinfo
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASES = ((datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
|
||||||
|
"2012-12-25T13:30:00+10:00"),
|
||||||
|
# DST ON
|
||||||
|
(datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
|
||||||
|
"1999-12-25T13:30:00+11:00"),
|
||||||
|
# microseconds
|
||||||
|
(datetime(2012, 10, 12, 8, 29, 46, 69178), "%Y-%m-%dT%H:%M:%S.%f",
|
||||||
|
"2012-10-12T08:29:46.069178"),
|
||||||
|
(datetime(2012, 10, 12, 8, 29, 46, 691780), "%Y-%m-%dT%H:%M:%S.%f",
|
||||||
|
"2012-10-12T08:29:46.691780"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_testcase(dt, format, expectation):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestDate(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to test ISO date formatting.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# local time zone mock function
|
||||||
|
def localtime_mock(self, secs):
|
||||||
|
"""
|
||||||
|
mock time.localtime so that it always returns a time_struct with tm_idst=1
|
||||||
|
"""
|
||||||
|
tt = self.ORIG['localtime'](secs)
|
||||||
|
# befor 2000 everything is dst, after 2000 no dst.
|
||||||
|
if tt.tm_year < 2000:
|
||||||
|
dst = 1
|
||||||
|
else:
|
||||||
|
dst = 0
|
||||||
|
tt = (tt.tm_year, tt.tm_mon, tt.tm_mday,
|
||||||
|
tt.tm_hour, tt.tm_min, tt.tm_sec,
|
||||||
|
tt.tm_wday, tt.tm_yday, dst)
|
||||||
|
return time.struct_time(tt)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.ORIG = {}
|
||||||
|
self.ORIG['STDOFFSET'] = tzinfo.STDOFFSET
|
||||||
|
self.ORIG['DSTOFFSET'] = tzinfo.DSTOFFSET
|
||||||
|
self.ORIG['DSTDIFF'] = tzinfo.DSTDIFF
|
||||||
|
self.ORIG['localtime'] = time.localtime
|
||||||
|
# ovveride all saved values with fixtures.
|
||||||
|
# calculate LOCAL TZ offset, so that this test runs in every time zone
|
||||||
|
tzinfo.STDOFFSET = timedelta(seconds=36000) # assume we are in +10:00
|
||||||
|
tzinfo.DSTOFFSET = timedelta(seconds=39600) # assume DST = +11:00
|
||||||
|
tzinfo.DSTDIFF = tzinfo.DSTOFFSET - tzinfo.STDOFFSET
|
||||||
|
time.localtime = self.localtime_mock
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# restore test fixtures
|
||||||
|
tzinfo.STDOFFSET = self.ORIG['STDOFFSET']
|
||||||
|
tzinfo.DSTOFFSET = self.ORIG['DSTOFFSET']
|
||||||
|
tzinfo.DSTDIFF = self.ORIG['DSTDIFF']
|
||||||
|
time.localtime = self.ORIG['localtime']
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Take date object and create ISO string from it.
|
||||||
|
This is the reverse test to test_parse.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(AttributeError,
|
||||||
|
strftime(dt, format))
|
||||||
|
else:
|
||||||
|
self.assertEqual(strftime(dt, format),
|
||||||
|
expectation)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestDate)
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Construct a TestSuite instance for all test cases.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
for dt, format, expectation in TEST_CASES:
|
||||||
|
suite.addTest(create_testcase(dt, format, expectation))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
143
awx/lib/site-packages/isodate/tests/test_time.py
Normal file
143
awx/lib/site-packages/isodate/tests/test_time.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright 2009, Gerhard Weis
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the authors nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
##############################################################################
|
||||||
|
'''
|
||||||
|
Test cases for the isotime module.
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
from datetime import time
|
||||||
|
|
||||||
|
from isodate import parse_time, UTC, FixedOffset, ISO8601Error, time_isoformat
|
||||||
|
from isodate import TIME_BAS_COMPLETE, TIME_BAS_MINUTE
|
||||||
|
from isodate import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
|
||||||
|
from isodate import TIME_HOUR
|
||||||
|
from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
|
||||||
|
|
||||||
|
# the following list contains tuples of ISO time strings and the expected
|
||||||
|
# result from the parse_time method. A result of None means an ISO8601Error
|
||||||
|
# is expected.
|
||||||
|
TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS),
|
||||||
|
('23:20:50', time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT),
|
||||||
|
('2320', time(23, 20), TIME_BAS_MINUTE),
|
||||||
|
('23:20', time(23, 20), TIME_EXT_MINUTE),
|
||||||
|
('23', time(23), TIME_HOUR),
|
||||||
|
('232050,5', time(23, 20, 50, 500000), None),
|
||||||
|
('23:20:50.5', time(23, 20, 50, 500000), None),
|
||||||
|
# test precision
|
||||||
|
('15:33:42.123456', time(15, 33, 42, 123456), None),
|
||||||
|
('15:33:42.1234564', time(15, 33, 42, 123456), None),
|
||||||
|
('15:33:42.1234557', time(15, 33, 42, 123456), None),
|
||||||
|
('2320,8', time(23, 20, 48), None),
|
||||||
|
('23:20,8', time(23, 20, 48), None),
|
||||||
|
('23,3', time(23, 18), None),
|
||||||
|
('232030Z', time(23, 20, 30, tzinfo=UTC),
|
||||||
|
TIME_BAS_COMPLETE + TZ_BAS),
|
||||||
|
('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS),
|
||||||
|
('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS),
|
||||||
|
('23:20:30Z', time(23, 20, 30, tzinfo=UTC),
|
||||||
|
TIME_EXT_COMPLETE + TZ_EXT),
|
||||||
|
('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT),
|
||||||
|
('152746+0100', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(1, 0, '+0100')),
|
||||||
|
TIME_BAS_COMPLETE + TZ_BAS),
|
||||||
|
('152746-0500', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(-5, 0, '-0500')),
|
||||||
|
TIME_BAS_COMPLETE + TZ_BAS),
|
||||||
|
('152746+01', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(1, 0, '+01:00')),
|
||||||
|
TIME_BAS_COMPLETE + TZ_HOUR),
|
||||||
|
('152746-05', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(-5, -0, '-05:00')),
|
||||||
|
TIME_BAS_COMPLETE + TZ_HOUR),
|
||||||
|
('15:27:46+01:00', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(1, 0, '+01:00')),
|
||||||
|
TIME_EXT_COMPLETE + TZ_EXT),
|
||||||
|
('15:27:46-05:00', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(-5, -0, '-05:00')),
|
||||||
|
TIME_EXT_COMPLETE + TZ_EXT),
|
||||||
|
('15:27:46+01', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(1, 0, '+01:00')),
|
||||||
|
TIME_EXT_COMPLETE + TZ_HOUR),
|
||||||
|
('15:27:46-05', time(15, 27, 46,
|
||||||
|
tzinfo=FixedOffset(-5, -0, '-05:00')),
|
||||||
|
TIME_EXT_COMPLETE + TZ_HOUR),
|
||||||
|
('1:17:30', None, TIME_EXT_COMPLETE)]
|
||||||
|
|
||||||
|
|
||||||
|
def create_testcase(timestring, expectation, format):
|
||||||
|
"""
|
||||||
|
Create a TestCase class for a specific test.
|
||||||
|
|
||||||
|
This allows having a separate TestCase for each test tuple from the
|
||||||
|
TEST_CASES list, so that a failed test won't stop other tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestTime(unittest.TestCase):
|
||||||
|
'''
|
||||||
|
A test case template to parse an ISO time string into a time
|
||||||
|
object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
'''
|
||||||
|
Parse an ISO time string and compare it to the expected value.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(ISO8601Error, parse_time, timestring)
|
||||||
|
else:
|
||||||
|
result = parse_time(timestring)
|
||||||
|
self.assertEqual(result, expectation)
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
'''
|
||||||
|
Take time object and create ISO string from it.
|
||||||
|
This is the reverse test to test_parse.
|
||||||
|
'''
|
||||||
|
if expectation is None:
|
||||||
|
self.assertRaises(AttributeError,
|
||||||
|
time_isoformat, expectation, format)
|
||||||
|
elif format is not None:
|
||||||
|
self.assertEqual(time_isoformat(expectation, format),
|
||||||
|
timestring)
|
||||||
|
|
||||||
|
return unittest.TestLoader().loadTestsFromTestCase(TestTime)
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
'''
|
||||||
|
Construct a TestSuite instance for all test cases.
|
||||||
|
'''
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
for timestring, expectation, format in TEST_CASES:
|
||||||
|
suite.addTest(create_testcase(timestring, expectation, format))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
# load_tests Protocol
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
return test_suite()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
||||||
137
awx/lib/site-packages/isodate/tzinfo.py
Normal file
137
awx/lib/site-packages/isodate/tzinfo.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
'''
|
||||||
|
This module provides some datetime.tzinfo implementations.
|
||||||
|
|
||||||
|
All those classes are taken from the Python documentation.
|
||||||
|
'''
|
||||||
|
from datetime import timedelta, tzinfo
|
||||||
|
import time
|
||||||
|
|
||||||
|
ZERO = timedelta(0)
|
||||||
|
# constant for zero time offset.
|
||||||
|
|
||||||
|
class Utc(tzinfo):
|
||||||
|
'''UTC
|
||||||
|
|
||||||
|
Universal time coordinated time zone.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
'''
|
||||||
|
Return offset from UTC in minutes east of UTC, which is ZERO for UTC.
|
||||||
|
'''
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
'''
|
||||||
|
Return the time zone name corresponding to the datetime object dt, as a string.
|
||||||
|
'''
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
'''
|
||||||
|
Return the daylight saving time (DST) adjustment, in minutes east of UTC.
|
||||||
|
'''
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
UTC = Utc()
|
||||||
|
# the default instance for UTC.
|
||||||
|
|
||||||
|
class FixedOffset(tzinfo):
|
||||||
|
'''
|
||||||
|
A class building tzinfo objects for fixed-offset time zones.
|
||||||
|
|
||||||
|
Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to
|
||||||
|
build a UTC tzinfo object.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"):
|
||||||
|
'''
|
||||||
|
Initialise an instance with time offset and name.
|
||||||
|
The time offset should be positive for time zones east of UTC
|
||||||
|
and negate for time zones west of UTC.
|
||||||
|
'''
|
||||||
|
self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
|
||||||
|
self.__name = name
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
'''
|
||||||
|
Return offset from UTC in minutes of UTC.
|
||||||
|
'''
|
||||||
|
return self.__offset
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
'''
|
||||||
|
Return the time zone name corresponding to the datetime object dt, as a
|
||||||
|
string.
|
||||||
|
'''
|
||||||
|
return self.__name
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
'''
|
||||||
|
Return the daylight saving time (DST) adjustment, in minutes east of
|
||||||
|
UTC.
|
||||||
|
'''
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
'''
|
||||||
|
Return nicely formatted repr string.
|
||||||
|
'''
|
||||||
|
return "<FixedOffset %r>" % self.__name
|
||||||
|
|
||||||
|
|
||||||
|
STDOFFSET = timedelta(seconds = -time.timezone)
|
||||||
|
# locale time zone offset
|
||||||
|
|
||||||
|
# calculate local daylight saving offset if any.
|
||||||
|
if time.daylight:
|
||||||
|
DSTOFFSET = timedelta(seconds = -time.altzone)
|
||||||
|
else:
|
||||||
|
DSTOFFSET = STDOFFSET
|
||||||
|
|
||||||
|
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||||
|
# difference between local time zone and local DST time zone
|
||||||
|
|
||||||
|
class LocalTimezone(tzinfo):
|
||||||
|
"""
|
||||||
|
A class capturing the platform's idea of local time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
'''
|
||||||
|
Return offset from UTC in minutes of UTC.
|
||||||
|
'''
|
||||||
|
if self._isdst(dt):
|
||||||
|
return DSTOFFSET
|
||||||
|
else:
|
||||||
|
return STDOFFSET
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
'''
|
||||||
|
Return daylight saving offset.
|
||||||
|
'''
|
||||||
|
if self._isdst(dt):
|
||||||
|
return DSTDIFF
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
'''
|
||||||
|
Return the time zone name corresponding to the datetime object dt, as a
|
||||||
|
string.
|
||||||
|
'''
|
||||||
|
return time.tzname[self._isdst(dt)]
|
||||||
|
|
||||||
|
def _isdst(self, dt):
|
||||||
|
'''
|
||||||
|
Returns true if DST is active for given datetime object dt.
|
||||||
|
'''
|
||||||
|
tt = (dt.year, dt.month, dt.day,
|
||||||
|
dt.hour, dt.minute, dt.second,
|
||||||
|
dt.weekday(), 0, -1)
|
||||||
|
stamp = time.mktime(tt)
|
||||||
|
tt = time.localtime(stamp)
|
||||||
|
return tt.tm_isdst > 0
|
||||||
|
|
||||||
|
LOCAL = LocalTimezone()
|
||||||
|
# the default instance for local time zone.
|
||||||
29
awx/lib/site-packages/winrm/__init__.py
Normal file
29
awx/lib/site-packages/winrm/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from winrm.protocol import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class Response(object):
|
||||||
|
"""Response from a remote command execution"""
|
||||||
|
def __init__(self, args):
|
||||||
|
self.std_out, self.std_err, self.status_code = args
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
#TODO put tree dots at the end if out/err was truncated
|
||||||
|
return '<Response code {0}, out "{1}", err "{2}">'.format(
|
||||||
|
self.status_code, self.std_out[:20], self.std_err[:20])
|
||||||
|
|
||||||
|
|
||||||
|
class Session(object):
|
||||||
|
#TODO implement context manager methods
|
||||||
|
def __init__(self, url, auth):
|
||||||
|
#TODO convert short urls into well-formed endpoint
|
||||||
|
username, password = auth
|
||||||
|
self.protocol = Protocol(url, username=username, password=password)
|
||||||
|
|
||||||
|
def run_cmd(self, command, args=()):
|
||||||
|
#TODO optimize perf. Do not call open/close shell every time
|
||||||
|
shell_id = self.protocol.open_shell()
|
||||||
|
command_id = self.protocol.run_command(shell_id, command, args)
|
||||||
|
rs = Response(self.protocol.get_command_output(shell_id, command_id))
|
||||||
|
self.protocol.cleanup_command(shell_id, command_id)
|
||||||
|
self.protocol.close_shell(shell_id)
|
||||||
|
return rs
|
||||||
18
awx/lib/site-packages/winrm/exceptions.py
Normal file
18
awx/lib/site-packages/winrm/exceptions.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class WinRMWebServiceError(Exception):
|
||||||
|
"""Generic WinRM SOAP Error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WinRMAuthorizationError(Exception):
|
||||||
|
"""Authorization Error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WinRMWSManFault(Exception):
|
||||||
|
"""A Fault returned in the SOAP response. The XML node is a WSManFault"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WinRMTransportError(Exception):
|
||||||
|
""""Transport-level error"""
|
||||||
|
pass
|
||||||
318
awx/lib/site-packages/winrm/protocol.py
Normal file
318
awx/lib/site-packages/winrm/protocol.py
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
import base64
|
||||||
|
from datetime import timedelta
|
||||||
|
import uuid
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from isodate.isoduration import duration_isoformat
|
||||||
|
import xmltodict
|
||||||
|
from winrm.transport import HttpPlaintext, HttpKerberos, HttpSSL
|
||||||
|
|
||||||
|
|
||||||
|
class Protocol(object):
|
||||||
|
"""
|
||||||
|
This is the main class that does the SOAP request/response logic. There are a few helper classes, but pretty
|
||||||
|
much everything comes through here first.
|
||||||
|
"""
|
||||||
|
DEFAULT_TIMEOUT = 'PT60S'
|
||||||
|
DEFAULT_MAX_ENV_SIZE = 153600
|
||||||
|
DEFAULT_LOCALE = 'en-US'
|
||||||
|
|
||||||
|
def __init__(self, endpoint, transport='plaintext', username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None):
|
||||||
|
"""
|
||||||
|
@param string endpoint: the WinRM webservice endpoint
|
||||||
|
@param string transport: transport type, one of 'kerberos' (default), 'ssl', 'plaintext'
|
||||||
|
@param string username: username
|
||||||
|
@param string password: password
|
||||||
|
@param string realm: the Kerberos realm we are authenticating to
|
||||||
|
@param string service: the service name, default is HTTP
|
||||||
|
@param string keytab: the path to a keytab file if you are using one
|
||||||
|
@param string ca_trust_path: Certification Authority trust path
|
||||||
|
@param string cert_pem: client authentication certificate file path in PEM format
|
||||||
|
@param string cert_key_pem: client authentication certificate key file path in PEM format
|
||||||
|
"""
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.timeout = Protocol.DEFAULT_TIMEOUT
|
||||||
|
self.max_env_sz = Protocol.DEFAULT_MAX_ENV_SIZE
|
||||||
|
self.locale = Protocol.DEFAULT_LOCALE
|
||||||
|
if transport == 'plaintext':
|
||||||
|
self.transport = HttpPlaintext(endpoint, username, password)
|
||||||
|
elif transport == 'kerberos':
|
||||||
|
self.transport = HttpKerberos(endpoint)
|
||||||
|
elif transport == 'ssl':
|
||||||
|
self.transport = HttpSSL(endpoint, username, password, cert_pem=cert_pem, cert_key_pem=cert_key_pem)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.service = service
|
||||||
|
self.keytab = keytab
|
||||||
|
self.ca_trust_path = ca_trust_path
|
||||||
|
|
||||||
|
def set_timeout(self, seconds):
|
||||||
|
"""
|
||||||
|
Operation timeout, see http://msdn.microsoft.com/en-us/library/ee916629(v=PROT.13).aspx
|
||||||
|
@param int seconds: the number of seconds to set the timeout to. It will be converted to an ISO8601 format.
|
||||||
|
"""
|
||||||
|
# in original library there is an alias - op_timeout method
|
||||||
|
return duration_isoformat(timedelta(seconds))
|
||||||
|
|
||||||
|
def open_shell(self, i_stream='stdin', o_stream='stdout stderr', working_directory=None, env_vars=None, noprofile=False, codepage=437, lifetime=None, idle_timeout=None):
|
||||||
|
"""
|
||||||
|
Create a Shell on the destination host
|
||||||
|
@param string i_stream: Which input stream to open. Leave this alone unless you know what you're doing (default: stdin)
|
||||||
|
@param string o_stream: Which output stream to open. Leave this alone unless you know what you're doing (default: stdout stderr)
|
||||||
|
@param string working_directory: the directory to create the shell in
|
||||||
|
@param dict env_vars: environment variables to set for the shell. Fir instance: {'PATH': '%PATH%;c:/Program Files (x86)/Git/bin/', 'CYGWIN': 'nontsec codepage:utf8'}
|
||||||
|
@returns The ShellId from the SOAP response. This is our open shell instance on the remote machine.
|
||||||
|
@rtype string
|
||||||
|
"""
|
||||||
|
rq = {'env:Envelope': self._get_soap_header(
|
||||||
|
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
||||||
|
action='http://schemas.xmlsoap.org/ws/2004/09/transfer/Create')}
|
||||||
|
header = rq['env:Envelope']['env:Header']
|
||||||
|
header['w:OptionSet'] = {
|
||||||
|
'w:Option': [
|
||||||
|
{
|
||||||
|
'@Name': 'WINRS_NOPROFILE',
|
||||||
|
'#text': str(noprofile).upper() #TODO remove str call
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@Name': 'WINRS_CODEPAGE',
|
||||||
|
'#text': str(codepage) #TODO remove str call
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
shell = rq['env:Envelope'].setdefault('env:Body', {}).setdefault('rsp:Shell', {})
|
||||||
|
shell['rsp:InputStreams'] = i_stream
|
||||||
|
shell['rsp:OutputStreams'] = o_stream
|
||||||
|
|
||||||
|
if working_directory:
|
||||||
|
#TODO ensure that rsp:WorkingDirectory should be nested within rsp:Shell
|
||||||
|
shell['rsp:WorkingDirectory'] = working_directory
|
||||||
|
# TODO: research Lifetime a bit more: http://msdn.microsoft.com/en-us/library/cc251546(v=PROT.13).aspx
|
||||||
|
#if lifetime:
|
||||||
|
# shell['rsp:Lifetime'] = iso8601_duration.sec_to_dur(lifetime)
|
||||||
|
# TODO: make it so the input is given in milliseconds and converted to xs:duration
|
||||||
|
if idle_timeout:
|
||||||
|
shell['rsp:IdleTimeOut'] = idle_timeout
|
||||||
|
if env_vars:
|
||||||
|
env = shell.setdefault('rsp:Environment', {})
|
||||||
|
for key, value in env_vars.items():
|
||||||
|
env['rsp:Variable'] = {'@Name': key, '#text': value}
|
||||||
|
|
||||||
|
rs = self.send_message(xmltodict.unparse(rq))
|
||||||
|
#rs = xmltodict.parse(rs)
|
||||||
|
#return rs['s:Envelope']['s:Body']['x:ResourceCreated']['a:ReferenceParameters']['w:SelectorSet']['w:Selector']['#text']
|
||||||
|
root = ET.fromstring(rs)
|
||||||
|
return next(node for node in root.findall('.//*') if node.get('Name') == 'ShellId').text
|
||||||
|
|
||||||
|
# Helper method for building SOAP Header
|
||||||
|
def _get_soap_header(self, action=None, resource_uri=None, shell_id=None, message_id=None):
|
||||||
|
if not message_id:
|
||||||
|
message_id = uuid.uuid4()
|
||||||
|
header = {
|
||||||
|
'@xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
|
||||||
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||||
|
'@xmlns:env': 'http://www.w3.org/2003/05/soap-envelope',
|
||||||
|
|
||||||
|
'@xmlns:a': 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
|
||||||
|
'@xmlns:b': 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd',
|
||||||
|
'@xmlns:n': 'http://schemas.xmlsoap.org/ws/2004/09/enumeration',
|
||||||
|
'@xmlns:x': 'http://schemas.xmlsoap.org/ws/2004/09/transfer',
|
||||||
|
'@xmlns:w': 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
|
||||||
|
'@xmlns:p': 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd',
|
||||||
|
'@xmlns:rsp': 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell',
|
||||||
|
'@xmlns:cfg': 'http://schemas.microsoft.com/wbem/wsman/1/config',
|
||||||
|
|
||||||
|
'env:Header': {
|
||||||
|
'a:To': 'http://windows-host:5985/wsman',
|
||||||
|
'a:ReplyTo': {
|
||||||
|
'a:Address': {
|
||||||
|
'@mustUnderstand': 'true',
|
||||||
|
'#text': 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'w:MaxEnvelopeSize': {
|
||||||
|
'@mustUnderstand': 'true',
|
||||||
|
'#text': '153600'
|
||||||
|
},
|
||||||
|
'a:MessageID': 'uuid:{0}'.format(message_id),
|
||||||
|
'w:Locale': {
|
||||||
|
'@mustUnderstand': 'false',
|
||||||
|
'@xml:lang': 'en-US'
|
||||||
|
},
|
||||||
|
'p:DataLocale': {
|
||||||
|
'@mustUnderstand': 'false',
|
||||||
|
'@xml:lang': 'en-US'
|
||||||
|
},
|
||||||
|
# TODO: research this a bit http://msdn.microsoft.com/en-us/library/cc251561(v=PROT.13).aspx
|
||||||
|
#'cfg:MaxTimeoutms': 600
|
||||||
|
'w:OperationTimeout': 'PT60S',
|
||||||
|
'w:ResourceURI': {
|
||||||
|
'@mustUnderstand': 'true',
|
||||||
|
'#text': resource_uri
|
||||||
|
},
|
||||||
|
'a:Action': {
|
||||||
|
'@mustUnderstand': 'true',
|
||||||
|
'#text': action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shell_id:
|
||||||
|
header['env:Header']['w:SelectorSet'] = {
|
||||||
|
'w:Selector': {
|
||||||
|
'@Name': 'ShellId',
|
||||||
|
'#text': shell_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return header
|
||||||
|
|
||||||
|
def send_message(self, message):
|
||||||
|
# TODO add message_id vs relates_to checking
|
||||||
|
# TODO port error handling code
|
||||||
|
return self.transport.send_message(message)
|
||||||
|
|
||||||
|
def close_shell(self, shell_id):
|
||||||
|
"""
|
||||||
|
Close the shell
|
||||||
|
@param string shell_id: The shell id on the remote machine. See #open_shell
|
||||||
|
@returns This should have more error checking but it just returns true for now.
|
||||||
|
@rtype bool
|
||||||
|
"""
|
||||||
|
message_id = uuid.uuid4()
|
||||||
|
rq = {'env:Envelope': self._get_soap_header(
|
||||||
|
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
||||||
|
action='http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete',
|
||||||
|
shell_id=shell_id,
|
||||||
|
message_id=message_id)}
|
||||||
|
|
||||||
|
# SOAP message requires empty env:Body
|
||||||
|
rq['env:Envelope'].setdefault('env:Body', {})
|
||||||
|
|
||||||
|
rs = self.send_message(xmltodict.unparse(rq))
|
||||||
|
root = ET.fromstring(rs)
|
||||||
|
relates_to = next(node for node in root.findall('.//*') if node.tag.endswith('RelatesTo')).text
|
||||||
|
# TODO change assert into user-friendly exception
|
||||||
|
assert uuid.UUID(relates_to.replace('uuid:', '')) == message_id
|
||||||
|
|
||||||
|
def run_command(self, shell_id, command, arguments=(), console_mode_stdin=True, skip_cmd_shell=False):
|
||||||
|
"""
|
||||||
|
Run a command on a machine with an open shell
|
||||||
|
@param string shell_id: The shell id on the remote machine. See #open_shell
|
||||||
|
@param string command: The command to run on the remote machine
|
||||||
|
@param iterable of string arguments: An array of arguments for this command
|
||||||
|
@param bool console_mode_stdin: (default: True)
|
||||||
|
@param bool skip_cmd_shell: (default: False)
|
||||||
|
@return: The CommandId from the SOAP response. This is the ID we need to query in order to get output.
|
||||||
|
@rtype string
|
||||||
|
"""
|
||||||
|
rq = {'env:Envelope': self._get_soap_header(
|
||||||
|
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
||||||
|
action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command',
|
||||||
|
shell_id=shell_id)}
|
||||||
|
header = rq['env:Envelope']['env:Header']
|
||||||
|
header['w:OptionSet'] = {
|
||||||
|
'w:Option': [
|
||||||
|
{
|
||||||
|
'@Name': 'WINRS_CONSOLEMODE_STDIN',
|
||||||
|
'#text': str(console_mode_stdin).upper()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@Name': 'WINRS_SKIP_CMD_SHELL',
|
||||||
|
'#text': str(skip_cmd_shell).upper()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
cmd_line = rq['env:Envelope'].setdefault('env:Body', {})\
|
||||||
|
.setdefault('rsp:CommandLine', {})
|
||||||
|
cmd_line['rsp:Command'] = {'#text': command}
|
||||||
|
if arguments:
|
||||||
|
cmd_line['rsp:Arguments'] = ' '.join(arguments)
|
||||||
|
|
||||||
|
rs = self.send_message(xmltodict.unparse(rq))
|
||||||
|
root = ET.fromstring(rs)
|
||||||
|
command_id = next(node for node in root.findall('.//*') if node.tag.endswith('CommandId')).text
|
||||||
|
return command_id
|
||||||
|
|
||||||
|
def cleanup_command(self, shell_id, command_id):
|
||||||
|
"""
|
||||||
|
Clean-up after a command. @see #run_command
|
||||||
|
@param string shell_id: The shell id on the remote machine. See #open_shell
|
||||||
|
@param string command_id: The command id on the remote machine. See #run_command
|
||||||
|
@returns: This should have more error checking but it just returns true for now.
|
||||||
|
@rtype bool
|
||||||
|
"""
|
||||||
|
message_id = uuid.uuid4()
|
||||||
|
rq = {'env:Envelope': self._get_soap_header(
|
||||||
|
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
||||||
|
action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal',
|
||||||
|
shell_id=shell_id,
|
||||||
|
message_id=message_id)}
|
||||||
|
|
||||||
|
# Signal the Command references to terminate (close stdout/stderr)
|
||||||
|
signal = rq['env:Envelope'].setdefault('env:Body', {}).setdefault('rsp:Signal', {})
|
||||||
|
signal['@CommandId'] = command_id
|
||||||
|
signal['rsp:Code'] = \
|
||||||
|
'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate'
|
||||||
|
|
||||||
|
rs = self.send_message(xmltodict.unparse(rq))
|
||||||
|
root = ET.fromstring(rs)
|
||||||
|
relates_to = next(node for node in root.findall('.//*') if node.tag.endswith('RelatesTo')).text
|
||||||
|
# TODO change assert into user-friendly exception
|
||||||
|
assert uuid.UUID(relates_to.replace('uuid:', '')) == message_id
|
||||||
|
|
||||||
|
def get_command_output(self, shell_id, command_id):
|
||||||
|
"""
|
||||||
|
Get the Output of the given shell and command
|
||||||
|
@param string shell_id: The shell id on the remote machine. See #open_shell
|
||||||
|
@param string command_id: The command id on the remote machine. See #run_command
|
||||||
|
#@return [Hash] Returns a Hash with a key :exitcode and :data. Data is an Array of Hashes where the cooresponding key
|
||||||
|
# is either :stdout or :stderr. The reason it is in an Array so so we can get the output in the order it ocurrs on
|
||||||
|
# the console.
|
||||||
|
"""
|
||||||
|
stdout_buffer, stderr_buffer = [], []
|
||||||
|
command_done = False
|
||||||
|
while not command_done:
|
||||||
|
stdout, stderr, return_code, command_done = \
|
||||||
|
self._raw_get_command_output(shell_id, command_id)
|
||||||
|
stdout_buffer.append(stdout)
|
||||||
|
stderr_buffer.append(stderr)
|
||||||
|
return ''.join(stdout_buffer), ''.join(stderr_buffer), return_code
|
||||||
|
|
||||||
|
def _raw_get_command_output(self, shell_id, command_id):
|
||||||
|
rq = {'env:Envelope': self._get_soap_header(
|
||||||
|
resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
|
||||||
|
action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive',
|
||||||
|
shell_id=shell_id)}
|
||||||
|
|
||||||
|
stream = rq['env:Envelope'].setdefault('env:Body', {}).setdefault('rsp:Receive', {})\
|
||||||
|
.setdefault('rsp:DesiredStream', {})
|
||||||
|
stream['@CommandId'] = command_id
|
||||||
|
stream['#text'] = 'stdout stderr'
|
||||||
|
|
||||||
|
rs = self.send_message(xmltodict.unparse(rq))
|
||||||
|
root = ET.fromstring(rs)
|
||||||
|
stream_nodes = [node for node in root.findall('.//*') if node.tag.endswith('Stream')]
|
||||||
|
stdout = stderr = ''
|
||||||
|
return_code = -1
|
||||||
|
for stream_node in stream_nodes:
|
||||||
|
if stream_node.text:
|
||||||
|
if stream_node.attrib['Name'] == 'stdout':
|
||||||
|
stdout += str(base64.b64decode(stream_node.text.encode('ascii')))
|
||||||
|
elif stream_node.attrib['Name'] == 'stderr':
|
||||||
|
stderr += str(base64.b64decode(stream_node.text.encode('ascii')))
|
||||||
|
|
||||||
|
# We may need to get additional output if the stream has not finished.
|
||||||
|
# The CommandState will change from Running to Done like so:
|
||||||
|
# @example
|
||||||
|
# from...
|
||||||
|
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Running"/>
|
||||||
|
# to...
|
||||||
|
# <rsp:CommandState CommandId="..." State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
|
||||||
|
# <rsp:ExitCode>0</rsp:ExitCode>
|
||||||
|
# </rsp:CommandState>
|
||||||
|
command_done = len([node for node in root.findall('.//*') if node.get('State', '').endswith('CommandState/Done')]) == 1
|
||||||
|
if command_done:
|
||||||
|
return_code = int(next(node for node in root.findall('.//*') if node.tag.endswith('ExitCode')).text)
|
||||||
|
|
||||||
|
return stdout, stderr, return_code, command_done
|
||||||
0
awx/lib/site-packages/winrm/tests/__init__.py
Normal file
0
awx/lib/site-packages/winrm/tests/__init__.py
Normal file
6
awx/lib/site-packages/winrm/tests/config_example.json
Normal file
6
awx/lib/site-packages/winrm/tests/config_example.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"endpoint": "http://windows-host:5985/wsman",
|
||||||
|
"transport": "plaintext",
|
||||||
|
"username": "username_without_domain",
|
||||||
|
"password": "password_as_plain_text"
|
||||||
|
}
|
||||||
332
awx/lib/site-packages/winrm/tests/conftest.py
Normal file
332
awx/lib/site-packages/winrm/tests/conftest.py
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import xmltodict
|
||||||
|
from pytest import skip, fixture
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
open_shell_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action>
|
||||||
|
<w:OptionSet>
|
||||||
|
<w:Option Name="WINRS_NOPROFILE">FALSE</w:Option>
|
||||||
|
<w:Option Name="WINRS_CODEPAGE">437</w:Option>
|
||||||
|
</w:OptionSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
<rsp:Shell>
|
||||||
|
<rsp:InputStreams>stdin</rsp:InputStreams>
|
||||||
|
<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>
|
||||||
|
</rsp:Shell>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
open_shell_response = """\
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<s:Envelope xml:lang="en-US" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse</a:Action>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111112</a:MessageID>
|
||||||
|
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
||||||
|
<a:RelatesTo>uuid:11111111-1111-1111-1111-111111111111</a:RelatesTo>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body>
|
||||||
|
<x:ResourceCreated>
|
||||||
|
<a:Address>http://windows-host:5985/wsman</a:Address>
|
||||||
|
<a:ReferenceParameters>
|
||||||
|
<w:ResourceURI>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
</a:ReferenceParameters>
|
||||||
|
</x:ResourceCreated>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>"""
|
||||||
|
|
||||||
|
close_shell_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
close_shell_response = """\
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<s:Envelope xml:lang="en-US" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/DeleteResponse</a:Action>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111112</a:MessageID>
|
||||||
|
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
||||||
|
<a:RelatesTo>uuid:11111111-1111-1111-1111-111111111111</a:RelatesTo>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body/>
|
||||||
|
</s:Envelope>
|
||||||
|
"""
|
||||||
|
|
||||||
|
run_cmd_with_args_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
<w:OptionSet>
|
||||||
|
<w:Option Name="WINRS_CONSOLEMODE_STDIN">TRUE</w:Option>
|
||||||
|
<w:Option Name="WINRS_SKIP_CMD_SHELL">FALSE</w:Option>
|
||||||
|
</w:OptionSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
<rsp:CommandLine>
|
||||||
|
<rsp:Command>ipconfig</rsp:Command>
|
||||||
|
<rsp:Arguments>/all</rsp:Arguments>
|
||||||
|
</rsp:CommandLine>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
run_cmd_wo_args_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
<w:OptionSet>
|
||||||
|
<w:Option Name="WINRS_CONSOLEMODE_STDIN">TRUE</w:Option>
|
||||||
|
<w:Option Name="WINRS_SKIP_CMD_SHELL">FALSE</w:Option>
|
||||||
|
</w:OptionSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
<rsp:CommandLine>
|
||||||
|
<rsp:Command>hostname</rsp:Command>
|
||||||
|
</rsp:CommandLine>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
run_cmd_response = """\
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<s:Envelope xml:lang="en-US" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandResponse</a:Action>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111112</a:MessageID>
|
||||||
|
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
||||||
|
<a:RelatesTo>uuid:11111111-1111-1111-1111-111111111111</a:RelatesTo>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body>
|
||||||
|
<rsp:CommandResponse>
|
||||||
|
<rsp:CommandId>11111111-1111-1111-1111-111111111114</rsp:CommandId>
|
||||||
|
</rsp:CommandResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>"""
|
||||||
|
|
||||||
|
cleanup_cmd_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal</a:Action>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
<rsp:Signal CommandId="11111111-1111-1111-1111-111111111114">
|
||||||
|
<rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate</rsp:Code>
|
||||||
|
</rsp:Signal>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
cleanup_cmd_response = """\
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<s:Envelope xml:lang="en-US" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/SignalResponse</a:Action>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111112</a:MessageID>
|
||||||
|
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
||||||
|
<a:RelatesTo>uuid:11111111-1111-1111-1111-111111111111</a:RelatesTo>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body>
|
||||||
|
<rsp:SignalResponse/>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>"""
|
||||||
|
|
||||||
|
get_cmd_output_request = """\
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<env:Envelope xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||||
|
<env:Header>
|
||||||
|
<a:To>http://windows-host:5985/wsman</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111111</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US" />
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>
|
||||||
|
<w:SelectorSet>
|
||||||
|
<w:Selector Name="ShellId">11111111-1111-1111-1111-111111111113</w:Selector>
|
||||||
|
</w:SelectorSet>
|
||||||
|
</env:Header>
|
||||||
|
<env:Body>
|
||||||
|
<rsp:Receive>
|
||||||
|
<rsp:DesiredStream CommandId="11111111-1111-1111-1111-111111111114">stdout stderr</rsp:DesiredStream>
|
||||||
|
</rsp:Receive>
|
||||||
|
</env:Body>
|
||||||
|
</env:Envelope>"""
|
||||||
|
|
||||||
|
get_cmd_output_response = """\
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<s:Envelope xml:lang="en-US" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
|
||||||
|
<s:Header>
|
||||||
|
<a:Action>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/ReceiveResponse</a:Action>
|
||||||
|
<a:MessageID>uuid:11111111-1111-1111-1111-111111111112</a:MessageID>
|
||||||
|
<a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To>
|
||||||
|
<a:RelatesTo>uuid:11111111-1111-1111-1111-111111111111</a:RelatesTo>
|
||||||
|
</s:Header>
|
||||||
|
<s:Body>
|
||||||
|
<rsp:ReceiveResponse>
|
||||||
|
<rsp:Stream CommandId="11111111-1111-1111-1111-111111111114" Name="stdout">DQpXaW5kb3dzIElQIENvbmZpZ3VyYXRpb24NCg0K</rsp:Stream>
|
||||||
|
<rsp:Stream CommandId="11111111-1111-1111-1111-111111111114" Name="stdout">ICAgSG9zdCBOYW1lIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogV0lORE9XUy1IT1NUCiAgIFByaW1hcnkgRG5zIFN1ZmZpeCAgLiAuIC4gLiAuIC4gLiA6IAogICBOb2RlIFR5cGUgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBIeWJyaWQKICAgSVAgUm91dGluZyBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgV0lOUyBQcm94eSBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KCkV0aGVybmV0IGFkYXB0ZXIgTG9jYWwgQXJlYSBDb25uZWN0aW9uOgoKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IEludGVsKFIpIDgyNTY3Vi0yIEdpZ2FiaXQgTmV0d29yayBDb25uZWN0aW9uCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IEY4LTBGLTQxLTE2LTg4LUU4CiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwogICBMaW5rLWxvY2FsIElQdjYgQWRkcmVzcyAuIC4gLiAuIC4gOiBmZTgwOjphOTkwOjM1ZTM6YTZhYjpmYzE1JTEwKFByZWZlcnJlZCkgCiAgIElQdjQgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE3My4xODUuMTUzLjkzKFByZWZlcnJlZCkgCiAgIFN1Ym5ldCBNYXNrIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDI1NS4yNTUuMjU1LjI0OAogICBEZWZhdWx0IEdhdGV3YXkgLiAuIC4gLiAuIC4gLiAuIC4gOiAxNzMuMTg1LjE1My44OQogICBESENQdjYgSUFJRCAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNTExMzc4NTcKICAgREhDUHY2IENsaWVudCBEVUlELiAuIC4gLiAuIC4gLiAuIDogMDAtMDEtMDAtMDEtMTYtM0ItM0YtQzItRjgtMEYtNDEtMTYtODgtRTgKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMjA3LjkxLjUuMzIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjA4LjY3LjIyMi4yMjIKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRW5hYmxlZAoKRXRoZXJuZXQgYWRhcHRlciBMb2NhbCBBcmVhIENvbm5lY3Rpb24qIDk6CgogICBNZWRpYSBTdGF0ZSAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNZWRpYSBkaXNjb25uZWN0ZWQKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IEp1bmlwZXIgTmV0d29yayBDb25uZWN0IFZpcnR1YWwgQWRhcHRlcgogICBQaHlzaWNhbCBBZGRyZXNzLiAuIC4gLiAuIC4gLiAuIC4gOiAwMC1GRi1BMC04My00OC0wNAogICBESENQIEVuYWJsZWQuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBZZXMKICAgQXV0b2NvbmZpZ3VyYXRpb24gRW5hYmxlZCAuIC4gLiAuIDogWWVzCgpUdW5uZWwgYWRhcHRlciBpc2F0YXAue0FBNDI2QjM3LTM2OTUtNEVCOC05OTBGLTRDRkFDODQ1RkQxN306CgogICBNZWRpYSBTdGF0ZSAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNZWRpYSBkaXNjb25uZWN0ZWQKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE1pY3Jvc29mdCBJU0FUQVAgQWRhcHRlcgogICBQaHlzaWNhbCBBZGRyZXNzLiAuIC4gLiAuIC4gLiAuIC4gOiAwMC0wMC0wMC0wMC0wMC0wMC0wMC1FMAogICBESENQIEVuYWJsZWQuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBObwogICBBdXRvY29uZmlndXJhdGlvbiBFbmFibGVkIC4gLiAuIC4gOiBZZXMKClR1bm5lbCBhZGFwdGVyIFRlcmVkbyBUdW5uZWxpbmcgUHNldWRvLUludGVyZmFjZToKCiAgIENvbm5lY3Rpb24tc3BlY2lmaWMgRE5TIFN1ZmZpeCAgLiA6IAogICBEZXNjcmlwdGlvbiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBUZXJlZG8gVHVubmVsaW5nIFBzZXVkby1JbnRlcmZhY2UKICAgUGh5c2ljYWwgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIDogMDAtMDAtMDAtMDAtMDAtMDAtMDAtRTAKICAgREhDUCBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgQXV0b2NvbmZpZ3VyYXRpb24gRW5hYmxlZCAuIC4gLiAuIDogWWVzCiAgIElQdjYgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDIwMDE6MDo5ZDM4Ojk1M2M6MmNlZjo3ZmM6NTI0Njo2NmEyKFByZWZlcnJlZCkgCiAgIExpbmstbG9jYWwgSVB2NiBBZGRyZXNzIC4gLiAuIC4gLiA6IGZlODA6OjJjZWY6N2ZjOjUyNDY6NjZhMiUxMyhQcmVmZXJyZWQpIAogICBEZWZhdWx0IEdhdGV3YXkgLiAuIC4gLiAuIC4gLiAuIC4gOiAKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRGlzYWJsZWQKClR1bm5lbCBhZGFwdGVyIDZUTzQgQWRhcHRlcjoKCiAgIENvbm5lY3Rpb24tc3BlY2lmaWMgRE5TIFN1ZmZpeCAgLiA6IAogICBEZXNjcmlwdGlvbiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNaWNyb3NvZnQgNnRvNCBBZGFwdGVyICMyCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTAwLTAwLTAwLTAwLTAwLTAwLUUwCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwogICBJUHY2IEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyMDAyOmFkYjk6OTk1ZDo6YWRiOTo5OTVkKFByZWZlcnJlZCkgCiAgIERlZmF1bHQgR2F0ZXdheSAuIC4gLiAuIC4gLiAuIC4gLiA6IDIwMDI6YzA1ODo2MzAxOjpjMDU4OjYzMDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjAwMjpjMDU4OjYzMDE6OjEKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMjA3LjkxLjUuMzIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjA4LjY3LjIyMi4yMjIKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRGlzYWJsZWQKClR1bm5lbCBhZGFwdGVyIGlzYXRhcC57QkExNjBGQzUtNzAyOC00QjFGLUEwNEItMUFDODAyQjBGRjVBfToKCiAgIE1lZGlhIFN0YXRlIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE1lZGlhIGRpc2Nvbm5lY3RlZAogICBDb25uZWN0aW9uLXNwZWNpZmljIEROUyBTdWZmaXggIC4gOiAKICAgRGVzY3JpcHRpb24gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogTWljcm9zb2Z0IElTQVRBUCBBZGFwdGVyICMyCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTAwLTAwLTAwLTAwLTAwLTAwLUUwCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwo=</rsp:Stream>
|
||||||
|
<rsp:Stream CommandId="11111111-1111-1111-1111-111111111114" End="true" Name="stdout"/>
|
||||||
|
<rsp:Stream CommandId="11111111-1111-1111-1111-111111111114" End="true" Name="stderr"/>
|
||||||
|
<rsp:CommandState CommandId="11111111-1111-1111-1111-111111111114" State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done">
|
||||||
|
<rsp:ExitCode>0</rsp:ExitCode>
|
||||||
|
</rsp:CommandState>
|
||||||
|
</rsp:ReceiveResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>"""
|
||||||
|
|
||||||
|
|
||||||
|
def sort_dict(ordered_dict):
|
||||||
|
items = sorted(ordered_dict.items(), key=lambda x: x[0])
|
||||||
|
ordered_dict.clear()
|
||||||
|
for key, value in items:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
sort_dict(value)
|
||||||
|
ordered_dict[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
def xml_str_compare(first, second):
|
||||||
|
first_dict = xmltodict.parse(first)
|
||||||
|
second_dict = xmltodict.parse(second)
|
||||||
|
sort_dict(first_dict)
|
||||||
|
sort_dict(second_dict)
|
||||||
|
return first_dict == second_dict
|
||||||
|
|
||||||
|
|
||||||
|
class TransportStub(object):
|
||||||
|
def send_message(self, message):
|
||||||
|
if xml_str_compare(message, open_shell_request):
|
||||||
|
return open_shell_response
|
||||||
|
elif xml_str_compare(message, close_shell_request):
|
||||||
|
return close_shell_response
|
||||||
|
elif xml_str_compare(
|
||||||
|
message, run_cmd_with_args_request) or xml_str_compare(
|
||||||
|
message, run_cmd_wo_args_request):
|
||||||
|
return run_cmd_response
|
||||||
|
elif xml_str_compare(message, cleanup_cmd_request):
|
||||||
|
return cleanup_cmd_response
|
||||||
|
elif xml_str_compare(message, get_cmd_output_request):
|
||||||
|
return get_cmd_output_response
|
||||||
|
else:
|
||||||
|
raise Exception('Message was not expected')
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(scope='module')
|
||||||
|
def protocol_fake(request):
|
||||||
|
uuid4_patcher = patch('uuid.uuid4')
|
||||||
|
uuid4_mock = uuid4_patcher.start()
|
||||||
|
uuid4_mock.return_value = uuid.UUID(
|
||||||
|
'11111111-1111-1111-1111-111111111111')
|
||||||
|
|
||||||
|
from winrm.protocol import Protocol
|
||||||
|
|
||||||
|
protocol_fake = Protocol(
|
||||||
|
endpoint='http://windows-host:5985/wsman',
|
||||||
|
transport='plaintext',
|
||||||
|
username='john.smith',
|
||||||
|
password='secret')
|
||||||
|
|
||||||
|
protocol_fake.transport = TransportStub()
|
||||||
|
|
||||||
|
def uuid4_patch_stop():
|
||||||
|
uuid4_patcher.stop()
|
||||||
|
|
||||||
|
request.addfinalizer(uuid4_patch_stop)
|
||||||
|
return protocol_fake
|
||||||
|
|
||||||
|
|
||||||
|
@fixture(scope='module')
|
||||||
|
def protocol_real():
|
||||||
|
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
|
||||||
|
if os.path.isfile(config_path):
|
||||||
|
# TODO consider replace json with yaml for integration test settings
|
||||||
|
# TODO json does not support comments
|
||||||
|
settings = json.load(open(config_path))
|
||||||
|
|
||||||
|
from winrm.protocol import Protocol
|
||||||
|
protocol = Protocol(**settings)
|
||||||
|
return protocol
|
||||||
|
else:
|
||||||
|
skip('config.json was not found. Integration tests will be skipped')
|
||||||
0
awx/lib/site-packages/winrm/tests/sample_script.ps1
Normal file
0
awx/lib/site-packages/winrm/tests/sample_script.ps1
Normal file
0
awx/lib/site-packages/winrm/tests/test_cmd.py
Normal file
0
awx/lib/site-packages/winrm/tests/test_cmd.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
xfail = pytest.mark.xfail
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_shell_and_close_shell(protocol_real):
|
||||||
|
shell_id = protocol_real.open_shell()
|
||||||
|
assert re.match('^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', shell_id)
|
||||||
|
|
||||||
|
protocol_real.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_with_arguments_and_cleanup_command(protocol_real):
|
||||||
|
shell_id = protocol_real.open_shell()
|
||||||
|
command_id = protocol_real.run_command(shell_id, 'ipconfig', ['/all'])
|
||||||
|
assert re.match('^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', command_id)
|
||||||
|
|
||||||
|
protocol_real.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_real.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_without_arguments_and_cleanup_command(protocol_real):
|
||||||
|
shell_id = protocol_real.open_shell()
|
||||||
|
command_id = protocol_real.run_command(shell_id, 'hostname')
|
||||||
|
assert re.match('^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', command_id)
|
||||||
|
|
||||||
|
protocol_real.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_real.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_command_output(protocol_real):
|
||||||
|
shell_id = protocol_real.open_shell()
|
||||||
|
command_id = protocol_real.run_command(shell_id, 'ipconfig', ['/all'])
|
||||||
|
std_out, std_err, status_code = protocol_real.get_command_output(
|
||||||
|
shell_id, command_id)
|
||||||
|
|
||||||
|
assert status_code == 0
|
||||||
|
assert 'Windows IP Configuration' in std_out
|
||||||
|
assert len(std_err) == 0
|
||||||
|
|
||||||
|
protocol_real.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_real.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_taking_more_than_60_seconds(protocol_real):
|
||||||
|
shell_id = protocol_real.open_shell()
|
||||||
|
command_id = protocol_real.run_command(shell_id, 'PowerShell -Command Start-Sleep -s 75')
|
||||||
|
assert re.match('^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', command_id)
|
||||||
|
std_out, std_err, status_code = protocol_real.get_command_output(
|
||||||
|
shell_id, command_id)
|
||||||
|
|
||||||
|
assert status_code == 0
|
||||||
|
assert len(std_err) == 0
|
||||||
|
|
||||||
|
protocol_real.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_real.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
@xfail()
|
||||||
|
def test_set_timeout(protocol_real):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@xfail()
|
||||||
|
def test_set_max_env_size(protocol_real):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@xfail()
|
||||||
|
def test_set_locale(protocol_real):
|
||||||
|
raise NotImplementedError()
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import pytest
|
||||||
|
from winrm import Session
|
||||||
|
xfail = pytest.mark.xfail
|
||||||
|
|
||||||
|
|
||||||
|
@xfail()
|
||||||
|
def test_run_cmd():
|
||||||
|
raise NotImplementedError()
|
||||||
35
awx/lib/site-packages/winrm/tests/test_protocol.py
Normal file
35
awx/lib/site-packages/winrm/tests/test_protocol.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
def test_open_shell_and_close_shell(protocol_fake):
|
||||||
|
shell_id = protocol_fake.open_shell()
|
||||||
|
assert shell_id == '11111111-1111-1111-1111-111111111113'
|
||||||
|
|
||||||
|
protocol_fake.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_with_arguments_and_cleanup_command(protocol_fake):
|
||||||
|
shell_id = protocol_fake.open_shell()
|
||||||
|
command_id = protocol_fake.run_command(shell_id, 'ipconfig', ['/all'])
|
||||||
|
assert command_id == '11111111-1111-1111-1111-111111111114'
|
||||||
|
|
||||||
|
protocol_fake.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_fake.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_command_without_arguments_and_cleanup_command(protocol_fake):
|
||||||
|
shell_id = protocol_fake.open_shell()
|
||||||
|
command_id = protocol_fake.run_command(shell_id, 'hostname')
|
||||||
|
assert command_id == '11111111-1111-1111-1111-111111111114'
|
||||||
|
|
||||||
|
protocol_fake.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_fake.close_shell(shell_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_command_output(protocol_fake):
|
||||||
|
shell_id = protocol_fake.open_shell()
|
||||||
|
command_id = protocol_fake.run_command(shell_id, 'ipconfig', ['/all'])
|
||||||
|
std_out, std_err, status_code = protocol_fake.get_command_output(shell_id, command_id)
|
||||||
|
assert status_code == 0
|
||||||
|
assert 'Windows IP Configuration' in std_out
|
||||||
|
assert len(std_err) == 0
|
||||||
|
|
||||||
|
protocol_fake.cleanup_command(shell_id, command_id)
|
||||||
|
protocol_fake.close_shell(shell_id)
|
||||||
13
awx/lib/site-packages/winrm/tests/test_session.py
Normal file
13
awx/lib/site-packages/winrm/tests/test_session.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from winrm import Session
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_cmd(protocol_fake):
|
||||||
|
#TODO this test should cover __init__ method
|
||||||
|
s = Session('windows-host', auth=('john.smith', 'secret'))
|
||||||
|
s.protocol = protocol_fake
|
||||||
|
|
||||||
|
r = s.run_cmd('ipconfig', ['/all'])
|
||||||
|
|
||||||
|
assert r.status_code == 0
|
||||||
|
assert 'Windows IP Configuration' in r.std_out
|
||||||
|
assert len(r.std_err) == 0
|
||||||
0
awx/lib/site-packages/winrm/tests/test_wql.py
Normal file
0
awx/lib/site-packages/winrm/tests/test_wql.py
Normal file
229
awx/lib/site-packages/winrm/transport.py
Normal file
229
awx/lib/site-packages/winrm/transport.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
import sys
|
||||||
|
import base64
|
||||||
|
from winrm.exceptions import WinRMTransportError
|
||||||
|
|
||||||
|
HAVE_KERBEROS=False
|
||||||
|
try:
|
||||||
|
import kerberos
|
||||||
|
HAVE_KERBEROS=True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
is_py2 = sys.version[0] == '2'
|
||||||
|
if is_py2:
|
||||||
|
from urllib2 import Request, URLError, HTTPError, HTTPBasicAuthHandler, HTTPPasswordMgrWithDefaultRealm, HTTPSHandler
|
||||||
|
from urllib2 import urlopen, build_opener, install_opener
|
||||||
|
from urlparse import urlparse
|
||||||
|
from httplib import HTTPSConnection
|
||||||
|
else:
|
||||||
|
from urllib.request import Request, URLError, HTTPError, HTTPBasicAuthHandler, HTTPPasswordMgrWithDefaultRealm, HTTPSHandler
|
||||||
|
from urllib.request import urlopen, build_opener, install_opener
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from http.client import HTTPSConnection
|
||||||
|
|
||||||
|
|
||||||
|
class HttpTransport(object):
|
||||||
|
def __init__(self, endpoint, username, password):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.user_agent = 'Python WinRM client'
|
||||||
|
self.timeout = 3600 # Set this to an unreasonable amount for now because WinRM has timeouts
|
||||||
|
|
||||||
|
def basic_auth_only(self):
|
||||||
|
#here we should remove handler for any authentication handlers other than basic
|
||||||
|
# but maybe leave original credentials
|
||||||
|
|
||||||
|
# auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
||||||
|
# auths.delete_if {|i| i.scheme !~ /basic/i}
|
||||||
|
# drop all variables in auths if they not contains "basic" as insensitive.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def no_sspi_auth(self):
|
||||||
|
# here we should remove handler for Negotiate/NTLM negotiation
|
||||||
|
# but maybe leave original credentials
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HttpPlaintext(HttpTransport):
|
||||||
|
def __init__(self, endpoint, username='', password='', disable_sspi=True, basic_auth_only=True):
|
||||||
|
super(HttpPlaintext, self).__init__(endpoint, username, password)
|
||||||
|
if disable_sspi:
|
||||||
|
self.no_sspi_auth()
|
||||||
|
if basic_auth_only:
|
||||||
|
self.basic_auth_only()
|
||||||
|
|
||||||
|
self._headers = {'Content-Type' : 'application/soap+xml;charset=UTF-8',
|
||||||
|
'User-Agent' : 'Python WinRM client'}
|
||||||
|
|
||||||
|
def _setup_opener(self):
|
||||||
|
password_manager = HTTPPasswordMgrWithDefaultRealm()
|
||||||
|
password_manager.add_password(None, self.endpoint, self.username, self.password)
|
||||||
|
auth_manager = HTTPBasicAuthHandler(password_manager)
|
||||||
|
opener = build_opener(auth_manager)
|
||||||
|
install_opener(opener)
|
||||||
|
|
||||||
|
def send_message(self, message):
|
||||||
|
headers = self._headers.copy()
|
||||||
|
headers['Content-Length'] = len(message)
|
||||||
|
|
||||||
|
self._setup_opener()
|
||||||
|
request = Request(self.endpoint, data=message, headers=headers)
|
||||||
|
try:
|
||||||
|
response = urlopen(request, timeout=self.timeout)
|
||||||
|
# Version 1.1 of WinRM adds the namespaces in the document instead of the envelope so we have to
|
||||||
|
# add them ourselves here. This should have no affect version 2.
|
||||||
|
response_text = response.read()
|
||||||
|
return response_text
|
||||||
|
#doc = ElementTree.fromstring(response.read())
|
||||||
|
#Ruby
|
||||||
|
#doc = Nokogiri::XML(resp.http_body.content)
|
||||||
|
#doc.collect_namespaces.each_pair do |k,v|
|
||||||
|
# doc.root.add_namespace((k.split(/:/).last),v) unless doc.namespaces.has_key?(k)
|
||||||
|
#end
|
||||||
|
#return doc
|
||||||
|
#return doc
|
||||||
|
except HTTPError as ex:
|
||||||
|
response_text = ex.read()
|
||||||
|
# Per http://msdn.microsoft.com/en-us/library/cc251676.aspx rule 3,
|
||||||
|
# should handle this 500 error and retry receiving command output.
|
||||||
|
if 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive' in message and 'Code="2150858793"' in response_text:
|
||||||
|
return response_text
|
||||||
|
error_message = 'Bad HTTP response returned from server. Code {0}'.format(ex.code)
|
||||||
|
if ex.msg:
|
||||||
|
error_message += ', {0}'.format(ex.msg)
|
||||||
|
raise WinRMTransportError(error_message)
|
||||||
|
except URLError as ex:
|
||||||
|
raise WinRMTransportError(ex.reason)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPSClientAuthHandler(HTTPSHandler):
|
||||||
|
def __init__(self, cert, key):
|
||||||
|
HTTPSHandler.__init__(self)
|
||||||
|
self.cert = cert
|
||||||
|
self.key = key
|
||||||
|
|
||||||
|
def https_open(self, req):
|
||||||
|
return self.do_open(self.getConnection, req)
|
||||||
|
|
||||||
|
def getConnection(self, host, timeout=300):
|
||||||
|
return HTTPSConnection(host, key_file=self.key, cert_file=self.cert)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpSSL(HttpPlaintext):
|
||||||
|
"""Uses SSL to secure the transport"""
|
||||||
|
def __init__(self, endpoint, username, password, ca_trust_path=None, disable_sspi=True, basic_auth_only=True,
|
||||||
|
cert_pem=None, cert_key_pem=None):
|
||||||
|
super(HttpSSL, self).__init__(endpoint, username, password)
|
||||||
|
|
||||||
|
self._cert_pem = cert_pem
|
||||||
|
self._cert_key_pem = cert_key_pem
|
||||||
|
|
||||||
|
#Ruby
|
||||||
|
#@httpcli.set_auth(endpoint, user, pass)
|
||||||
|
#@httpcli.ssl_config.set_trust_ca(ca_trust_path) unless ca_trust_path.nil?
|
||||||
|
if disable_sspi:
|
||||||
|
self.no_sspi_auth()
|
||||||
|
if basic_auth_only:
|
||||||
|
self.basic_auth_only()
|
||||||
|
|
||||||
|
if self._cert_pem:
|
||||||
|
self._headers['Authorization'] = "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual"
|
||||||
|
|
||||||
|
def _setup_opener(self):
|
||||||
|
if not self._cert_pem:
|
||||||
|
super(HttpSSL, self)._setup_opener()
|
||||||
|
else:
|
||||||
|
opener = build_opener(HTTPSClientAuthHandler(self._cert_pem, self._cert_key_pem))
|
||||||
|
install_opener(opener)
|
||||||
|
|
||||||
|
|
||||||
|
class KerberosTicket:
|
||||||
|
"""
|
||||||
|
Implementation based on http://ncoghlan_devs-python-notes.readthedocs.org/en/latest/python_kerberos.html
|
||||||
|
"""
|
||||||
|
def __init__(self, service):
|
||||||
|
ignored_code, krb_context = kerberos.authGSSClientInit(service)
|
||||||
|
kerberos.authGSSClientStep(krb_context, '')
|
||||||
|
# TODO authGSSClientStep may raise following error:
|
||||||
|
#GSSError: (('Unspecified GSS failure. Minor code may provide more information', 851968), ("Credentials cache file '/tmp/krb5cc_1000' not found", -1765328189))
|
||||||
|
self._krb_context = krb_context
|
||||||
|
gss_response = kerberos.authGSSClientResponse(krb_context)
|
||||||
|
self.auth_header = 'Negotiate {0}'.format(gss_response)
|
||||||
|
|
||||||
|
def verify_response(self, auth_header):
|
||||||
|
# Handle comma-separated lists of authentication fields
|
||||||
|
for field in auth_header.split(','):
|
||||||
|
kind, ignored_space, details = field.strip().partition(' ')
|
||||||
|
if kind.lower() == 'negotiate':
|
||||||
|
auth_details = details.strip()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError('Negotiate not found in {0}'.format(auth_header))
|
||||||
|
# Finish the Kerberos handshake
|
||||||
|
krb_context = self._krb_context
|
||||||
|
if krb_context is None:
|
||||||
|
raise RuntimeError('Ticket already used for verification')
|
||||||
|
self._krb_context = None
|
||||||
|
kerberos.authGSSClientStep(krb_context, auth_details)
|
||||||
|
#print('User {0} authenticated successfully using Kerberos authentication'.format(kerberos.authGSSClientUserName(krb_context)))
|
||||||
|
kerberos.authGSSClientClean(krb_context)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpKerberos(HttpTransport):
|
||||||
|
def __init__(self, endpoint, realm=None, service='HTTP', keytab=None):
|
||||||
|
"""
|
||||||
|
Uses Kerberos/GSS-API to authenticate and encrypt messages
|
||||||
|
@param string endpoint: the WinRM webservice endpoint
|
||||||
|
@param string realm: the Kerberos realm we are authenticating to
|
||||||
|
@param string service: the service name, default is HTTP
|
||||||
|
@param string keytab: the path to a keytab file if you are using one
|
||||||
|
"""
|
||||||
|
if not HAVE_KERBEROS:
|
||||||
|
raise WinRMTransportError('kerberos is not installed')
|
||||||
|
|
||||||
|
super(HttpKerberos, self).__init__(endpoint, None, None)
|
||||||
|
self.krb_service = '{0}@{1}'.format(service, urlparse(endpoint).hostname)
|
||||||
|
#self.krb_ticket = KerberosTicket(krb_service)
|
||||||
|
|
||||||
|
def set_auth(self, username, password):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def send_message(self, message):
|
||||||
|
# TODO current implementation does negotiation on each HTTP request which is not efficient
|
||||||
|
# TODO support kerberos session with message encryption
|
||||||
|
krb_ticket = KerberosTicket(self.krb_service)
|
||||||
|
headers = {'Authorization': krb_ticket.auth_header,
|
||||||
|
'Connection': 'Keep-Alive',
|
||||||
|
'Content-Type': 'application/soap+xml;charset=UTF-8',
|
||||||
|
'User-Agent': 'Python WinRM client'}
|
||||||
|
|
||||||
|
request = Request(self.endpoint, data=message, headers=headers)
|
||||||
|
try:
|
||||||
|
response = urlopen(request, timeout=self.timeout)
|
||||||
|
krb_ticket.verify_response(response.headers['WWW-Authenticate'])
|
||||||
|
response_text = response.read()
|
||||||
|
return response_text
|
||||||
|
except HTTPError as ex:
|
||||||
|
response_text = ex.read()
|
||||||
|
# Per http://msdn.microsoft.com/en-us/library/cc251676.aspx rule 3,
|
||||||
|
# should handle this 500 error and retry receiving command output.
|
||||||
|
if 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive' in message and 'Code="2150858793"' in response_text:
|
||||||
|
return response_text
|
||||||
|
#if ex.code == 401 and ex.headers['WWW-Authenticate'] == 'Negotiate, Basic realm="WSMAN"':
|
||||||
|
error_message = 'Kerberos-based authentication was failed. Code {0}'.format(ex.code)
|
||||||
|
if ex.msg:
|
||||||
|
error_message += ', {0}'.format(ex.msg)
|
||||||
|
raise WinRMTransportError(error_message)
|
||||||
|
except URLError as ex:
|
||||||
|
raise WinRMTransportError(ex.reason)
|
||||||
|
|
||||||
|
def _winrm_encrypt(self, string):
|
||||||
|
"""
|
||||||
|
@returns the encrypted request string
|
||||||
|
@rtype string
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _winrm_decrypt(self, string):
|
||||||
|
raise NotImplementedError
|
||||||
359
awx/lib/site-packages/xmltodict.py
Normal file
359
awx/lib/site-packages/xmltodict.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"Makes working with XML feel like you are working with JSON"
|
||||||
|
|
||||||
|
from xml.parsers import expat
|
||||||
|
from xml.sax.saxutils import XMLGenerator
|
||||||
|
from xml.sax.xmlreader import AttributesImpl
|
||||||
|
try: # pragma no cover
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError: # pragma no cover
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
try: # pragma no cover
|
||||||
|
from collections import OrderedDict
|
||||||
|
except ImportError: # pragma no cover
|
||||||
|
try:
|
||||||
|
from ordereddict import OrderedDict
|
||||||
|
except ImportError:
|
||||||
|
OrderedDict = dict
|
||||||
|
|
||||||
|
try: # pragma no cover
|
||||||
|
_basestring = basestring
|
||||||
|
except NameError: # pragma no cover
|
||||||
|
_basestring = str
|
||||||
|
try: # pragma no cover
|
||||||
|
_unicode = unicode
|
||||||
|
except NameError: # pragma no cover
|
||||||
|
_unicode = str
|
||||||
|
|
||||||
|
__author__ = 'Martin Blech'
|
||||||
|
__version__ = '0.9.0'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingInterrupted(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _DictSAXHandler(object):
|
||||||
|
def __init__(self,
|
||||||
|
item_depth=0,
|
||||||
|
item_callback=lambda *args: True,
|
||||||
|
xml_attribs=True,
|
||||||
|
attr_prefix='@',
|
||||||
|
cdata_key='#text',
|
||||||
|
force_cdata=False,
|
||||||
|
cdata_separator='',
|
||||||
|
postprocessor=None,
|
||||||
|
dict_constructor=OrderedDict,
|
||||||
|
strip_whitespace=True,
|
||||||
|
namespace_separator=':',
|
||||||
|
namespaces=None):
|
||||||
|
self.path = []
|
||||||
|
self.stack = []
|
||||||
|
self.data = None
|
||||||
|
self.item = None
|
||||||
|
self.item_depth = item_depth
|
||||||
|
self.xml_attribs = xml_attribs
|
||||||
|
self.item_callback = item_callback
|
||||||
|
self.attr_prefix = attr_prefix
|
||||||
|
self.cdata_key = cdata_key
|
||||||
|
self.force_cdata = force_cdata
|
||||||
|
self.cdata_separator = cdata_separator
|
||||||
|
self.postprocessor = postprocessor
|
||||||
|
self.dict_constructor = dict_constructor
|
||||||
|
self.strip_whitespace = strip_whitespace
|
||||||
|
self.namespace_separator = namespace_separator
|
||||||
|
self.namespaces = namespaces
|
||||||
|
|
||||||
|
def _build_name(self, full_name):
|
||||||
|
if not self.namespaces:
|
||||||
|
return full_name
|
||||||
|
i = full_name.rfind(self.namespace_separator)
|
||||||
|
if i == -1:
|
||||||
|
return full_name
|
||||||
|
namespace, name = full_name[:i], full_name[i+1:]
|
||||||
|
short_namespace = self.namespaces.get(namespace, namespace)
|
||||||
|
if not short_namespace:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
return self.namespace_separator.join((short_namespace, name))
|
||||||
|
|
||||||
|
def _attrs_to_dict(self, attrs):
|
||||||
|
if isinstance(attrs, dict):
|
||||||
|
return attrs
|
||||||
|
return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
|
||||||
|
|
||||||
|
def startElement(self, full_name, attrs):
|
||||||
|
name = self._build_name(full_name)
|
||||||
|
attrs = self._attrs_to_dict(attrs)
|
||||||
|
self.path.append((name, attrs or None))
|
||||||
|
if len(self.path) > self.item_depth:
|
||||||
|
self.stack.append((self.item, self.data))
|
||||||
|
if self.xml_attribs:
|
||||||
|
attrs = self.dict_constructor(
|
||||||
|
(self.attr_prefix+key, value)
|
||||||
|
for (key, value) in attrs.items())
|
||||||
|
else:
|
||||||
|
attrs = None
|
||||||
|
self.item = attrs or None
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
def endElement(self, full_name):
|
||||||
|
name = self._build_name(full_name)
|
||||||
|
if len(self.path) == self.item_depth:
|
||||||
|
item = self.item
|
||||||
|
if item is None:
|
||||||
|
item = self.data
|
||||||
|
should_continue = self.item_callback(self.path, item)
|
||||||
|
if not should_continue:
|
||||||
|
raise ParsingInterrupted()
|
||||||
|
if len(self.stack):
|
||||||
|
item, data = self.item, self.data
|
||||||
|
self.item, self.data = self.stack.pop()
|
||||||
|
if self.strip_whitespace and data is not None:
|
||||||
|
data = data.strip() or None
|
||||||
|
if data and self.force_cdata and item is None:
|
||||||
|
item = self.dict_constructor()
|
||||||
|
if item is not None:
|
||||||
|
if data:
|
||||||
|
self.push_data(item, self.cdata_key, data)
|
||||||
|
self.item = self.push_data(self.item, name, item)
|
||||||
|
else:
|
||||||
|
self.item = self.push_data(self.item, name, data)
|
||||||
|
else:
|
||||||
|
self.item = self.data = None
|
||||||
|
self.path.pop()
|
||||||
|
|
||||||
|
def characters(self, data):
|
||||||
|
if not self.data:
|
||||||
|
self.data = data
|
||||||
|
else:
|
||||||
|
self.data += self.cdata_separator + data
|
||||||
|
|
||||||
|
def push_data(self, item, key, data):
|
||||||
|
if self.postprocessor is not None:
|
||||||
|
result = self.postprocessor(self.path, key, data)
|
||||||
|
if result is None:
|
||||||
|
return item
|
||||||
|
key, data = result
|
||||||
|
if item is None:
|
||||||
|
item = self.dict_constructor()
|
||||||
|
try:
|
||||||
|
value = item[key]
|
||||||
|
if isinstance(value, list):
|
||||||
|
value.append(data)
|
||||||
|
else:
|
||||||
|
item[key] = [value, data]
|
||||||
|
except KeyError:
|
||||||
|
item[key] = data
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
|
||||||
|
namespace_separator=':', **kwargs):
|
||||||
|
"""Parse the given XML input and convert it into a dictionary.
|
||||||
|
|
||||||
|
`xml_input` can either be a `string` or a file-like object.
|
||||||
|
|
||||||
|
If `xml_attribs` is `True`, element attributes are put in the dictionary
|
||||||
|
among regular child elements, using `@` as a prefix to avoid collisions. If
|
||||||
|
set to `False`, they are just ignored.
|
||||||
|
|
||||||
|
Simple example::
|
||||||
|
|
||||||
|
>>> import xmltodict
|
||||||
|
>>> doc = xmltodict.parse(\"\"\"
|
||||||
|
... <a prop="x">
|
||||||
|
... <b>1</b>
|
||||||
|
... <b>2</b>
|
||||||
|
... </a>
|
||||||
|
... \"\"\")
|
||||||
|
>>> doc['a']['@prop']
|
||||||
|
u'x'
|
||||||
|
>>> doc['a']['b']
|
||||||
|
[u'1', u'2']
|
||||||
|
|
||||||
|
If `item_depth` is `0`, the function returns a dictionary for the root
|
||||||
|
element (default behavior). Otherwise, it calls `item_callback` every time
|
||||||
|
an item at the specified depth is found and returns `None` in the end
|
||||||
|
(streaming mode).
|
||||||
|
|
||||||
|
The callback function receives two parameters: the `path` from the document
|
||||||
|
root to the item (name-attribs pairs), and the `item` (dict). If the
|
||||||
|
callback's return value is false-ish, parsing will be stopped with the
|
||||||
|
:class:`ParsingInterrupted` exception.
|
||||||
|
|
||||||
|
Streaming example::
|
||||||
|
|
||||||
|
>>> def handle(path, item):
|
||||||
|
... print 'path:%s item:%s' % (path, item)
|
||||||
|
... return True
|
||||||
|
...
|
||||||
|
>>> xmltodict.parse(\"\"\"
|
||||||
|
... <a prop="x">
|
||||||
|
... <b>1</b>
|
||||||
|
... <b>2</b>
|
||||||
|
... </a>\"\"\", item_depth=2, item_callback=handle)
|
||||||
|
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
|
||||||
|
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
|
||||||
|
|
||||||
|
The optional argument `postprocessor` is a function that takes `path`,
|
||||||
|
`key` and `value` as positional arguments and returns a new `(key, value)`
|
||||||
|
pair where both `key` and `value` may have changed. Usage example::
|
||||||
|
|
||||||
|
>>> def postprocessor(path, key, value):
|
||||||
|
... try:
|
||||||
|
... return key + ':int', int(value)
|
||||||
|
... except (ValueError, TypeError):
|
||||||
|
... return key, value
|
||||||
|
>>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
|
||||||
|
... postprocessor=postprocessor)
|
||||||
|
OrderedDict([(u'a', OrderedDict([(u'b:int', [1, 2]), (u'b', u'x')]))])
|
||||||
|
|
||||||
|
You can pass an alternate version of `expat` (such as `defusedexpat`) by
|
||||||
|
using the `expat` parameter. E.g:
|
||||||
|
|
||||||
|
>>> import defusedexpat
|
||||||
|
>>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
|
||||||
|
OrderedDict([(u'a', u'hello')])
|
||||||
|
|
||||||
|
"""
|
||||||
|
handler = _DictSAXHandler(namespace_separator=namespace_separator,
|
||||||
|
**kwargs)
|
||||||
|
if isinstance(xml_input, _unicode):
|
||||||
|
if not encoding:
|
||||||
|
encoding = 'utf-8'
|
||||||
|
xml_input = xml_input.encode(encoding)
|
||||||
|
if not process_namespaces:
|
||||||
|
namespace_separator = None
|
||||||
|
parser = expat.ParserCreate(
|
||||||
|
encoding,
|
||||||
|
namespace_separator
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
parser.ordered_attributes = True
|
||||||
|
except AttributeError:
|
||||||
|
# Jython's expat does not support ordered_attributes
|
||||||
|
pass
|
||||||
|
parser.StartElementHandler = handler.startElement
|
||||||
|
parser.EndElementHandler = handler.endElement
|
||||||
|
parser.CharacterDataHandler = handler.characters
|
||||||
|
parser.buffer_text = True
|
||||||
|
try:
|
||||||
|
parser.ParseFile(xml_input)
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
parser.Parse(xml_input, True)
|
||||||
|
return handler.item
|
||||||
|
|
||||||
|
|
||||||
|
def _emit(key, value, content_handler,
|
||||||
|
attr_prefix='@',
|
||||||
|
cdata_key='#text',
|
||||||
|
depth=0,
|
||||||
|
preprocessor=None,
|
||||||
|
pretty=False,
|
||||||
|
newl='\n',
|
||||||
|
indent='\t'):
|
||||||
|
if preprocessor is not None:
|
||||||
|
result = preprocessor(key, value)
|
||||||
|
if result is None:
|
||||||
|
return
|
||||||
|
key, value = result
|
||||||
|
if not isinstance(value, (list, tuple)):
|
||||||
|
value = [value]
|
||||||
|
if depth == 0 and len(value) > 1:
|
||||||
|
raise ValueError('document with multiple roots')
|
||||||
|
for v in value:
|
||||||
|
if v is None:
|
||||||
|
v = OrderedDict()
|
||||||
|
elif not isinstance(v, dict):
|
||||||
|
v = _unicode(v)
|
||||||
|
if isinstance(v, _basestring):
|
||||||
|
v = OrderedDict(((cdata_key, v),))
|
||||||
|
cdata = None
|
||||||
|
attrs = OrderedDict()
|
||||||
|
children = []
|
||||||
|
for ik, iv in v.items():
|
||||||
|
if ik == cdata_key:
|
||||||
|
cdata = iv
|
||||||
|
continue
|
||||||
|
if ik.startswith(attr_prefix):
|
||||||
|
attrs[ik[len(attr_prefix):]] = iv
|
||||||
|
continue
|
||||||
|
children.append((ik, iv))
|
||||||
|
if pretty:
|
||||||
|
content_handler.ignorableWhitespace(depth * indent)
|
||||||
|
content_handler.startElement(key, AttributesImpl(attrs))
|
||||||
|
if pretty and children:
|
||||||
|
content_handler.ignorableWhitespace(newl)
|
||||||
|
for child_key, child_value in children:
|
||||||
|
_emit(child_key, child_value, content_handler,
|
||||||
|
attr_prefix, cdata_key, depth+1, preprocessor,
|
||||||
|
pretty, newl, indent)
|
||||||
|
if cdata is not None:
|
||||||
|
content_handler.characters(cdata)
|
||||||
|
if pretty and children:
|
||||||
|
content_handler.ignorableWhitespace(depth * indent)
|
||||||
|
content_handler.endElement(key)
|
||||||
|
if pretty and depth:
|
||||||
|
content_handler.ignorableWhitespace(newl)
|
||||||
|
|
||||||
|
|
||||||
|
def unparse(input_dict, output=None, encoding='utf-8', full_document=True,
|
||||||
|
**kwargs):
|
||||||
|
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
|
||||||
|
|
||||||
|
The resulting XML document is returned as a string, but if `output` (a
|
||||||
|
file-like object) is specified, it is written there instead.
|
||||||
|
|
||||||
|
Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
|
||||||
|
as XML node attributes, whereas keys equal to `cdata_key`
|
||||||
|
(default=`'#text'`) are treated as character data.
|
||||||
|
|
||||||
|
The `pretty` parameter (default=`False`) enables pretty-printing. In this
|
||||||
|
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
|
||||||
|
can be customized with the `newl` and `indent` parameters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
((key, value),) = input_dict.items()
|
||||||
|
must_return = False
|
||||||
|
if output is None:
|
||||||
|
output = StringIO()
|
||||||
|
must_return = True
|
||||||
|
content_handler = XMLGenerator(output, encoding)
|
||||||
|
if full_document:
|
||||||
|
content_handler.startDocument()
|
||||||
|
_emit(key, value, content_handler, **kwargs)
|
||||||
|
if full_document:
|
||||||
|
content_handler.endDocument()
|
||||||
|
if must_return:
|
||||||
|
value = output.getvalue()
|
||||||
|
try: # pragma no cover
|
||||||
|
value = value.decode(encoding)
|
||||||
|
except AttributeError: # pragma no cover
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
|
if __name__ == '__main__': # pragma: no cover
|
||||||
|
import sys
|
||||||
|
import marshal
|
||||||
|
|
||||||
|
(item_depth,) = sys.argv[1:]
|
||||||
|
item_depth = int(item_depth)
|
||||||
|
|
||||||
|
def handle_item(path, item):
|
||||||
|
marshal.dump((path, item), sys.stdout)
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
root = parse(sys.stdin,
|
||||||
|
item_depth=item_depth,
|
||||||
|
item_callback=handle_item,
|
||||||
|
dict_constructor=dict)
|
||||||
|
if item_depth == 0:
|
||||||
|
handle_item([], root)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
BIN
requirements/isodate-0.5.0.tar.gz
Normal file
BIN
requirements/isodate-0.5.0.tar.gz
Normal file
Binary file not shown.
BIN
requirements/pywinrm-master.zip
Normal file
BIN
requirements/pywinrm-master.zip
Normal file
Binary file not shown.
BIN
requirements/xmltodict-0.9.0.tar.gz
Normal file
BIN
requirements/xmltodict-0.9.0.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user