Add server version to master api

Add IsEvadedOffense to EFPenalty
Fix remote log reading in not Windows
This commit is contained in:
RaidMax 2018-12-16 21:16:56 -06:00
parent b77bdbe793
commit 12cf2e8247
1676 changed files with 287228 additions and 66 deletions

View File

@ -13,6 +13,8 @@ namespace IW4MAdmin.Application.API.Master
public string IPAddress { get; set; }
[JsonProperty("port")]
public short Port { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("gametype")]
public string Gametype { get; set; }
[JsonProperty("map")]

View File

@ -39,6 +39,7 @@ namespace IW4MAdmin.Application.API.Master
{
ClientNum = s.ClientNum,
Game = s.GameName.ToString(),
Version = s.Version,
Gametype = s.Gametype,
Hostname = s.Hostname,
Map = s.CurrentMap.Name,

View File

@ -6,7 +6,7 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2.2.2</Version>
<Version>2.2.2.3</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
@ -31,8 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.2.2</AssemblyVersion>
<FileVersion>2.2.2.2</FileVersion>
<AssemblyVersion>2.2.2.3</AssemblyVersion>
<FileVersion>2.2.2.3</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -68,7 +68,11 @@ namespace IW4MAdmin
// this only happens if the preconnect event occurred from RCon polling
if (clientFromLog.IPAddress.HasValue)
{
await client.OnJoin(clientFromLog.IPAddress);
// they're banned so we don't want to add them as connected
if (!await client.OnJoin(clientFromLog.IPAddress))
{
return;
}
}
client.OnConnect();
@ -219,6 +223,7 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Flag)
{
// todo: maybe move this to a seperate function
Penalty newPenalty = new Penalty()
{
Type = Penalty.PenaltyType.Flag,
@ -259,7 +264,8 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Ban)
{
await Ban(E.Data, E.Target, E.Origin);
await Ban(E.Data, E.Target, E.Origin, E.Extra as bool? ?? false);
}
else if (E.Type == GameEvent.EventType.Unban)
@ -675,6 +681,7 @@ namespace IW4MAdmin
}
var version = await this.GetDvarAsync<string>("version");
Version = version.Value;
GameName = Utilities.GetGame(version.Value);
if (GameName == Game.IW4)
@ -763,42 +770,45 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir();
#if DEBUG
// basepath.Value = @"D:\";
#endif
string logPath = string.Empty;
LogPath = game == string.Empty ?
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" :
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}";
bool remoteLog = false;
if (GameName == Game.IW5 || ServerConfig.ManualLogPath?.Length > 0)
{
logPath = ServerConfig.ManualLogPath;
remoteLog = logPath.StartsWith("http");
}
else
{
logPath = LogPath;
}
// hopefully fix wine drive name mangling
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (remoteLog)
{
logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{LogPath}", @"[A-Z]:/", "");
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
}
if (!File.Exists(logPath) && !logPath.StartsWith("http"))
{
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}");
#else
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
#endif
}
else
{
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
// fix wine drive name mangling
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{LogPath}", @"[A-Z]:/", "");
}
if (!File.Exists(logPath))
{
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}");
#else
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
#endif
}
}
Logger.WriteInfo($"Log file is {logPath}");
@ -930,21 +940,21 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().Create(newPenalty);
}
override protected async Task Ban(String Message, EFClient Target, EFClient Origin)
override protected async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade)
{
// ensure player gets banned if command not performed on them in game
if (Target.ClientNumber < 0)
if (targetClient.ClientNumber < 0)
{
EFClient ingameClient = null;
ingameClient = Manager.GetServers()
.Select(s => s.GetClientsAsList())
.FirstOrDefault(l => l.FirstOrDefault(c => c.ClientId == Target?.ClientId) != null)
?.First(c => c.ClientId == Target.ClientId);
.FirstOrDefault(l => l.FirstOrDefault(c => c.ClientId == targetClient?.ClientId) != null)
?.First(c => c.ClientId == targetClient.ClientId);
if (ingameClient != null)
{
await Ban(Message, ingameClient, Origin);
await Ban(reason, ingameClient, originClient, isEvade);
return;
}
}
@ -952,13 +962,13 @@ namespace IW4MAdmin
else
{
// this is set only because they're still in the server.
Target.Level = EFClient.Permission.Banned;
targetClient.Level = EFClient.Permission.Banned;
#if !DEBUG
string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");
await Target.CurrentServer.ExecuteCommandAsync(formattedString);
string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
#else
await Target.CurrentServer.OnClientDisconnected(Target);
await targetClient.CurrentServer.OnClientDisconnected(targetClient);
#endif
}
@ -966,13 +976,14 @@ namespace IW4MAdmin
{
Type = Penalty.PenaltyType.Ban,
Expires = null,
Offender = Target,
Offense = Message,
Punisher = Origin,
Offender = targetClient,
Offense = reason,
Punisher = originClient,
Active = true,
When = DateTime.UtcNow,
Link = Target.AliasLink,
AutomatedOffense = Origin.AdministeredPenalties?.FirstOrDefault()?.AutomatedOffense
Link = targetClient.AliasLink,
AutomatedOffense = originClient.AdministeredPenalties?.FirstOrDefault()?.AutomatedOffense,
IsEvadedOffense = isEvade
};
await Manager.GetPenaltyService().Create(newPenalty);

View File

@ -106,16 +106,11 @@ namespace IW4MAdmin.Application.RconParsers
ping = int.Parse(regex.Groups[3].Value);
}
else
{
continue;
}
long networkId = regex.Groups[4].Value.ConvertLong();
string name = regex.Groups[5].Value.StripColors().Trim();
int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
int? ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
var P = new EFClient()
var client = new EFClient()
{
CurrentAlias = new EFAlias()
{
@ -123,13 +118,19 @@ namespace IW4MAdmin.Application.RconParsers
},
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip == 0 ? int.MinValue : ip,
IPAddress = ip,
Ping = ping,
Score = score,
IsBot = ip == 0,
IsBot = ip == null,
State = EFClient.ClientState.Connecting
};
StatusPlayers.Add(P);
StatusPlayers.Add(client);
// they've not fully connected yet
if (!client.IsBot && ping == 999)
{
continue;
}
}
}

View File

@ -142,7 +142,7 @@ namespace IW4MAdmin.Application.RconParsers
long networkId = 0;//playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
int? ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);

View File

@ -97,7 +97,7 @@ namespace IW4MAdmin.Application.RconParsers
#if DEBUG
Ping = 1;
#endif
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
int? ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
var p = new EFClient()
{
@ -108,7 +108,7 @@ namespace IW4MAdmin.Application.RconParsers
Ping = Ping,
Score = 0,
State = EFClient.ClientState.Connecting,
IsBot = networkId == 0
IsBot = ipAddress == null
};
if (p.IsBot)

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>Master</Name>
<RootNamespace>Master</RootNamespace>
<InterpreterId>MSBuild|env|X:\IW4MAdmin\GameLogServer\GameLogServer.pyproj</InterpreterId>
<InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
<IsWindowsApplication>False</IsWindowsApplication>
<PythonRunWebServerCommand>
</PythonRunWebServerCommand>
@ -119,7 +119,15 @@
<Content Include="master\templates\layout.html" />
</ItemGroup>
<ItemGroup>
<InterpreterReference Include="MSBuild|env|X:\IW4MAdmin\GameLogServer\GameLogServer.pyproj" />
<Interpreter Include="env_master\">
<Id>env_master</Id>
<Version>3.6</Version>
<Description>env_master (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
<Architecture>X64</Architecture>
</Interpreter>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Specify pre- and post-build commands in the BeforeBuild and

View File

@ -0,0 +1,140 @@
Metadata-Version: 2.1
Name: APScheduler
Version: 3.5.3
Summary: In-process task scheduler with Cron-like capabilities
Home-page: https://github.com/agronholm/apscheduler
Author: Alex Grönholm
Author-email: apscheduler@nextday.fi
License: MIT
Keywords: scheduling cron
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Provides-Extra: testing
Provides-Extra: rethinkdb
Provides-Extra: redis
Provides-Extra: tornado
Provides-Extra: sqlalchemy
Provides-Extra: mongodb
Provides-Extra: zookeeper
Provides-Extra: gevent
Provides-Extra: asyncio
Provides-Extra: twisted
Requires-Dist: setuptools (>=0.7)
Requires-Dist: six (>=1.4.0)
Requires-Dist: pytz
Requires-Dist: tzlocal (>=1.2)
Requires-Dist: futures; python_version == "2.7"
Requires-Dist: funcsigs; python_version == "2.7"
Provides-Extra: asyncio
Requires-Dist: trollius; (python_version == "2.7") and extra == 'asyncio'
Provides-Extra: gevent
Requires-Dist: gevent; extra == 'gevent'
Provides-Extra: mongodb
Requires-Dist: pymongo (>=2.8); extra == 'mongodb'
Provides-Extra: redis
Requires-Dist: redis; extra == 'redis'
Provides-Extra: rethinkdb
Requires-Dist: rethinkdb; extra == 'rethinkdb'
Provides-Extra: sqlalchemy
Requires-Dist: sqlalchemy (>=0.8); extra == 'sqlalchemy'
Provides-Extra: testing
Requires-Dist: pytest (<3.7); extra == 'testing'
Requires-Dist: pytest-cov; extra == 'testing'
Requires-Dist: pytest-tornado5; extra == 'testing'
Provides-Extra: testing
Requires-Dist: pytest-asyncio (<0.6.0); (python_version != "2.7") and extra == 'testing'
Provides-Extra: testing
Requires-Dist: mock; (python_version == "2.7") and extra == 'testing'
Provides-Extra: tornado
Requires-Dist: tornado (>=4.3); extra == 'tornado'
Provides-Extra: twisted
Requires-Dist: twisted; extra == 'twisted'
Provides-Extra: zookeeper
Requires-Dist: kazoo; extra == 'zookeeper'
.. image:: https://travis-ci.org/agronholm/apscheduler.svg?branch=master
:target: https://travis-ci.org/agronholm/apscheduler
:alt: Build Status
.. image:: https://coveralls.io/repos/github/agronholm/apscheduler/badge.svg?branch=master
:target: https://coveralls.io/github/agronholm/apscheduler?branch=master
:alt: Code Coverage
Advanced Python Scheduler (APScheduler) is a Python library that lets you schedule your Python code
to be executed later, either just once or periodically. You can add new jobs or remove old ones on
the fly as you please. If you store your jobs in a database, they will also survive scheduler
restarts and maintain their state. When the scheduler is restarted, it will then run all the jobs
it should have run while it was offline [#f1]_.
Among other things, APScheduler can be used as a cross-platform, application specific replacement
to platform specific schedulers, such as the cron daemon or the Windows task scheduler. Please
note, however, that APScheduler is **not** a daemon or service itself, nor does it come with any
command line tools. It is primarily meant to be run inside existing applications. That said,
APScheduler does provide some building blocks for you to build a scheduler service or to run a
dedicated scheduler process.
APScheduler has three built-in scheduling systems you can use:
* Cron-style scheduling (with optional start/end times)
* Interval-based execution (runs jobs on even intervals, with optional start/end times)
* One-off delayed execution (runs jobs once, on a set date/time)
You can mix and match scheduling systems and the backends where the jobs are stored any way you
like. Supported backends for storing jobs include:
* Memory
* `SQLAlchemy <http://www.sqlalchemy.org/>`_ (any RDBMS supported by SQLAlchemy works)
* `MongoDB <http://www.mongodb.org/>`_
* `Redis <http://redis.io/>`_
* `RethinkDB <https://www.rethinkdb.com/>`_
* `ZooKeeper <https://zookeeper.apache.org/>`_
APScheduler also integrates with several common Python frameworks, like:
* `asyncio <http://docs.python.org/3.4/library/asyncio.html>`_ (:pep:`3156`)
* `gevent <http://www.gevent.org/>`_
* `Tornado <http://www.tornadoweb.org/>`_
* `Twisted <http://twistedmatrix.com/>`_
* `Qt <http://qt-project.org/>`_ (using either
`PyQt <http://www.riverbankcomputing.com/software/pyqt/intro>`_ or
`PySide <http://qt-project.org/wiki/PySide>`_)
.. [#f1] The cutoff period for this is also configurable.
Documentation
-------------
Documentation can be found `here <http://readthedocs.org/docs/apscheduler/en/latest/>`_.
Source
------
The source can be browsed at `Github <https://github.com/agronholm/apscheduler>`_.
Reporting bugs
--------------
A `bug tracker <https://github.com/agronholm/apscheduler/issues>`_ is provided by Github.
Getting help
------------
If you have problems or other questions, you can either:
* Ask in the `apscheduler <https://gitter.im/apscheduler/Lobby>`_ room on Gitter
* Ask on the `APScheduler Google group <http://groups.google.com/group/apscheduler>`_, or
* Ask on `StackOverflow <http://stackoverflow.com/questions/tagged/apscheduler>`_ and tag your
question with the ``apscheduler`` tag

View File

@ -0,0 +1,82 @@
APScheduler-3.5.3.dist-info/METADATA,sha256=oh5F2iHBxfcQx9t9JCzOCz8lwQHtyflRhrh3wMpsi9Y,5426
APScheduler-3.5.3.dist-info/RECORD,,
APScheduler-3.5.3.dist-info/WHEEL,sha256=gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk,110
APScheduler-3.5.3.dist-info/entry_points.txt,sha256=7RgkYN_OYyCUQtIGhj-UNcelnIjsNm7nC9rogdMQh3U,1148
APScheduler-3.5.3.dist-info/top_level.txt,sha256=O3oMCWxG-AHkecUoO6Ze7-yYjWrttL95uHO8-RFdYvE,12
apscheduler/__init__.py,sha256=qFEK2ysRBcLiYmm3deyJJ1avUOugaM_nCGHMD42WMBw,380
apscheduler/events.py,sha256=CQsWk1KgCdpEfXm6H8s08HWExJ_getgfoCe2BGUQRKo,3571
apscheduler/job.py,sha256=S8Q-uVtG1PKsZpdYdraN2xb7K12vdJnV2oi2PIZp7fU,11008
apscheduler/util.py,sha256=cXkSLpJAI5mB6Tyr5-WRjHjuQDKCYjRnLZ94f2VUGGg,13113
apscheduler/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
apscheduler/executors/asyncio.py,sha256=HR8SmX-IJnN2FprdtdX65glYhRXbWurzkocKLyQt2xQ,2111
apscheduler/executors/base.py,sha256=hogiMc_t-huw6BMod0HEeY2FhRNmAAUyNNuBHvIX31M,5336
apscheduler/executors/base_py3.py,sha256=s_4siAjBHrr7JZnm64VVow9zyvs2JBc-VRPkPuDeBTI,1775
apscheduler/executors/debug.py,sha256=15_ogSBzl8RRCfBYDnkIV2uMH8cLk1KImYmBa_NVGpc,573
apscheduler/executors/gevent.py,sha256=aulrNmoefyBgrOkH9awRhFiXIDnSCnZ4U0o0_JXIXgc,777
apscheduler/executors/pool.py,sha256=q9TC6KzwWI9tpLNxQhdrKRWFtsN5dmx_Vegu23BV-Sk,1672
apscheduler/executors/tornado.py,sha256=BlWrZ0MUoHBloIup7YgXGzxN5DWFI6V7s23jpx42Kmk,1747
apscheduler/executors/twisted.py,sha256=bRoU0C4BoVcS6_BjKD5wfUs0IJpGkmLsRAcMH2rJJss,778
apscheduler/jobstores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
apscheduler/jobstores/base.py,sha256=DXzSW9XscueHZHMvy1qFiG-vYqUl_MMv0n0uBSZWXGo,4523
apscheduler/jobstores/memory.py,sha256=ZxWiKsqfsCHFvac-6X9BztuhnuSxlOYi1dhT6g-pjQo,3655
apscheduler/jobstores/mongodb.py,sha256=e9KNzPFrjiVpiM3iPT_c0ONxZQT70VCF2rDXW0-22zk,5296
apscheduler/jobstores/redis.py,sha256=ImiEL7aTMEUy8zNZpbtZEpshFA_JabP6Q7iwzKlWxgs,5437
apscheduler/jobstores/rethinkdb.py,sha256=1X8PKyB3MQJlbhzwWmNO6POvsOJXjrPenMF6BT3e_MA,5683
apscheduler/jobstores/sqlalchemy.py,sha256=Bz2DfcyqWPY-OTiT6Td22lPdGZeu6NnFORTwC4H1jLg,6118
apscheduler/jobstores/zookeeper.py,sha256=BzyqZ08XIDcbu5frQWGmDVEHAEScNxjt8oML6Tty8j8,6406
apscheduler/schedulers/__init__.py,sha256=jM63xA_K7GSToBenhsz-SCcqfhk1pdEVb6ajwoO5Kqg,406
apscheduler/schedulers/asyncio.py,sha256=0j0mcDpf-zI_vQHcUCZZtBfEEZEiocEOZ767efIZ5YM,2082
apscheduler/schedulers/background.py,sha256=dGX0T0z6T6HzZHG7njWgp90SFHpetZ4ZBUV2gGOSqoc,1505
apscheduler/schedulers/base.py,sha256=Oya7QHCEdya7AvtyhU-b9kn9-WOJcbvbAuIyQj6zU_w,42858
apscheduler/schedulers/blocking.py,sha256=c-5YR-dKn3D82tPt38t50KGPJrAiC852v8ai2Vwanmg,924
apscheduler/schedulers/gevent.py,sha256=csPBvV75FGcboXXsdex6fCD7J54QgBddYNdWj62ZO9g,1031
apscheduler/schedulers/qt.py,sha256=9fFJEr3zbJXuki1vTEdFG3IEg1TCzze1vhnlAeLP0Uo,1254
apscheduler/schedulers/tornado.py,sha256=D9Vaq3Ee9EFiXa1jDy9tedI048gR_YT_LAFUWqO_uEw,1926
apscheduler/schedulers/twisted.py,sha256=D5EBjjMRtMBxy0_aAURcULAI8Ky2IvCTr9tK9sO1rYk,1844
apscheduler/triggers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
apscheduler/triggers/base.py,sha256=WMo5f2g14fjO5VzpIxFQtk47Z9VEUDDPSxjoPL9FGSQ,1837
apscheduler/triggers/combining.py,sha256=WTEnaEkBHysF1009sCvBaQa99hiy9l5Oz-hHyjy3jv8,3473
apscheduler/triggers/date.py,sha256=RrfB1PNO9G9e91p1BOf-y_TseVHQQR-KJPhNdPpAHcU,1705
apscheduler/triggers/interval.py,sha256=LiIunGOd96yaiAceG1XGP8eY3JxSyHDWCipVhQWMzDU,4381
apscheduler/triggers/cron/__init__.py,sha256=a8ASzvM7ci-djOI2jIL2XErL6zEx4Wr1012aD1XJw_w,9246
apscheduler/triggers/cron/expressions.py,sha256=hu1kq0mKvivIw7U0D0Nnrbuk3q01dCuhZ7SHRPw6qhI,9184
apscheduler/triggers/cron/fields.py,sha256=NWPClh1NgSOpTlJ3sm1TXM_ViC2qJGKWkd_vg0xsw7o,3510
APScheduler-3.5.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
apscheduler/executors/__pycache__/asyncio.cpython-36.pyc,,
apscheduler/executors/__pycache__/base.cpython-36.pyc,,
apscheduler/executors/__pycache__/base_py3.cpython-36.pyc,,
apscheduler/executors/__pycache__/debug.cpython-36.pyc,,
apscheduler/executors/__pycache__/gevent.cpython-36.pyc,,
apscheduler/executors/__pycache__/pool.cpython-36.pyc,,
apscheduler/executors/__pycache__/tornado.cpython-36.pyc,,
apscheduler/executors/__pycache__/twisted.cpython-36.pyc,,
apscheduler/executors/__pycache__/__init__.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/base.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/memory.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/mongodb.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/redis.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/rethinkdb.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/sqlalchemy.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/zookeeper.cpython-36.pyc,,
apscheduler/jobstores/__pycache__/__init__.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/asyncio.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/background.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/base.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/blocking.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/gevent.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/qt.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/tornado.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/twisted.cpython-36.pyc,,
apscheduler/schedulers/__pycache__/__init__.cpython-36.pyc,,
apscheduler/triggers/cron/__pycache__/expressions.cpython-36.pyc,,
apscheduler/triggers/cron/__pycache__/fields.cpython-36.pyc,,
apscheduler/triggers/cron/__pycache__/__init__.cpython-36.pyc,,
apscheduler/triggers/__pycache__/base.cpython-36.pyc,,
apscheduler/triggers/__pycache__/combining.cpython-36.pyc,,
apscheduler/triggers/__pycache__/date.cpython-36.pyc,,
apscheduler/triggers/__pycache__/interval.cpython-36.pyc,,
apscheduler/triggers/__pycache__/__init__.cpython-36.pyc,,
apscheduler/__pycache__/events.cpython-36.pyc,,
apscheduler/__pycache__/job.cpython-36.pyc,,
apscheduler/__pycache__/util.cpython-36.pyc,,
apscheduler/__pycache__/__init__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.1)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1,24 @@
[apscheduler.executors]
asyncio = apscheduler.executors.asyncio:AsyncIOExecutor [asyncio]
debug = apscheduler.executors.debug:DebugExecutor
gevent = apscheduler.executors.gevent:GeventExecutor [gevent]
processpool = apscheduler.executors.pool:ProcessPoolExecutor
threadpool = apscheduler.executors.pool:ThreadPoolExecutor
tornado = apscheduler.executors.tornado:TornadoExecutor [tornado]
twisted = apscheduler.executors.twisted:TwistedExecutor [twisted]
[apscheduler.jobstores]
memory = apscheduler.jobstores.memory:MemoryJobStore
mongodb = apscheduler.jobstores.mongodb:MongoDBJobStore [mongodb]
redis = apscheduler.jobstores.redis:RedisJobStore [redis]
rethinkdb = apscheduler.jobstores.rethinkdb:RethinkDBJobStore [rethinkdb]
sqlalchemy = apscheduler.jobstores.sqlalchemy:SQLAlchemyJobStore [sqlalchemy]
zookeeper = apscheduler.jobstores.zookeeper:ZooKeeperJobStore [zookeeper]
[apscheduler.triggers]
and = apscheduler.triggers.combining:AndTrigger
cron = apscheduler.triggers.cron:CronTrigger
date = apscheduler.triggers.date:DateTrigger
interval = apscheduler.triggers.interval:IntervalTrigger
or = apscheduler.triggers.combining:OrTrigger

View File

@ -0,0 +1 @@
apscheduler

View File

@ -0,0 +1,31 @@
Copyright © 2010 by the Pallets team.
Some rights reserved.
Redistribution and use in source and binary forms of the software as
well as documentation, 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 copyright holder 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 AND DOCUMENTATION 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 HOLDER 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
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -0,0 +1,130 @@
Metadata-Version: 2.1
Name: Flask
Version: 1.0.2
Summary: A simple framework for building complex web applications.
Home-page: https://www.palletsprojects.com/p/flask/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets team
Maintainer-email: contact@palletsprojects.com
License: BSD
Project-URL: Documentation, http://flask.pocoo.org/docs/
Project-URL: Code, https://github.com/pallets/flask
Project-URL: Issue tracker, https://github.com/pallets/flask/issues
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: dev
Provides-Extra: docs
Provides-Extra: dotenv
Requires-Dist: Werkzeug (>=0.14)
Requires-Dist: Jinja2 (>=2.10)
Requires-Dist: itsdangerous (>=0.24)
Requires-Dist: click (>=5.1)
Provides-Extra: dev
Requires-Dist: pytest (>=3); extra == 'dev'
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: tox; extra == 'dev'
Requires-Dist: sphinx; extra == 'dev'
Requires-Dist: pallets-sphinx-themes; extra == 'dev'
Requires-Dist: sphinxcontrib-log-cabinet; extra == 'dev'
Provides-Extra: docs
Requires-Dist: sphinx; extra == 'docs'
Requires-Dist: pallets-sphinx-themes; extra == 'docs'
Requires-Dist: sphinxcontrib-log-cabinet; extra == 'docs'
Provides-Extra: dotenv
Requires-Dist: python-dotenv; extra == 'dotenv'
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Flask
A Simple Example
----------------
.. code-block:: python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
.. code-block:: text
$ FLASK_APP=hello.py flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://psfmember.org/civicrm/contribute/transact?reset=1&id=20
Links
-----
* Website: https://www.palletsprojects.com/p/flask/
* Documentation: http://flask.pocoo.org/docs/
* License: `BSD <https://github.com/pallets/flask/blob/master/LICENSE>`_
* Releases: https://pypi.org/project/Flask/
* Code: https://github.com/pallets/flask
* Issue tracker: https://github.com/pallets/flask/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/flask
* Windows: https://ci.appveyor.com/project/pallets/flask
* Test coverage: https://codecov.io/gh/pallets/flask
.. _WSGI: https://wsgi.readthedocs.io
.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
.. _Jinja: https://www.palletsprojects.com/p/jinja/
.. _pip: https://pip.pypa.io/en/stable/quickstart/

View File

@ -0,0 +1,48 @@
Flask-1.0.2.dist-info/LICENSE.txt,sha256=ziEXA3AIuaiUn1qe4cd1XxCESWTYrk4TjN7Qb06J3l8,1575
Flask-1.0.2.dist-info/METADATA,sha256=iA5tiNWzTtgCVe80aTZGNWsckj853fJyfvHs9U-WZRk,4182
Flask-1.0.2.dist-info/RECORD,,
Flask-1.0.2.dist-info/WHEEL,sha256=J3CsTk7Mf2JNUyhImI-mjX-fmI4oDjyiXgWT4qgZiCE,110
Flask-1.0.2.dist-info/entry_points.txt,sha256=gBLA1aKg0OYR8AhbAfg8lnburHtKcgJLDU52BBctN0k,42
Flask-1.0.2.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
flask/__init__.py,sha256=qq8lK6QQbxJALf1igz7qsvUwOTAoKvFGfdLm7jPNsso,1673
flask/__main__.py,sha256=pgIXrHhxM5MAMvgzAqWpw_t6AXZ1zG38us4JRgJKtxk,291
flask/_compat.py,sha256=UDFGhosh6mOdNB-4evKPuneHum1OpcAlwTNJCRm0irQ,2892
flask/app.py,sha256=ahpe3T8w98rQd_Er5d7uDxK57S1nnqGQx3V3hirBovU,94147
flask/blueprints.py,sha256=Cyhl_x99tgwqEZPtNDJUFneAfVJxWfEU4bQA7zWS6VU,18331
flask/cli.py,sha256=30QYAO10Do9LbZYCLgfI_xhKjASdLopL8wKKVUGS2oA,29442
flask/config.py,sha256=kznUhj4DLYxsTF_4kfDG8GEHto1oZG_kqblyrLFtpqQ,9951
flask/ctx.py,sha256=leFzS9fzmo0uaLCdxpHc5_iiJZ1H0X_Ig4yPCOvT--g,16224
flask/debughelpers.py,sha256=1ceC-UyqZTd4KsJkf0OObHPsVt5R3T6vnmYhiWBjV-w,6479
flask/globals.py,sha256=pGg72QW_-4xUfsI33I5L_y76c21AeqfSqXDcbd8wvXU,1649
flask/helpers.py,sha256=YCl8D1plTO1evEYP4KIgaY3H8Izww5j4EdgRJ89oHTw,40106
flask/logging.py,sha256=qV9h0vt7NIRkKM9OHDWndzO61E5CeBMlqPJyTt-W2Wc,2231
flask/sessions.py,sha256=2XHV4ASREhSEZ8bsPQW6pNVNuFtbR-04BzfKg0AfvHo,14452
flask/signals.py,sha256=BGQbVyCYXnzKK2DVCzppKFyWN1qmrtW1QMAYUs-1Nr8,2211
flask/templating.py,sha256=FDfWMbpgpC3qObW8GGXRAVrkHFF8K4CHOJymB1wvULI,4914
flask/testing.py,sha256=XD3gWNvLUV8dqVHwKd9tZzsj81fSHtjOphQ1wTNtlMs,9379
flask/views.py,sha256=Wy-_WkUVtCfE2zCXYeJehNgHuEtviE4v3HYfJ--MpbY,5733
flask/wrappers.py,sha256=1Z9hF5-hXQajn_58XITQFRY8efv3Vy3uZ0avBfZu6XI,7511
flask/json/__init__.py,sha256=Ns1Hj805XIxuBMh2z0dYnMVfb_KUgLzDmP3WoUYaPhw,10729
flask/json/tag.py,sha256=9ehzrmt5k7hxf7ZEK0NOs3swvQyU9fWNe-pnYe69N60,8223
../../Scripts/flask.exe,sha256=ljrIXQzU8f6egd4aaMAsVCEY1T9OUzAT7Epn4Xl52NA,102766
Flask-1.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
flask/json/__pycache__/tag.cpython-36.pyc,,
flask/json/__pycache__/__init__.cpython-36.pyc,,
flask/__pycache__/app.cpython-36.pyc,,
flask/__pycache__/blueprints.cpython-36.pyc,,
flask/__pycache__/cli.cpython-36.pyc,,
flask/__pycache__/config.cpython-36.pyc,,
flask/__pycache__/ctx.cpython-36.pyc,,
flask/__pycache__/debughelpers.cpython-36.pyc,,
flask/__pycache__/globals.cpython-36.pyc,,
flask/__pycache__/helpers.cpython-36.pyc,,
flask/__pycache__/logging.cpython-36.pyc,,
flask/__pycache__/sessions.cpython-36.pyc,,
flask/__pycache__/signals.cpython-36.pyc,,
flask/__pycache__/templating.cpython-36.pyc,,
flask/__pycache__/testing.cpython-36.pyc,,
flask/__pycache__/views.cpython-36.pyc,,
flask/__pycache__/wrappers.cpython-36.pyc,,
flask/__pycache__/_compat.cpython-36.pyc,,
flask/__pycache__/__init__.cpython-36.pyc,,
flask/__pycache__/__main__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
flask = flask.cli:main

View File

@ -0,0 +1,33 @@
Metadata-Version: 1.1
Name: Flask-JWT
Version: 0.3.2
Summary: JWT token authentication for Flask apps
Home-page: https://github.com/mattupstate/flask-jwt
Author: Matt Wright
Author-email: matt@nobien.net
License: MIT
Description:
Flask-JWT
=========
Flask-JWT is a Flask extension that adds basic Json Web Token features to any application.
Resources
---------
* `Documentation <http://packages.python.org/Flask-JWT/>`_
* `Issue Tracker <https://github.com/mattupstate/flask-jwt/issues>`_
* `Source <https://github.com/mattupstate/flask-jwt>`_
* `Development Version
<https://github.com/mattupstate/flask-jwt/raw/develop#egg=Flask-JWT-dev>`_
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules

View File

@ -0,0 +1,19 @@
CHANGES
LICENSE
MANIFEST.in
README.rst
requirements-dev.txt
requirements.txt
setup.cfg
setup.py
tox.ini
Flask_JWT.egg-info/PKG-INFO
Flask_JWT.egg-info/SOURCES.txt
Flask_JWT.egg-info/dependency_links.txt
Flask_JWT.egg-info/not-zip-safe
Flask_JWT.egg-info/requires.txt
Flask_JWT.egg-info/top_level.txt
flask_jwt/__init__.py
tests/conftest.py
tests/conftest.pyc
tests/test_jwt.py

View File

@ -0,0 +1,8 @@
..\flask_jwt\__init__.py
..\flask_jwt\__pycache__\__init__.cpython-36.pyc
PKG-INFO
SOURCES.txt
dependency_links.txt
not-zip-safe
requires.txt
top_level.txt

View File

@ -0,0 +1,2 @@
Flask>=0.9
PyJWT<1.5.0,>=1.4.0

View File

@ -0,0 +1,25 @@
Metadata-Version: 2.1
Name: Flask-JWT-Extended
Version: 3.8.1
Summary: Extended JWT integration with Flask
Home-page: https://github.com/vimalloc/flask-jwt-extended
Author: Landon Gilbert-Bland
Author-email: landogbland@gmail.com
License: MIT
Description: Extended JWT integration with Flask
Keywords: flask,jwt,json web token
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: asymmetric_crypto

View File

@ -0,0 +1,20 @@
LICENSE
MANIFEST.in
README.md
requirements.txt
setup.cfg
setup.py
Flask_JWT_Extended.egg-info/PKG-INFO
Flask_JWT_Extended.egg-info/SOURCES.txt
Flask_JWT_Extended.egg-info/dependency_links.txt
Flask_JWT_Extended.egg-info/not-zip-safe
Flask_JWT_Extended.egg-info/requires.txt
Flask_JWT_Extended.egg-info/top_level.txt
flask_jwt_extended/__init__.py
flask_jwt_extended/config.py
flask_jwt_extended/default_callbacks.py
flask_jwt_extended/exceptions.py
flask_jwt_extended/jwt_manager.py
flask_jwt_extended/tokens.py
flask_jwt_extended/utils.py
flask_jwt_extended/view_decorators.py

View File

@ -0,0 +1,22 @@
..\flask_jwt_extended\__init__.py
..\flask_jwt_extended\__pycache__\__init__.cpython-36.pyc
..\flask_jwt_extended\__pycache__\config.cpython-36.pyc
..\flask_jwt_extended\__pycache__\default_callbacks.cpython-36.pyc
..\flask_jwt_extended\__pycache__\exceptions.cpython-36.pyc
..\flask_jwt_extended\__pycache__\jwt_manager.cpython-36.pyc
..\flask_jwt_extended\__pycache__\tokens.cpython-36.pyc
..\flask_jwt_extended\__pycache__\utils.cpython-36.pyc
..\flask_jwt_extended\__pycache__\view_decorators.cpython-36.pyc
..\flask_jwt_extended\config.py
..\flask_jwt_extended\default_callbacks.py
..\flask_jwt_extended\exceptions.py
..\flask_jwt_extended\jwt_manager.py
..\flask_jwt_extended\tokens.py
..\flask_jwt_extended\utils.py
..\flask_jwt_extended\view_decorators.py
PKG-INFO
SOURCES.txt
dependency_links.txt
not-zip-safe
requires.txt
top_level.txt

View File

@ -0,0 +1,6 @@
Werkzeug>=0.14
Flask
PyJWT
[asymmetric_crypto]
cryptography

View File

@ -0,0 +1,31 @@
Metadata-Version: 2.0
Name: Flask-RESTful
Version: 0.3.6
Summary: Simple framework for creating REST APIs
Home-page: https://www.github.com/flask-restful/flask-restful/
Author: Twilio API Team
Author-email: help@twilio.com
License: BSD
Platform: any
Classifier: Framework :: Flask
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: License :: OSI Approved :: BSD License
Requires-Dist: Flask (>=0.8)
Requires-Dist: aniso8601 (>=0.82)
Requires-Dist: pytz
Requires-Dist: six (>=1.3.0)
Provides-Extra: docs
Requires-Dist: sphinx; extra == 'docs'
Provides-Extra: paging
Requires-Dist: pycrypto (>=2.6); extra == 'paging'
UNKNOWN

View File

@ -0,0 +1,29 @@
Flask_RESTful-0.3.6.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
Flask_RESTful-0.3.6.dist-info/METADATA,sha256=THyvrIu4SUuC9fqpfDBP-m6kdZcesFu2-Gm2oxgJnWM,985
Flask_RESTful-0.3.6.dist-info/RECORD,,
Flask_RESTful-0.3.6.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
Flask_RESTful-0.3.6.dist-info/metadata.json,sha256=1zKAyvYdpplkpX_NJU8vKCZ9nvy0Y_paxRiMJXCNiXU,1178
Flask_RESTful-0.3.6.dist-info/top_level.txt,sha256=lNpWPlejgBAtMhCUwz_FTyJH12ul1mBZ-Uv3ZK1HiGg,14
flask_restful/__init__.py,sha256=dpZuahv5GMo2Rof4OWkGpQvIhnJIeOakL_1EHkkzUA4,28510
flask_restful/__version__.py,sha256=-RHjzBqfdsudCsVzQ9u-AoX7n-_90gcO1RlvPEVk7d4,45
flask_restful/fields.py,sha256=9cbc0vXaGzt1Ur8gUf3sHlMjNSy1qOSV_qvi-3YQAL8,13051
flask_restful/inputs.py,sha256=AOBF_1BpB8snY3XJT_vca_uWYzkCP2nla01lFU7xqLE,9114
flask_restful/paging.py,sha256=H_nL-UlViLSHJhS7NbHJurHMwzxv7IsaAnuTC4L5Kec,1207
flask_restful/reqparse.py,sha256=I9qxtSqjVzrDTxQtfSHM4yGx3XsiVoa-wMPlA1mod9s,13475
flask_restful/representations/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
flask_restful/representations/json.py,sha256=swKwnbt7v2ioHfHkqhqbzIu_yrcP0ComlSl49IGFJOo,873
flask_restful/utils/__init__.py,sha256=Qh5pyCIT2dfHmrUdS6lsMbBLjZmAhz1fl7vWyJ_n4XQ,719
flask_restful/utils/cors.py,sha256=cZiqaHhIn0w66spRoSIdC-jIn4X_b6OlVms5eGF4Ess,2084
flask_restful/utils/crypto.py,sha256=q3PBvAYMJYybbqqQlKNF_Pqeyo9h3x5jFJuVqtEA5bA,996
Flask_RESTful-0.3.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
flask_restful/representations/__pycache__/json.cpython-36.pyc,,
flask_restful/representations/__pycache__/__init__.cpython-36.pyc,,
flask_restful/utils/__pycache__/cors.cpython-36.pyc,,
flask_restful/utils/__pycache__/crypto.cpython-36.pyc,,
flask_restful/utils/__pycache__/__init__.cpython-36.pyc,,
flask_restful/__pycache__/fields.cpython-36.pyc,,
flask_restful/__pycache__/inputs.cpython-36.pyc,,
flask_restful/__pycache__/paging.cpython-36.pyc,,
flask_restful/__pycache__/reqparse.cpython-36.pyc,,
flask_restful/__pycache__/__init__.cpython-36.pyc,,
flask_restful/__pycache__/__version__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1 @@
{"classifiers": ["Framework :: Flask", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "License :: OSI Approved :: BSD License"], "extensions": {"python.details": {"contacts": [{"email": "help@twilio.com", "name": "Twilio API Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://www.github.com/flask-restful/flask-restful/"}}}, "extras": ["docs", "paging"], "generator": "bdist_wheel (0.29.0)", "license": "BSD", "metadata_version": "2.0", "name": "Flask-RESTful", "platform": "any", "run_requires": [{"requires": ["Flask (>=0.8)", "aniso8601 (>=0.82)", "pytz", "six (>=1.3.0)"]}, {"extra": "paging", "requires": ["pycrypto (>=2.6)"]}, {"extra": "docs", "requires": ["sphinx"]}], "summary": "Simple framework for creating REST APIs", "test_requires": [{"requires": ["Flask-RESTful[paging]", "blinker", "mock (>=0.8)"]}], "version": "0.3.6"}

View File

@ -0,0 +1 @@
flask_restful

View File

@ -0,0 +1,37 @@
Jinja2
~~~~~~
Jinja2 is a template engine written in pure Python. It provides a
`Django`_ inspired non-XML syntax but supports inline expressions and
an optional `sandboxed`_ environment.
Nutshell
--------
Here a small example of a Jinja template::
{% extends 'base.html' %}
{% block title %}Memberlist{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Philosophy
----------
Application logic is for the controller but don't try to make the life
for the template designer too hard by giving him too few functionality.
For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
.. _sandboxed: https://en.wikipedia.org/wiki/Sandbox_(computer_security)
.. _Django: https://www.djangoproject.com/
.. _Jinja2 webpage: http://jinja.pocoo.org/
.. _documentation: http://jinja.pocoo.org/2/documentation/

View File

@ -0,0 +1,31 @@
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
Some 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.
* The names of the contributors may not 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
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,68 @@
Metadata-Version: 2.0
Name: Jinja2
Version: 2.10
Summary: A small but fast and easy to use stand-alone template engine written in pure python.
Home-page: http://jinja.pocoo.org/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD
Description-Content-Type: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Dist: MarkupSafe (>=0.23)
Provides-Extra: i18n
Requires-Dist: Babel (>=0.8); extra == 'i18n'
Jinja2
~~~~~~
Jinja2 is a template engine written in pure Python. It provides a
`Django`_ inspired non-XML syntax but supports inline expressions and
an optional `sandboxed`_ environment.
Nutshell
--------
Here a small example of a Jinja template::
{% extends 'base.html' %}
{% block title %}Memberlist{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Philosophy
----------
Application logic is for the controller but don't try to make the life
for the template designer too hard by giving him too few functionality.
For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
.. _sandboxed: https://en.wikipedia.org/wiki/Sandbox_(computer_security)
.. _Django: https://www.djangoproject.com/
.. _Jinja2 webpage: http://jinja.pocoo.org/
.. _documentation: http://jinja.pocoo.org/2/documentation/

View File

@ -0,0 +1,63 @@
Jinja2-2.10.dist-info/DESCRIPTION.rst,sha256=b5ckFDoM7vVtz_mAsJD4OPteFKCqE7beu353g4COoYI,978
Jinja2-2.10.dist-info/LICENSE.txt,sha256=JvzUNv3Io51EiWrAPm8d_SXjhJnEjyDYvB3Tvwqqils,1554
Jinja2-2.10.dist-info/METADATA,sha256=18EgU8zR6-av-0-5y_gXebzK4GnBB_76lALUsl-6QHM,2258
Jinja2-2.10.dist-info/RECORD,,
Jinja2-2.10.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
Jinja2-2.10.dist-info/entry_points.txt,sha256=NdzVcOrqyNyKDxD09aERj__3bFx2paZhizFDsKmVhiA,72
Jinja2-2.10.dist-info/metadata.json,sha256=NPUJ9TMBxVQAv_kTJzvU8HwmP-4XZvbK9mz6_4YUVl4,1473
Jinja2-2.10.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
jinja2/__init__.py,sha256=xJHjaMoy51_KXn1wf0cysH6tUUifUxZCwSOfcJGEYZw,2614
jinja2/_compat.py,sha256=xP60CE5Qr8FTYcDE1f54tbZLKGvMwYml4-8T7Q4KG9k,2596
jinja2/_identifier.py,sha256=W1QBSY-iJsyt6oR_nKSuNNCzV95vLIOYgUNPUI1d5gU,1726
jinja2/asyncfilters.py,sha256=cTDPvrS8Hp_IkwsZ1m9af_lr5nHysw7uTa5gV0NmZVE,4144
jinja2/asyncsupport.py,sha256=UErQ3YlTLaSjFb94P4MVn08-aVD9jJxty2JVfMRb-1M,7878
jinja2/bccache.py,sha256=nQldx0ZRYANMyfvOihRoYFKSlUdd5vJkS7BjxNwlOZM,12794
jinja2/compiler.py,sha256=BqC5U6JxObSRhblyT_a6Tp5GtEU5z3US1a4jLQaxxgo,65386
jinja2/constants.py,sha256=uwwV8ZUhHhacAuz5PTwckfsbqBaqM7aKfyJL7kGX5YQ,1626
jinja2/debug.py,sha256=WTVeUFGUa4v6ReCsYv-iVPa3pkNB75OinJt3PfxNdXs,12045
jinja2/defaults.py,sha256=Em-95hmsJxIenDCZFB1YSvf9CNhe9rBmytN3yUrBcWA,1400
jinja2/environment.py,sha256=VnkAkqw8JbjZct4tAyHlpBrka2vqB-Z58RAP-32P1ZY,50849
jinja2/exceptions.py,sha256=_Rj-NVi98Q6AiEjYQOsP8dEIdu5AlmRHzcSNOPdWix4,4428
jinja2/ext.py,sha256=atMQydEC86tN1zUsdQiHw5L5cF62nDbqGue25Yiu3N4,24500
jinja2/filters.py,sha256=yOAJk0MsH-_gEC0i0U6NweVQhbtYaC-uE8xswHFLF4w,36528
jinja2/idtracking.py,sha256=2GbDSzIvGArEBGLkovLkqEfmYxmWsEf8c3QZwM4uNsw,9197
jinja2/lexer.py,sha256=ySEPoXd1g7wRjsuw23uimS6nkGN5aqrYwcOKxCaVMBQ,28559
jinja2/loaders.py,sha256=xiTuURKAEObyym0nU8PCIXu_Qp8fn0AJ5oIADUUm-5Q,17382
jinja2/meta.py,sha256=fmKHxkmZYAOm9QyWWy8EMd6eefAIh234rkBMW2X4ZR8,4340
jinja2/nativetypes.py,sha256=_sJhS8f-8Q0QMIC0dm1YEdLyxEyoO-kch8qOL5xUDfE,7308
jinja2/nodes.py,sha256=L10L_nQDfubLhO3XjpF9qz46FSh2clL-3e49ogVlMmA,30853
jinja2/optimizer.py,sha256=MsdlFACJ0FRdPtjmCAdt7JQ9SGrXFaDNUaslsWQaG3M,1722
jinja2/parser.py,sha256=lPzTEbcpTRBLw8ii6OYyExHeAhaZLMA05Hpv4ll3ULk,35875
jinja2/runtime.py,sha256=DHdD38Pq8gj7uWQC5usJyWFoNWL317A9AvXOW_CLB34,27755
jinja2/sandbox.py,sha256=TVyZHlNqqTzsv9fv2NvJNmSdWRHTguhyMHdxjWms32U,16708
jinja2/tests.py,sha256=iJQLwbapZr-EKquTG_fVOVdwHUUKf3SX9eNkjQDF8oU,4237
jinja2/utils.py,sha256=q24VupGZotQ-uOyrJxCaXtDWhZC1RgsQG7kcdmjck2Q,20629
jinja2/visitor.py,sha256=JD1H1cANA29JcntFfN5fPyqQxB4bI4wC00BzZa-XHks,3316
Jinja2-2.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
jinja2/__pycache__/asyncfilters.cpython-36.pyc,,
jinja2/__pycache__/asyncsupport.cpython-36.pyc,,
jinja2/__pycache__/bccache.cpython-36.pyc,,
jinja2/__pycache__/compiler.cpython-36.pyc,,
jinja2/__pycache__/constants.cpython-36.pyc,,
jinja2/__pycache__/debug.cpython-36.pyc,,
jinja2/__pycache__/defaults.cpython-36.pyc,,
jinja2/__pycache__/environment.cpython-36.pyc,,
jinja2/__pycache__/exceptions.cpython-36.pyc,,
jinja2/__pycache__/ext.cpython-36.pyc,,
jinja2/__pycache__/filters.cpython-36.pyc,,
jinja2/__pycache__/idtracking.cpython-36.pyc,,
jinja2/__pycache__/lexer.cpython-36.pyc,,
jinja2/__pycache__/loaders.cpython-36.pyc,,
jinja2/__pycache__/meta.cpython-36.pyc,,
jinja2/__pycache__/nativetypes.cpython-36.pyc,,
jinja2/__pycache__/nodes.cpython-36.pyc,,
jinja2/__pycache__/optimizer.cpython-36.pyc,,
jinja2/__pycache__/parser.cpython-36.pyc,,
jinja2/__pycache__/runtime.cpython-36.pyc,,
jinja2/__pycache__/sandbox.cpython-36.pyc,,
jinja2/__pycache__/tests.cpython-36.pyc,,
jinja2/__pycache__/utils.cpython-36.pyc,,
jinja2/__pycache__/visitor.cpython-36.pyc,,
jinja2/__pycache__/_compat.cpython-36.pyc,,
jinja2/__pycache__/_identifier.cpython-36.pyc,,
jinja2/__pycache__/__init__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1,4 @@
[babel.extractors]
jinja2 = jinja2.ext:babel_extract[i18n]

View File

@ -0,0 +1 @@
{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: HTML"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "armin.ronacher@active-4.com", "name": "Armin Ronacher", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "http://jinja.pocoo.org/"}}, "python.exports": {"babel.extractors": {"jinja2": "jinja2.ext:babel_extract [i18n]"}}}, "extras": ["i18n"], "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "Jinja2", "run_requires": [{"extra": "i18n", "requires": ["Babel (>=0.8)"]}, {"requires": ["MarkupSafe (>=0.23)"]}], "summary": "A small but fast and easy to use stand-alone template engine written in pure python.", "version": "2.10"}

View File

@ -0,0 +1,133 @@
Metadata-Version: 1.1
Name: MarkupSafe
Version: 1.0
Summary: Implements a XML/HTML/XHTML Markup safe string for Python
Home-page: http://github.com/pallets/markupsafe
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD
Description: MarkupSafe
==========
Implements a unicode subclass that supports HTML strings:
.. code-block:: python
>>> from markupsafe import Markup, escape
>>> escape("<script>alert(document.cookie);</script>")
Markup(u'&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
>>> tmpl = Markup("<em>%s</em>")
>>> tmpl % "Peter > Lustig"
Markup(u'<em>Peter &gt; Lustig</em>')
If you want to make an object unicode that is not yet unicode
but don't want to lose the taint information, you can use the
``soft_unicode`` function. (On Python 3 you can also use ``soft_str`` which
is a different name for the same function).
.. code-block:: python
>>> from markupsafe import soft_unicode
>>> soft_unicode(42)
u'42'
>>> soft_unicode(Markup('foo'))
Markup(u'foo')
HTML Representations
--------------------
Objects can customize their HTML markup equivalent by overriding
the ``__html__`` function:
.. code-block:: python
>>> class Foo(object):
... def __html__(self):
... return '<strong>Nice</strong>'
...
>>> escape(Foo())
Markup(u'<strong>Nice</strong>')
>>> Markup(Foo())
Markup(u'<strong>Nice</strong>')
Silent Escapes
--------------
Since MarkupSafe 0.10 there is now also a separate escape function
called ``escape_silent`` that returns an empty string for ``None`` for
consistency with other systems that return empty strings for ``None``
when escaping (for instance Pylons' webhelpers).
If you also want to use this for the escape method of the Markup
object, you can create your own subclass that does that:
.. code-block:: python
from markupsafe import Markup, escape_silent as escape
class SilentMarkup(Markup):
__slots__ = ()
@classmethod
def escape(cls, s):
return cls(escape(s))
New-Style String Formatting
---------------------------
Starting with MarkupSafe 0.21 new style string formats from Python 2.6 and
3.x are now fully supported. Previously the escape behavior of those
functions was spotty at best. The new implementations operates under the
following algorithm:
1. if an object has an ``__html_format__`` method it is called as
replacement for ``__format__`` with the format specifier. It either
has to return a string or markup object.
2. if an object has an ``__html__`` method it is called.
3. otherwise the default format system of Python kicks in and the result
is HTML escaped.
Here is how you can implement your own formatting:
.. code-block:: python
class User(object):
def __init__(self, id, username):
self.id = id
self.username = username
def __html_format__(self, format_spec):
if format_spec == 'link':
return Markup('<a href="/user/{0}">{1}</a>').format(
self.id,
self.__html__(),
)
elif format_spec:
raise ValueError('Invalid format spec')
return self.__html__()
def __html__(self):
return Markup('<span class=user>{0}</span>').format(self.username)
And to format that user:
.. code-block:: python
>>> user = User(1, 'foo')
>>> Markup('<p>User: {0:link}').format(user)
Markup(u'<p>User: <a href="/user/1"><span class=user>foo</span></a>')
Markupsafe supports Python 2.6, 2.7 and Python 3.3 and higher.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: HTML

View File

@ -0,0 +1,18 @@
AUTHORS
CHANGES
LICENSE
MANIFEST.in
README.rst
setup.cfg
setup.py
tests.py
MarkupSafe.egg-info/PKG-INFO
MarkupSafe.egg-info/SOURCES.txt
MarkupSafe.egg-info/dependency_links.txt
MarkupSafe.egg-info/not-zip-safe
MarkupSafe.egg-info/top_level.txt
markupsafe/__init__.py
markupsafe/_compat.py
markupsafe/_constants.py
markupsafe/_native.py
markupsafe/_speedups.c

View File

@ -0,0 +1,15 @@
..\markupsafe\__init__.py
..\markupsafe\__pycache__\__init__.cpython-36.pyc
..\markupsafe\__pycache__\_compat.cpython-36.pyc
..\markupsafe\__pycache__\_constants.cpython-36.pyc
..\markupsafe\__pycache__\_native.cpython-36.pyc
..\markupsafe\_compat.py
..\markupsafe\_constants.py
..\markupsafe\_native.py
..\markupsafe\_speedups.c
..\markupsafe\_speedups.cp36-win_amd64.pyd
PKG-INFO
SOURCES.txt
dependency_links.txt
not-zip-safe
top_level.txt

View File

@ -0,0 +1,50 @@
# PyJWT
[![travis-status-image]][travis]
[![appveyor-status-image]][appveyor]
[![pypi-version-image]][pypi]
[![coveralls-status-image]][coveralls]
[![docs-status-image]][docs]
A Python implementation of [RFC 7519][jwt-spec].
Original implementation was written by [@progrium][progrium].
## Installing
```
$ pip install PyJWT
```
## Usage
```python
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}
```
## Tests
You can run tests from the project root after cloning with:
```
$ python setup.py test
```
[travis-status-image]: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master
[travis]: http://travis-ci.org/jpadilla/pyjwt?branch=master
[appveyor-status-image]: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
[appveyor]: https://ci.appveyor.com/project/jpadilla/pyjwt
[pypi-version-image]: https://img.shields.io/pypi/v/pyjwt.svg
[pypi]: https://pypi.python.org/pypi/pyjwt
[coveralls-status-image]: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
[coveralls]: https://coveralls.io/r/jpadilla/pyjwt?branch=master
[docs-status-image]: https://readthedocs.org/projects/pyjwt/badge/?version=latest
[docs]: http://pyjwt.readthedocs.org
[jwt-spec]: https://tools.ietf.org/html/rfc7519
[progrium]: https://github.com/progrium

View File

@ -0,0 +1,82 @@
Metadata-Version: 2.0
Name: PyJWT
Version: 1.4.2
Summary: JSON Web Token implementation in Python
Home-page: http://github.com/jpadilla/pyjwt
Author: José Padilla
Author-email: hello@jpadilla.com
License: MIT
Keywords: jwt json web token security signing
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Topic :: Utilities
Provides-Extra: crypto
Requires-Dist: cryptography; extra == 'crypto'
Provides-Extra: flake8
Requires-Dist: flake8; extra == 'flake8'
Requires-Dist: flake8-import-order; extra == 'flake8'
Requires-Dist: pep8-naming; extra == 'flake8'
Provides-Extra: test
Requires-Dist: pytest (==2.7.3); extra == 'test'
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: pytest-runner; extra == 'test'
# PyJWT
[![travis-status-image]][travis]
[![appveyor-status-image]][appveyor]
[![pypi-version-image]][pypi]
[![coveralls-status-image]][coveralls]
[![docs-status-image]][docs]
A Python implementation of [RFC 7519][jwt-spec].
Original implementation was written by [@progrium][progrium].
## Installing
```
$ pip install PyJWT
```
## Usage
```python
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}
```
## Tests
You can run tests from the project root after cloning with:
```
$ python setup.py test
```
[travis-status-image]: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master
[travis]: http://travis-ci.org/jpadilla/pyjwt?branch=master
[appveyor-status-image]: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
[appveyor]: https://ci.appveyor.com/project/jpadilla/pyjwt
[pypi-version-image]: https://img.shields.io/pypi/v/pyjwt.svg
[pypi]: https://pypi.python.org/pypi/pyjwt
[coveralls-status-image]: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
[coveralls]: https://coveralls.io/r/jpadilla/pyjwt?branch=master
[docs-status-image]: https://readthedocs.org/projects/pyjwt/badge/?version=latest
[docs]: http://pyjwt.readthedocs.org
[jwt-spec]: https://tools.ietf.org/html/rfc7519
[progrium]: https://github.com/progrium

View File

@ -0,0 +1,33 @@
jwt/__init__.py,sha256=7NJDM0Wdyc8Jr_LuDyPwmS3y7NJghrFZ6WXWy-2qfrc,738
jwt/__main__.py,sha256=93alATh8EI-GuR1-CiYzGwtjbyFllWDRGhEBPHDsFqU,3587
jwt/algorithms.py,sha256=bixXU8k0oqPOHbnQ3sm4ne-ofWn3tFloTeSL7iifDiM,8677
jwt/api_jws.py,sha256=YK6ODhpxH99mDcmdt4cVs8WPN0YTLLr8-_22q-WONuc,6760
jwt/api_jwt.py,sha256=5Qmyc9Y0JXrjdBuY8OuiFdiCcHDgjF799ZbxP7Ef9ZQ,6709
jwt/compat.py,sha256=7-3RAohhsEDwndWt1OyXaV4-HsF3QQnO_F-BddLSfa8,1317
jwt/exceptions.py,sha256=63QgVtqVgRHdVAi0NqRO0s13opKdKZmwUGzW_CyPw4c,841
jwt/utils.py,sha256=DsCtJn-c_q35NtTcDI3EVgsOJTWqHErQaOLPElBItio,1566
jwt/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jwt/contrib/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
jwt/contrib/algorithms/py_ecdsa.py,sha256=tSTUrwx-u14DJcqAChRzJG-wf7bEY2Gv2hI5xSZZNjk,1771
jwt/contrib/algorithms/pycrypto.py,sha256=M3nH1Rrk6yb6aPGo6zT4EI_MvPUM4vhO1EwC-uX9JAo,1250
PyJWT-1.4.2.dist-info/DESCRIPTION.rst,sha256=0qxBo0Hjlq25hSJbVkeg0h2pt1_S39LrMuEiPQbEmtI,1507
PyJWT-1.4.2.dist-info/entry_points.txt,sha256=DtGQ73zJer9SDGHdWhcHaianJpovcNUQkoqLa_8KObw,43
PyJWT-1.4.2.dist-info/METADATA,sha256=kTVDjNG00RK_sbMRoISEmBnRV7Vvpb0GhJt4IEPHP-A,2690
PyJWT-1.4.2.dist-info/metadata.json,sha256=MDnukon_DIPRItFf5RMwbx4sBvaagLxLgXtoV6eeA48,1418
PyJWT-1.4.2.dist-info/RECORD,,
PyJWT-1.4.2.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4
PyJWT-1.4.2.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
../../Scripts/jwt.exe,sha256=aPrULChdCm_xQarigRU2UmnZjYBpn6ucPgW12sGdm2A,102769
PyJWT-1.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc,,
jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc,,
jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc,,
jwt/contrib/__pycache__/__init__.cpython-36.pyc,,
jwt/__pycache__/algorithms.cpython-36.pyc,,
jwt/__pycache__/api_jws.cpython-36.pyc,,
jwt/__pycache__/api_jwt.cpython-36.pyc,,
jwt/__pycache__/compat.cpython-36.pyc,,
jwt/__pycache__/exceptions.cpython-36.pyc,,
jwt/__pycache__/utils.cpython-36.pyc,,
jwt/__pycache__/__init__.cpython-36.pyc,,
jwt/__pycache__/__main__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.24.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
jwt = jwt.__main__:main

View File

@ -0,0 +1 @@
{"license": "MIT", "name": "PyJWT", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "test_requires": [{"requires": ["pytest (==2.7.3)", "pytest-cov", "pytest-runner"]}], "summary": "JSON Web Token implementation in Python", "run_requires": [{"requires": ["pytest (==2.7.3)", "pytest-cov", "pytest-runner"], "extra": "test"}, {"requires": ["cryptography"], "extra": "crypto"}, {"requires": ["flake8", "flake8-import-order", "pep8-naming"], "extra": "flake8"}], "version": "1.4.2", "extensions": {"python.details": {"project_urls": {"Home": "http://github.com/jpadilla/pyjwt"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "hello@jpadilla.com", "name": "Jos\u00e9 Padilla"}]}, "python.commands": {"wrap_console": {"jwt": "jwt.__main__:main"}}, "python.exports": {"console_scripts": {"jwt": "jwt.__main__:main"}}}, "keywords": ["jwt", "json", "web", "token", "security", "signing"], "classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Utilities"], "extras": ["crypto", "flake8", "test"]}

View File

@ -0,0 +1,80 @@
Werkzeug
========
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
a simple collection of various utilities for WSGI applications and has
become one of the most advanced WSGI utility libraries.
It includes:
* An interactive debugger that allows inspecting stack traces and source
code in the browser with an interactive interpreter for any frame in
the stack.
* A full-featured request object with objects to interact with headers,
query args, form data, files, and cookies.
* A response object that can wrap other WSGI applications and handle
streaming data.
* A routing system for matching URLs to endpoints and generating URLs
for endpoints, with an extensible system for capturing variables from
URLs.
* HTTP utilities to handle entity tags, cache control, dates, user
agents, cookies, files, and more.
* A threaded WSGI server for use while developing applications locally.
* A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
to the developer to choose a template engine, database adapter, and even
how to handle requests. It can be used to build all sorts of end user
applications such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Werkzeug
A Simple Example
----------------
.. code-block:: python
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
Links
-----
* Website: https://www.palletsprojects.com/p/werkzeug/
* Releases: https://pypi.org/project/Werkzeug/
* Code: https://github.com/pallets/werkzeug
* Issue tracker: https://github.com/pallets/werkzeug/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/werkzeug
* Windows: https://ci.appveyor.com/project/davidism/werkzeug
* Test coverage: https://codecov.io/gh/pallets/werkzeug
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
.. _Flask: https://www.palletsprojects.com/p/flask/
.. _pip: https://pip.pypa.io/en/stable/quickstart/

View File

@ -0,0 +1,31 @@
Copyright © 2007 by the Pallets team.
Some 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 copyright holder 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 AND DOCUMENTATION 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 HOLDER 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
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -0,0 +1,116 @@
Metadata-Version: 2.0
Name: Werkzeug
Version: 0.14.1
Summary: The comprehensive WSGI web application library.
Home-page: https://www.palletsprojects.org/p/werkzeug/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD
Description-Content-Type: UNKNOWN
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: dev
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: sphinx; extra == 'dev'
Requires-Dist: tox; extra == 'dev'
Provides-Extra: termcolor
Requires-Dist: termcolor; extra == 'termcolor'
Provides-Extra: watchdog
Requires-Dist: watchdog; extra == 'watchdog'
Werkzeug
========
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
a simple collection of various utilities for WSGI applications and has
become one of the most advanced WSGI utility libraries.
It includes:
* An interactive debugger that allows inspecting stack traces and source
code in the browser with an interactive interpreter for any frame in
the stack.
* A full-featured request object with objects to interact with headers,
query args, form data, files, and cookies.
* A response object that can wrap other WSGI applications and handle
streaming data.
* A routing system for matching URLs to endpoints and generating URLs
for endpoints, with an extensible system for capturing variables from
URLs.
* HTTP utilities to handle entity tags, cache control, dates, user
agents, cookies, files, and more.
* A threaded WSGI server for use while developing applications locally.
* A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
to the developer to choose a template engine, database adapter, and even
how to handle requests. It can be used to build all sorts of end user
applications such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Werkzeug
A Simple Example
----------------
.. code-block:: python
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
Links
-----
* Website: https://www.palletsprojects.com/p/werkzeug/
* Releases: https://pypi.org/project/Werkzeug/
* Code: https://github.com/pallets/werkzeug
* Issue tracker: https://github.com/pallets/werkzeug/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/werkzeug
* Windows: https://ci.appveyor.com/project/davidism/werkzeug
* Test coverage: https://codecov.io/gh/pallets/werkzeug
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
.. _Flask: https://www.palletsprojects.com/p/flask/
.. _pip: https://pip.pypa.io/en/stable/quickstart/

View File

@ -0,0 +1,97 @@
Werkzeug-0.14.1.dist-info/DESCRIPTION.rst,sha256=rOCN36jwsWtWsTpqPG96z7FMilB5qI1CIARSKRuUmz8,2452
Werkzeug-0.14.1.dist-info/LICENSE.txt,sha256=xndz_dD4m269AF9l_Xbl5V3tM1N3C1LoZC2PEPxWO-8,1534
Werkzeug-0.14.1.dist-info/METADATA,sha256=FbfadrPdJNUWAxMOKxGUtHe5R3IDSBKYYmAz3FvI3uY,3872
Werkzeug-0.14.1.dist-info/RECORD,,
Werkzeug-0.14.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110
Werkzeug-0.14.1.dist-info/metadata.json,sha256=4489UTt6HBp2NQil95-pBkjU4Je93SMHvMxZ_rjOpqA,1452
Werkzeug-0.14.1.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
werkzeug/__init__.py,sha256=NR0d4n_-U9BLVKlOISean3zUt2vBwhvK-AZE6M0sC0k,6842
werkzeug/_compat.py,sha256=8c4U9o6A_TR9nKCcTbpZNxpqCXcXDVIbFawwKM2s92c,6311
werkzeug/_internal.py,sha256=GhEyGMlsSz_tYjsDWO9TG35VN7304MM8gjKDrXLEdVc,13873
werkzeug/_reloader.py,sha256=AyPphcOHPbu6qzW0UbrVvTDJdre5WgpxbhIJN_TqzUc,9264
werkzeug/datastructures.py,sha256=3IgNKNqrz-ZjmAG7y3YgEYK-enDiMT_b652PsypWcYg,90080
werkzeug/exceptions.py,sha256=3wp95Hqj9FqV8MdikV99JRcHse_fSMn27V8tgP5Hw2c,20505
werkzeug/filesystem.py,sha256=hHWeWo_gqLMzTRfYt8-7n2wWcWUNTnDyudQDLOBEICE,2175
werkzeug/formparser.py,sha256=mUuCwjzjb8_E4RzrAT2AioLuZSYpqR1KXTK6LScRYzA,21722
werkzeug/http.py,sha256=RQg4MJuhRv2isNRiEh__Phh09ebpfT3Kuu_GfrZ54_c,40079
werkzeug/local.py,sha256=QdQhWV5L8p1Y1CJ1CDStwxaUs24SuN5aebHwjVD08C8,14553
werkzeug/posixemulation.py,sha256=xEF2Bxc-vUCPkiu4IbfWVd3LW7DROYAT-ExW6THqyzw,3519
werkzeug/routing.py,sha256=2JVtdSgxKGeANy4Z_FP-dKESvKtkYGCZ1J2fARCLGCY,67214
werkzeug/script.py,sha256=DwaVDcXdaOTffdNvlBdLitxWXjKaRVT32VbhDtljFPY,11365
werkzeug/security.py,sha256=0m107exslz4QJLWQCpfQJ04z3re4eGHVggRvrQVAdWc,9193
werkzeug/serving.py,sha256=A0flnIJHufdn2QJ9oeuHfrXwP3LzP8fn3rNW6hbxKUg,31926
werkzeug/test.py,sha256=XmECSmnpASiYQTct4oMiWr0LT5jHWCtKqnpYKZd2ui8,36100
werkzeug/testapp.py,sha256=3HQRW1sHZKXuAjCvFMet4KXtQG3loYTFnvn6LWt-4zI,9396
werkzeug/urls.py,sha256=dUeLg2IeTm0WLmSvFeD4hBZWGdOs-uHudR5-t8n9zPo,36771
werkzeug/useragents.py,sha256=BhYMf4cBTHyN4U0WsQedePIocmNlH_34C-UwqSThGCc,5865
werkzeug/utils.py,sha256=BrY1j0DHQ8RTb0K1StIobKuMJhN9SQQkWEARbrh2qpk,22972
werkzeug/websocket.py,sha256=PpSeDxXD_0UsPAa5hQhQNM6mxibeUgn8lA8eRqiS0vM,11344
werkzeug/wrappers.py,sha256=kbyL_aFjxELwPgMwfNCYjKu-CR6kNkh-oO8wv3GXbk8,84511
werkzeug/wsgi.py,sha256=1Nob-aeChWQf7MsiicO8RZt6J90iRzEcik44ev9Qu8s,49347
werkzeug/contrib/__init__.py,sha256=f7PfttZhbrImqpr5Ezre8CXgwvcGUJK7zWNpO34WWrw,623
werkzeug/contrib/atom.py,sha256=qqfJcfIn2RYY-3hO3Oz0aLq9YuNubcPQ_KZcNsDwVJo,15575
werkzeug/contrib/cache.py,sha256=xBImHNj09BmX_7kC5NUCx8f_l4L8_O7zi0jCL21UZKE,32163
werkzeug/contrib/fixers.py,sha256=gR06T-w71ur-tHQ_31kP_4jpOncPJ4Wc1dOqTvYusr8,10179
werkzeug/contrib/iterio.py,sha256=RlqDvGhz0RneTpzE8dVc-yWCUv4nkPl1jEc_EDp2fH0,10814
werkzeug/contrib/jsrouting.py,sha256=QTmgeDoKXvNK02KzXgx9lr3cAH6fAzpwF5bBdPNvJPs,8564
werkzeug/contrib/limiter.py,sha256=iS8-ahPZ-JLRnmfIBzxpm7O_s3lPsiDMVWv7llAIDCI,1334
werkzeug/contrib/lint.py,sha256=Mj9NeUN7s4zIUWeQOAVjrmtZIcl3Mm2yDe9BSIr9YGE,12558
werkzeug/contrib/profiler.py,sha256=ISwCWvwVyGpDLRBRpLjo_qUWma6GXYBrTAco4PEQSHY,5151
werkzeug/contrib/securecookie.py,sha256=uWMyHDHY3lkeBRiCSayGqWkAIy4a7xAbSE_Hln9ecqc,12196
werkzeug/contrib/sessions.py,sha256=39LVNvLbm5JWpbxM79WC2l87MJFbqeISARjwYbkJatw,12577
werkzeug/contrib/testtools.py,sha256=G9xN-qeihJlhExrIZMCahvQOIDxdL9NiX874jiiHFMs,2453
werkzeug/contrib/wrappers.py,sha256=v7OYlz7wQtDlS9fey75UiRZ1IkUWqCpzbhsLy4k14Hw,10398
werkzeug/debug/__init__.py,sha256=uSn9BqCZ5E3ySgpoZtundpROGsn-uYvZtSFiTfAX24M,17452
werkzeug/debug/console.py,sha256=n3-dsKk1TsjnN-u4ZgmuWCU_HO0qw5IA7ttjhyyMM6I,5607
werkzeug/debug/repr.py,sha256=bKqstDYGfECpeLerd48s_hxuqK4b6UWnjMu3d_DHO8I,9340
werkzeug/debug/tbtools.py,sha256=rBudXCmkVdAKIcdhxANxgf09g6kQjJWW9_5bjSpr4OY,18451
werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
werkzeug/debug/shared/debugger.js,sha256=PKPVYuyO4SX1hkqLOwCLvmIEO5154WatFYaXE-zIfKI,6264
werkzeug/debug/shared/jquery.js,sha256=7LkWEzqTdpEfELxcZZlS6wAx5Ff13zZ83lYO2_ujj7g,95957
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818
werkzeug/debug/shared/style.css,sha256=IEO0PC2pWmh2aEyGCaN--txuWsRCliuhlbEhPDFwh0A,6270
werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220
Werkzeug-0.14.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
werkzeug/contrib/__pycache__/atom.cpython-36.pyc,,
werkzeug/contrib/__pycache__/cache.cpython-36.pyc,,
werkzeug/contrib/__pycache__/fixers.cpython-36.pyc,,
werkzeug/contrib/__pycache__/iterio.cpython-36.pyc,,
werkzeug/contrib/__pycache__/jsrouting.cpython-36.pyc,,
werkzeug/contrib/__pycache__/limiter.cpython-36.pyc,,
werkzeug/contrib/__pycache__/lint.cpython-36.pyc,,
werkzeug/contrib/__pycache__/profiler.cpython-36.pyc,,
werkzeug/contrib/__pycache__/securecookie.cpython-36.pyc,,
werkzeug/contrib/__pycache__/sessions.cpython-36.pyc,,
werkzeug/contrib/__pycache__/testtools.cpython-36.pyc,,
werkzeug/contrib/__pycache__/wrappers.cpython-36.pyc,,
werkzeug/contrib/__pycache__/__init__.cpython-36.pyc,,
werkzeug/debug/__pycache__/console.cpython-36.pyc,,
werkzeug/debug/__pycache__/repr.cpython-36.pyc,,
werkzeug/debug/__pycache__/tbtools.cpython-36.pyc,,
werkzeug/debug/__pycache__/__init__.cpython-36.pyc,,
werkzeug/__pycache__/datastructures.cpython-36.pyc,,
werkzeug/__pycache__/exceptions.cpython-36.pyc,,
werkzeug/__pycache__/filesystem.cpython-36.pyc,,
werkzeug/__pycache__/formparser.cpython-36.pyc,,
werkzeug/__pycache__/http.cpython-36.pyc,,
werkzeug/__pycache__/local.cpython-36.pyc,,
werkzeug/__pycache__/posixemulation.cpython-36.pyc,,
werkzeug/__pycache__/routing.cpython-36.pyc,,
werkzeug/__pycache__/script.cpython-36.pyc,,
werkzeug/__pycache__/security.cpython-36.pyc,,
werkzeug/__pycache__/serving.cpython-36.pyc,,
werkzeug/__pycache__/test.cpython-36.pyc,,
werkzeug/__pycache__/testapp.cpython-36.pyc,,
werkzeug/__pycache__/urls.cpython-36.pyc,,
werkzeug/__pycache__/useragents.cpython-36.pyc,,
werkzeug/__pycache__/utils.cpython-36.pyc,,
werkzeug/__pycache__/websocket.cpython-36.pyc,,
werkzeug/__pycache__/wrappers.cpython-36.pyc,,
werkzeug/__pycache__/wsgi.cpython-36.pyc,,
werkzeug/__pycache__/_compat.cpython-36.pyc,,
werkzeug/__pycache__/_internal.cpython-36.pyc,,
werkzeug/__pycache__/_reloader.cpython-36.pyc,,
werkzeug/__pycache__/__init__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.26.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1 @@
{"generator": "bdist_wheel (0.26.0)", "summary": "The comprehensive WSGI web application library.", "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"project_urls": {"Home": "https://www.palletsprojects.org/p/werkzeug/"}, "contacts": [{"email": "armin.ronacher@active-4.com", "name": "Armin Ronacher", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}}}, "license": "BSD", "metadata_version": "2.0", "name": "Werkzeug", "platform": "any", "extras": ["dev", "termcolor", "watchdog"], "run_requires": [{"requires": ["coverage", "pytest", "sphinx", "tox"], "extra": "dev"}, {"requires": ["termcolor"], "extra": "termcolor"}, {"requires": ["watchdog"], "extra": "watchdog"}], "version": "0.14.1"}

View File

@ -0,0 +1,384 @@
===========
aniso8601
===========
----------------------------------
Another ISO 8601 parser for Python
----------------------------------
Features
========
* Pure Python implementation
* Python 3 support
* Logical behavior
- Parse a time, get a `datetime.time <http://docs.python.org/2/library/datetime.html#datetime.time>`_
- Parse a date, get a `datetime.date <http://docs.python.org/2/library/datetime.html#datetime.date>`_
- Parse a datetime, get a `datetime.datetime <http://docs.python.org/2/library/datetime.html#datetime.datetime>`_
- Parse a duration, get a `datetime.timedelta <http://docs.python.org/2/library/datetime.html#datetime.timedelta>`_
- Parse an interval, get a tuple of dates or datetimes
- Parse a repeating interval, get a date or datetime `generator <http://www.python.org/dev/peps/pep-0255/>`_
* UTC offset represented as fixed-offset tzinfo
* Optional `dateutil.relativedelta <http://dateutil.readthedocs.io/en/latest/relativedelta.html>`_ support for calendar accuracy
* No regular expressions
Installation
============
The recommended installation method is to use pip::
$ pip install aniso8601
Alternatively, you can download the source (git repository hosted at `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_) and install directly::
$ python setup.py install
Use
===
Parsing datetimes
-----------------
To parse a typical ISO 8601 datetime string::
>>> import aniso8601
>>> aniso8601.parse_datetime('1977-06-10T12:00:00Z')
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC)
Alternative delimiters can be specified, for example, a space::
>>> aniso8601.parse_datetime('1977-06-10 12:00:00Z', delimiter=' ')
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC)
UTC offsets are supported::
>>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00')
datetime.datetime(1979, 6, 5, 8, 0, tzinfo=-8:00:00 UTC)
If a UTC offset is not specified, the returned datetime will be naive::
>>> aniso8601.parse_datetime('1983-01-22T08:00:00')
datetime.datetime(1983, 1, 22, 8, 0)
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`::
>>> aniso8601.parse_datetime('2018-03-06T23:59:60')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/time.py", line 127, in parse_datetime
timepart = parse_time(isotimestr)
File "aniso8601/time.py", line 110, in parse_time
return _parse_time_naive(timestr)
File "aniso8601/time.py", line 140, in _parse_time_naive
return _RESOLUTION_MAP[get_time_resolution(timestr)](timestr)
File "aniso8601/time.py", line 214, in _parse_second_time
raise LeapSecondError('Leap seconds are not supported.')
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported.
Parsing dates
-------------
To parse a date represented in an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_date('1984-04-23')
datetime.date(1984, 4, 23)
Basic format is supported as well::
>>> aniso8601.parse_date('19840423')
datetime.date(1984, 4, 23)
To parse a date using the ISO 8601 week date format::
>>> aniso8601.parse_date('1986-W38-1')
datetime.date(1986, 9, 15)
To parse an ISO 8601 ordinal date::
>>> aniso8601.parse_date('1988-132')
datetime.date(1988, 5, 11)
Parsing times
-------------
To parse a time formatted as an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_time('11:31:14')
datetime.time(11, 31, 14)
As with all of the above, basic format is supported::
>>> aniso8601.parse_time('113114')
datetime.time(11, 31, 14)
A UTC offset can be specified for times::
>>> aniso8601.parse_time('17:18:19-02:30')
datetime.time(17, 18, 19, tzinfo=-2:30:00 UTC)
>>> aniso8601.parse_time('171819Z')
datetime.time(17, 18, 19, tzinfo=+0:00:00 UTC)
Reduced accuracy is supported::
>>> aniso8601.parse_time('21:42')
datetime.time(21, 42)
>>> aniso8601.parse_time('22')
datetime.time(22, 0)
A decimal fraction is always allowed on the lowest order element of an ISO 8601 formatted time::
>>> aniso8601.parse_time('22:33.5')
datetime.time(22, 33, 30)
>>> aniso8601.parse_time('23.75')
datetime.time(23, 45)
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`::
>>> aniso8601.parse_time('23:59:60')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/time.py", line 110, in parse_time
return _parse_time_naive(timestr)
File "aniso8601/time.py", line 140, in _parse_time_naive
return _RESOLUTION_MAP[get_time_resolution(timestr)](timestr)
File "aniso8601/time.py", line 214, in _parse_second_time
raise LeapSecondError('Leap seconds are not supported.')
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported.
Parsing durations
-----------------
To parse a duration formatted as an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_duration('P1Y2M3DT4H54M6S')
datetime.timedelta(428, 17646)
Reduced accuracy is supported::
>>> aniso8601.parse_duration('P1Y')
datetime.timedelta(365)
A decimal fraction is allowed on the lowest order element::
>>> aniso8601.parse_duration('P1YT3.5M')
datetime.timedelta(365, 210)
The decimal fraction can be specified with a comma instead of a full-stop::
>>> aniso8601.parse_duration('P1YT3,5M')
datetime.timedelta(365, 210)
Parsing a duration from a combined date and time is supported as well::
>>> aniso8601.parse_duration('P0001-01-02T01:30:5')
datetime.timedelta(397, 5405)
The above treat years as 365 days and months as 30 days. If calendar level accuracy is required, the relative keyword argument can be used if `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_ is installed::
>>> import aniso8601
>>> from datetime import date
>>> one_month = aniso8601.parse_duration('P1M', relative=True)
>>> print one_month
relativedelta(months=+1)
>>> date(2003,1,27) + one_month
datetime.date(2003, 2, 27)
>>> date(2003,1,31) + one_month
datetime.date(2003, 2, 28)
>>> date(2003,1,31) + two_months
datetime.date(2003, 3, 31)
Since a relative fractional month or year is not logical, a :code:`RelativeValueError` is raised when attempting to parse a duration with :code:`relative=True` and fractional month or year::
>>> aniso8601.parse_duration('P2.1Y', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 73, in _parse_duration_prescribed
raise RelativeValueError('Fractional months and years are not defined for relative intervals.')
aniso8601.exceptions.RelativeValueError: Fractional months and years are not defined for relative intervals.
If :code:`relative=True` is set without python-dateutil available, a :code:`RuntimeError` is raised::
>>> aniso8601.parse_duration('P1M', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 77, in _parse_duration_prescribed
raise RuntimeError('dateutil must be installed for relative duration support.')
RuntimeError: dateutil must be installed for relative duration support
Parsing intervals
-----------------
To parse an interval specified by a start and end::
>>> import aniso8601
>>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00')
(datetime.datetime(2007, 3, 1, 13, 0), datetime.datetime(2008, 5, 11, 15, 30))
Intervals specified by a start time and a duration are supported::
>>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M')
(datetime.datetime(2007, 3, 1, 13, 0, tzinfo=+0:00:00 UTC), datetime.datetime(2008, 5, 9, 15, 30, tzinfo=+0:00:00 UTC))
A duration can also be specified by a duration and end time::
>>> aniso8601.parse_interval('P1M/1981-04-05')
(datetime.date(1981, 4, 5), datetime.date(1981, 3, 6))
Notice that the result of the above parse is not in order from earliest to latest. If sorted intervals are required, simply use the :code:`sorted` keyword as shown below::
>>> sorted(aniso8601.parse_interval('P1M/1981-04-05'))
[datetime.date(1981, 3, 6), datetime.date(1981, 4, 5)]
The end of an interval is given as a datetime when required to maintain the resolution specified by a duration, even if the duration start is given as a date::
>>> aniso8601.parse_interval('2014-11-12/PT4H54M6.5S')
(datetime.date(2014, 11, 12), datetime.datetime(2014, 11, 12, 4, 54, 6, 500000))
Repeating intervals are supported as well, and return a generator::
>>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D')
<generator object date_generator at 0x7f698cdefc80>
>>> list(aniso8601.parse_repeating_interval('R3/1981-04-05/P1D'))
[datetime.date(1981, 4, 5), datetime.date(1981, 4, 6), datetime.date(1981, 4, 7)]
Repeating intervals are allowed to go in the reverse direction::
>>> list(aniso8601.parse_repeating_interval('R2/PT1H2M/1980-03-05T01:01:00'))
[datetime.datetime(1980, 3, 5, 1, 1), datetime.datetime(1980, 3, 4, 23, 59)]
Unbounded intervals are also allowed (Python 2)::
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')
>>> result.next()
datetime.datetime(1980, 3, 5, 1, 1)
>>> result.next()
datetime.datetime(1980, 3, 4, 23, 59)
or for Python 3::
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')
>>> next(result)
datetime.datetime(1980, 3, 5, 1, 1)
>>> next(result)
datetime.datetime(1980, 3, 4, 23, 59)
Note that you should never try to convert a generator produced by an unbounded interval to a list::
>>> list(aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 161, in _date_generator_unbounded
currentdate += timedelta
OverflowError: date value out of range
The above treat years as 365 days and months as 30 days. If calendar level accuracy is required, the relative keyword argument can be used if `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_ is installed::
>>> aniso8601.parse_interval('2003-01-27/P1M', relative=True)
(datetime.date(2003, 1, 27), datetime.date(2003, 2, 27))
>>> aniso8601.parse_interval('2003-01-31/P1M', relative=True)
(datetime.date(2003, 1, 31), datetime.date(2003, 2, 28))
>>> aniso8601.parse_interval('P1Y/2001-02-28', relative=True)
(datetime.date(2001, 2, 28), datetime.date(2000, 2, 28)
Fractional years and months do not make sense for relative intervals. A :code:`RelativeValueError` is raised when attempting to parse an interval with :code:`relative=True` and a fractional month or year::
>>> aniso8601.parse_interval('P1.1Y/2001-02-28', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 37, in parse_interval
interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative)
File "aniso8601/interval.py", line 89, in _parse_interval_parts
duration = parse_duration(firstpart, relative=relative)
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 73, in _parse_duration_prescribed
raise RelativeValueError('Fractional months and years are not defined for relative intervals.')
aniso8601.exceptions.RelativeValueError: Fractional months and years are not defined for relative intervals.
If :code:`relative=True` is set without python-dateutil available, a :code:`RuntimeError` is raised::
>>> aniso8601.parse_interval('2003-01-27/P1M', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 37, in parse_interval
interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative)
File "aniso8601/interval.py", line 108, in _parse_interval_parts
duration = parse_duration(secondpart, relative=relative)
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 77, in _parse_duration_prescribed
raise RuntimeError('dateutil must be installed for relative duration support.')
RuntimeError: dateutil must be installed for relative duration support.
Date and time resolution
------------------------
In some situations, it may be useful to figure out the resolution provided by an ISO 8601 date or time string. Two functions are provided for this purpose.
To get the resolution of a ISO 8601 time string::
>>> aniso8601.get_time_resolution('11:31:14') == aniso8601.resolution.TimeResolution.Seconds
True
>>> aniso8601.get_time_resolution('11:31') == aniso8601.resolution.TimeResolution.Minutes
True
>>> aniso8601.get_time_resolution('11') == aniso8601.resolution.TimeResolution.Hours
True
Similarly, for an ISO 8601 date string::
>>> aniso8601.get_date_resolution('1981-04-05') == aniso8601.resolution.DateResolution.Day
True
>>> aniso8601.get_date_resolution('1981-04') == aniso8601.resolution.DateResolution.Month
True
>>> aniso8601.get_date_resolution('1981') == aniso8601.resolution.DateResolution.Year
True
Development
===========
Setup
-----
It is recommended to develop using a `virtualenv <https://virtualenv.pypa.io/en/stable/>`_.
The tests require the :code:`relative` feature to be enabled, install the necessary dependencies using pip::
$ pip install .[relative]
Tests
-----
To run the unit tests, navigate to the source directory and run the tests for the python version being worked on (python2, python3)::
$ python2 -m unittest discover aniso8601/tests/
or::
$ python3 -m unittest discover aniso8601/tests/
Contributing
============
aniso8601 is an open source project hosted on `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_.
Any and all bugs are welcome on our `issue tracker <https://bitbucket.org/nielsenb/aniso8601/issues>`_.
Of particular interest are valid ISO 8601 strings that don't parse, or invalid ones that do. At a minimum,
bug reports should include an example of the misbehaving string, as well as the expected result. Of course
patches containing unit tests (or fixed bugs) are welcome!
References
==========
* `ISO 8601:2004(E) <http://dotat.at/tmp/ISO_8601-2004_E.pdf>`_ (Caution, PDF link)
* `Wikipedia article on ISO 8601 <http://en.wikipedia.org/wiki/Iso8601>`_
* `Discussion on alternative ISO 8601 parsers for Python <https://groups.google.com/forum/#!topic/comp.lang.python/Q2w4R89Nq1w>`_

View File

@ -0,0 +1,413 @@
Metadata-Version: 2.0
Name: aniso8601
Version: 3.0.2
Summary: A library for parsing ISO 8601 strings.
Home-page: https://bitbucket.org/nielsenb/aniso8601
Author: Brandon Nielsen
Author-email: nielsenb@jetfuse.net
License: UNKNOWN
Project-URL: Source, https://bitbucket.org/nielsenb/aniso8601
Project-URL: Documentation, http://aniso8601.readthedocs.io/en/latest/
Project-URL: Tracker, https://bitbucket.org/nielsenb/aniso8601/issues
Keywords: iso8601 parser
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: relative
Provides-Extra: relative
Requires-Dist: python-dateutil; extra == 'relative'
===========
aniso8601
===========
----------------------------------
Another ISO 8601 parser for Python
----------------------------------
Features
========
* Pure Python implementation
* Python 3 support
* Logical behavior
- Parse a time, get a `datetime.time <http://docs.python.org/2/library/datetime.html#datetime.time>`_
- Parse a date, get a `datetime.date <http://docs.python.org/2/library/datetime.html#datetime.date>`_
- Parse a datetime, get a `datetime.datetime <http://docs.python.org/2/library/datetime.html#datetime.datetime>`_
- Parse a duration, get a `datetime.timedelta <http://docs.python.org/2/library/datetime.html#datetime.timedelta>`_
- Parse an interval, get a tuple of dates or datetimes
- Parse a repeating interval, get a date or datetime `generator <http://www.python.org/dev/peps/pep-0255/>`_
* UTC offset represented as fixed-offset tzinfo
* Optional `dateutil.relativedelta <http://dateutil.readthedocs.io/en/latest/relativedelta.html>`_ support for calendar accuracy
* No regular expressions
Installation
============
The recommended installation method is to use pip::
$ pip install aniso8601
Alternatively, you can download the source (git repository hosted at `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_) and install directly::
$ python setup.py install
Use
===
Parsing datetimes
-----------------
To parse a typical ISO 8601 datetime string::
>>> import aniso8601
>>> aniso8601.parse_datetime('1977-06-10T12:00:00Z')
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC)
Alternative delimiters can be specified, for example, a space::
>>> aniso8601.parse_datetime('1977-06-10 12:00:00Z', delimiter=' ')
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC)
UTC offsets are supported::
>>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00')
datetime.datetime(1979, 6, 5, 8, 0, tzinfo=-8:00:00 UTC)
If a UTC offset is not specified, the returned datetime will be naive::
>>> aniso8601.parse_datetime('1983-01-22T08:00:00')
datetime.datetime(1983, 1, 22, 8, 0)
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`::
>>> aniso8601.parse_datetime('2018-03-06T23:59:60')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/time.py", line 127, in parse_datetime
timepart = parse_time(isotimestr)
File "aniso8601/time.py", line 110, in parse_time
return _parse_time_naive(timestr)
File "aniso8601/time.py", line 140, in _parse_time_naive
return _RESOLUTION_MAP[get_time_resolution(timestr)](timestr)
File "aniso8601/time.py", line 214, in _parse_second_time
raise LeapSecondError('Leap seconds are not supported.')
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported.
Parsing dates
-------------
To parse a date represented in an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_date('1984-04-23')
datetime.date(1984, 4, 23)
Basic format is supported as well::
>>> aniso8601.parse_date('19840423')
datetime.date(1984, 4, 23)
To parse a date using the ISO 8601 week date format::
>>> aniso8601.parse_date('1986-W38-1')
datetime.date(1986, 9, 15)
To parse an ISO 8601 ordinal date::
>>> aniso8601.parse_date('1988-132')
datetime.date(1988, 5, 11)
Parsing times
-------------
To parse a time formatted as an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_time('11:31:14')
datetime.time(11, 31, 14)
As with all of the above, basic format is supported::
>>> aniso8601.parse_time('113114')
datetime.time(11, 31, 14)
A UTC offset can be specified for times::
>>> aniso8601.parse_time('17:18:19-02:30')
datetime.time(17, 18, 19, tzinfo=-2:30:00 UTC)
>>> aniso8601.parse_time('171819Z')
datetime.time(17, 18, 19, tzinfo=+0:00:00 UTC)
Reduced accuracy is supported::
>>> aniso8601.parse_time('21:42')
datetime.time(21, 42)
>>> aniso8601.parse_time('22')
datetime.time(22, 0)
A decimal fraction is always allowed on the lowest order element of an ISO 8601 formatted time::
>>> aniso8601.parse_time('22:33.5')
datetime.time(22, 33, 30)
>>> aniso8601.parse_time('23.75')
datetime.time(23, 45)
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`::
>>> aniso8601.parse_time('23:59:60')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/time.py", line 110, in parse_time
return _parse_time_naive(timestr)
File "aniso8601/time.py", line 140, in _parse_time_naive
return _RESOLUTION_MAP[get_time_resolution(timestr)](timestr)
File "aniso8601/time.py", line 214, in _parse_second_time
raise LeapSecondError('Leap seconds are not supported.')
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported.
Parsing durations
-----------------
To parse a duration formatted as an ISO 8601 string::
>>> import aniso8601
>>> aniso8601.parse_duration('P1Y2M3DT4H54M6S')
datetime.timedelta(428, 17646)
Reduced accuracy is supported::
>>> aniso8601.parse_duration('P1Y')
datetime.timedelta(365)
A decimal fraction is allowed on the lowest order element::
>>> aniso8601.parse_duration('P1YT3.5M')
datetime.timedelta(365, 210)
The decimal fraction can be specified with a comma instead of a full-stop::
>>> aniso8601.parse_duration('P1YT3,5M')
datetime.timedelta(365, 210)
Parsing a duration from a combined date and time is supported as well::
>>> aniso8601.parse_duration('P0001-01-02T01:30:5')
datetime.timedelta(397, 5405)
The above treat years as 365 days and months as 30 days. If calendar level accuracy is required, the relative keyword argument can be used if `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_ is installed::
>>> import aniso8601
>>> from datetime import date
>>> one_month = aniso8601.parse_duration('P1M', relative=True)
>>> print one_month
relativedelta(months=+1)
>>> date(2003,1,27) + one_month
datetime.date(2003, 2, 27)
>>> date(2003,1,31) + one_month
datetime.date(2003, 2, 28)
>>> date(2003,1,31) + two_months
datetime.date(2003, 3, 31)
Since a relative fractional month or year is not logical, a :code:`RelativeValueError` is raised when attempting to parse a duration with :code:`relative=True` and fractional month or year::
>>> aniso8601.parse_duration('P2.1Y', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 73, in _parse_duration_prescribed
raise RelativeValueError('Fractional months and years are not defined for relative intervals.')
aniso8601.exceptions.RelativeValueError: Fractional months and years are not defined for relative intervals.
If :code:`relative=True` is set without python-dateutil available, a :code:`RuntimeError` is raised::
>>> aniso8601.parse_duration('P1M', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 77, in _parse_duration_prescribed
raise RuntimeError('dateutil must be installed for relative duration support.')
RuntimeError: dateutil must be installed for relative duration support
Parsing intervals
-----------------
To parse an interval specified by a start and end::
>>> import aniso8601
>>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00')
(datetime.datetime(2007, 3, 1, 13, 0), datetime.datetime(2008, 5, 11, 15, 30))
Intervals specified by a start time and a duration are supported::
>>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M')
(datetime.datetime(2007, 3, 1, 13, 0, tzinfo=+0:00:00 UTC), datetime.datetime(2008, 5, 9, 15, 30, tzinfo=+0:00:00 UTC))
A duration can also be specified by a duration and end time::
>>> aniso8601.parse_interval('P1M/1981-04-05')
(datetime.date(1981, 4, 5), datetime.date(1981, 3, 6))
Notice that the result of the above parse is not in order from earliest to latest. If sorted intervals are required, simply use the :code:`sorted` keyword as shown below::
>>> sorted(aniso8601.parse_interval('P1M/1981-04-05'))
[datetime.date(1981, 3, 6), datetime.date(1981, 4, 5)]
The end of an interval is given as a datetime when required to maintain the resolution specified by a duration, even if the duration start is given as a date::
>>> aniso8601.parse_interval('2014-11-12/PT4H54M6.5S')
(datetime.date(2014, 11, 12), datetime.datetime(2014, 11, 12, 4, 54, 6, 500000))
Repeating intervals are supported as well, and return a generator::
>>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D')
<generator object date_generator at 0x7f698cdefc80>
>>> list(aniso8601.parse_repeating_interval('R3/1981-04-05/P1D'))
[datetime.date(1981, 4, 5), datetime.date(1981, 4, 6), datetime.date(1981, 4, 7)]
Repeating intervals are allowed to go in the reverse direction::
>>> list(aniso8601.parse_repeating_interval('R2/PT1H2M/1980-03-05T01:01:00'))
[datetime.datetime(1980, 3, 5, 1, 1), datetime.datetime(1980, 3, 4, 23, 59)]
Unbounded intervals are also allowed (Python 2)::
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')
>>> result.next()
datetime.datetime(1980, 3, 5, 1, 1)
>>> result.next()
datetime.datetime(1980, 3, 4, 23, 59)
or for Python 3::
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')
>>> next(result)
datetime.datetime(1980, 3, 5, 1, 1)
>>> next(result)
datetime.datetime(1980, 3, 4, 23, 59)
Note that you should never try to convert a generator produced by an unbounded interval to a list::
>>> list(aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 161, in _date_generator_unbounded
currentdate += timedelta
OverflowError: date value out of range
The above treat years as 365 days and months as 30 days. If calendar level accuracy is required, the relative keyword argument can be used if `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_ is installed::
>>> aniso8601.parse_interval('2003-01-27/P1M', relative=True)
(datetime.date(2003, 1, 27), datetime.date(2003, 2, 27))
>>> aniso8601.parse_interval('2003-01-31/P1M', relative=True)
(datetime.date(2003, 1, 31), datetime.date(2003, 2, 28))
>>> aniso8601.parse_interval('P1Y/2001-02-28', relative=True)
(datetime.date(2001, 2, 28), datetime.date(2000, 2, 28)
Fractional years and months do not make sense for relative intervals. A :code:`RelativeValueError` is raised when attempting to parse an interval with :code:`relative=True` and a fractional month or year::
>>> aniso8601.parse_interval('P1.1Y/2001-02-28', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 37, in parse_interval
interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative)
File "aniso8601/interval.py", line 89, in _parse_interval_parts
duration = parse_duration(firstpart, relative=relative)
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 73, in _parse_duration_prescribed
raise RelativeValueError('Fractional months and years are not defined for relative intervals.')
aniso8601.exceptions.RelativeValueError: Fractional months and years are not defined for relative intervals.
If :code:`relative=True` is set without python-dateutil available, a :code:`RuntimeError` is raised::
>>> aniso8601.parse_interval('2003-01-27/P1M', relative=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "aniso8601/interval.py", line 37, in parse_interval
interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative)
File "aniso8601/interval.py", line 108, in _parse_interval_parts
duration = parse_duration(secondpart, relative=relative)
File "aniso8601/duration.py", line 29, in parse_duration
return _parse_duration_prescribed(isodurationstr, relative)
File "aniso8601/duration.py", line 77, in _parse_duration_prescribed
raise RuntimeError('dateutil must be installed for relative duration support.')
RuntimeError: dateutil must be installed for relative duration support.
Date and time resolution
------------------------
In some situations, it may be useful to figure out the resolution provided by an ISO 8601 date or time string. Two functions are provided for this purpose.
To get the resolution of a ISO 8601 time string::
>>> aniso8601.get_time_resolution('11:31:14') == aniso8601.resolution.TimeResolution.Seconds
True
>>> aniso8601.get_time_resolution('11:31') == aniso8601.resolution.TimeResolution.Minutes
True
>>> aniso8601.get_time_resolution('11') == aniso8601.resolution.TimeResolution.Hours
True
Similarly, for an ISO 8601 date string::
>>> aniso8601.get_date_resolution('1981-04-05') == aniso8601.resolution.DateResolution.Day
True
>>> aniso8601.get_date_resolution('1981-04') == aniso8601.resolution.DateResolution.Month
True
>>> aniso8601.get_date_resolution('1981') == aniso8601.resolution.DateResolution.Year
True
Development
===========
Setup
-----
It is recommended to develop using a `virtualenv <https://virtualenv.pypa.io/en/stable/>`_.
The tests require the :code:`relative` feature to be enabled, install the necessary dependencies using pip::
$ pip install .[relative]
Tests
-----
To run the unit tests, navigate to the source directory and run the tests for the python version being worked on (python2, python3)::
$ python2 -m unittest discover aniso8601/tests/
or::
$ python3 -m unittest discover aniso8601/tests/
Contributing
============
aniso8601 is an open source project hosted on `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_.
Any and all bugs are welcome on our `issue tracker <https://bitbucket.org/nielsenb/aniso8601/issues>`_.
Of particular interest are valid ISO 8601 strings that don't parse, or invalid ones that do. At a minimum,
bug reports should include an example of the misbehaving string, as well as the expected result. Of course
patches containing unit tests (or fixed bugs) are welcome!
References
==========
* `ISO 8601:2004(E) <http://dotat.at/tmp/ISO_8601-2004_E.pdf>`_ (Caution, PDF link)
* `Wikipedia article on ISO 8601 <http://en.wikipedia.org/wiki/Iso8601>`_
* `Discussion on alternative ISO 8601 parsers for Python <https://groups.google.com/forum/#!topic/comp.lang.python/Q2w4R89Nq1w>`_

View File

@ -0,0 +1,25 @@
aniso8601/__init__.py,sha256=r15awjHgn8H6loHfYB4mJgGeiSTiJpIvIpVK4gXCzzk,527
aniso8601/compat.py,sha256=TEGb7_3qpbKtAfu7FfSkf0tcc9lquiDZ-Yt4uQCw45o,305
aniso8601/date.py,sha256=fmY0t6ESI6dooFOI2g57RFgIPrmxnyEI8DznuW7QrmE,8405
aniso8601/duration.py,sha256=qwrK0nS-Zo9hVGZzmhL0aQXOuTv_mBNmO74u8PbC5RE,10726
aniso8601/exceptions.py,sha256=N94MSQSnyctkjFXRlg4T91qQpYYSSrXuGCc1V1-knUI,1210
aniso8601/interval.py,sha256=VPgu4po6j88CtRy-0RNPBX_BffyiN-mAFvpdyesCtT4,6447
aniso8601/resolution.py,sha256=JklhrwxP807fpZZhLvQrDy40lzIz5OctsMK7m1yNSk8,422
aniso8601/time.py,sha256=MG61Oc4cyCBYklNPIkO14PCod4F8FyMe9N0X8rme2Rs,8128
aniso8601/timezone.py,sha256=9A-ah5xFapOkxOJFEPvmF-_WYnn2zfS5ZrHqQdC5K9M,3376
aniso8601-3.0.2.dist-info/DESCRIPTION.rst,sha256=IRO_7zo9wM-GXLQbkggGyZoIVr3ddwLG627qf5-Wl90,15070
aniso8601-3.0.2.dist-info/METADATA,sha256=DaU0fNs-ghRC1q6nKwF1cXiQieIyHNh-oZhNv0vRGrc,16259
aniso8601-3.0.2.dist-info/RECORD,,
aniso8601-3.0.2.dist-info/WHEEL,sha256=5wvfB7GvgZAbKBSE9uX9Zbi6LCL-_KgezgHblXhCRnM,113
aniso8601-3.0.2.dist-info/metadata.json,sha256=loCVI9Gvc7WILza0eIH-iQITWlL8rDxjWSM2Z9K6ZLU,1135
aniso8601-3.0.2.dist-info/top_level.txt,sha256=MVQomyeED8nGIH7PUQdMzxgLppIB48oYHtcmL17ETB0,10
aniso8601-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
aniso8601/__pycache__/compat.cpython-36.pyc,,
aniso8601/__pycache__/date.cpython-36.pyc,,
aniso8601/__pycache__/duration.cpython-36.pyc,,
aniso8601/__pycache__/exceptions.cpython-36.pyc,,
aniso8601/__pycache__/interval.cpython-36.pyc,,
aniso8601/__pycache__/resolution.cpython-36.pyc,,
aniso8601/__pycache__/time.cpython-36.pyc,,
aniso8601/__pycache__/timezone.cpython-36.pyc,,
aniso8601/__pycache__/__init__.cpython-36.pyc,,

View File

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0.a0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -0,0 +1 @@
{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Libraries :: Python Modules"], "extensions": {"python.details": {"contacts": [{"email": "nielsenb@jetfuse.net", "name": "Brandon Nielsen", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://bitbucket.org/nielsenb/aniso8601"}}}, "extras": ["relative"], "generator": "bdist_wheel (0.30.0.a0)", "keywords": ["iso8601", "parser"], "metadata_version": "2.0", "name": "aniso8601", "project_url": "Source, https://bitbucket.org/nielsenb/aniso8601", "run_requires": [{"extra": "relative", "requires": ["python-dateutil"]}], "summary": "A library for parsing ISO 8601 strings.", "version": "3.0.2"}

View File

@ -0,0 +1 @@
aniso8601

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
#Import the main parsing functions so they are readily available
from aniso8601.time import parse_datetime, parse_time, get_time_resolution
from aniso8601.date import parse_date, get_date_resolution
from aniso8601.duration import parse_duration
from aniso8601.interval import parse_interval, parse_repeating_interval

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
import sys
PY2 = sys.version_info[0] == 2
if PY2:
range = xrange
else:
range = range

View File

@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
import datetime
from aniso8601.exceptions import DayOutOfBoundsError, ISOFormatError, \
WeekOutOfBoundsError, YearOutOfBoundsError
from aniso8601.resolution import DateResolution
def get_date_resolution(isodatestr):
#Valid string formats are:
#
#Y[YYY]
#YYYY-MM-DD
#YYYYMMDD
#YYYY-MM
#YYYY-Www
#YYYYWww
#YYYY-Www-D
#YYYYWwwD
#YYYY-DDD
#YYYYDDD
if isodatestr.startswith('+') or isodatestr.startswith('-'):
raise NotImplementedError('ISO 8601 extended year representation not supported.')
if isodatestr.find('W') != -1:
#Handle ISO 8601 week date format
hyphens_present = 1 if isodatestr.find('-') != -1 else 0
week_date_len = 7 + hyphens_present
weekday_date_len = 8 + 2 * hyphens_present
if len(isodatestr) == week_date_len:
#YYYY-Www
#YYYYWww
return DateResolution.Week
elif len(isodatestr) == weekday_date_len:
#YYYY-Www-D
#YYYYWwwD
return DateResolution.Weekday
else:
raise ISOFormatError('"{0}" is not a valid ISO 8601 week date.'.format(isodatestr))
#If the size of the string of 4 or less, assume its a truncated year representation
if len(isodatestr) <= 4:
return DateResolution.Year
#An ISO string may be a calendar represntation if:
# 1) When split on a hyphen, the sizes of the parts are 4, 2, 2 or 4, 2
# 2) There are no hyphens, and the length is 8
datestrsplit = isodatestr.split('-')
#Check case 1
if len(datestrsplit) == 2:
if len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 2:
return DateResolution.Month
if len(datestrsplit) == 3:
if len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 2 and len(datestrsplit[2]) == 2:
return DateResolution.Day
#Check case 2
if len(isodatestr) == 8 and isodatestr.find('-') == -1:
return DateResolution.Day
#An ISO string may be a ordinal date representation if:
# 1) When split on a hyphen, the sizes of the parts are 4, 3
# 2) There are no hyphens, and the length is 7
#Check case 1
if len(datestrsplit) == 2:
if len(datestrsplit[0]) == 4 and len(datestrsplit[1]) == 3:
return DateResolution.Ordinal
#Check case 2
if len(isodatestr) == 7 and isodatestr.find('-') == -1:
return DateResolution.Ordinal
#None of the date representations match
raise ISOFormatError('"{0}" is not an ISO 8601 date, perhaps it represents a time or datetime.'.format(isodatestr))
def parse_date(isodatestr):
#Given a string in any ISO 8601 date format, return a datetime.date
#object that corresponds to the given date. Valid string formats are:
#
#Y[YYY]
#YYYY-MM-DD
#YYYYMMDD
#YYYY-MM
#YYYY-Www
#YYYYWww
#YYYY-Www-D
#YYYYWwwD
#YYYY-DDD
#YYYYDDD
#
#Note that the ISO 8601 date format of ±YYYYY is expressly not supported
return _RESOLUTION_MAP[get_date_resolution(isodatestr)](isodatestr)
def _parse_year(yearstr):
#yearstr is of the format Y[YYY]
#
#0000 (1 BC) is not representible as a Python date so a ValueError is
#raised
#
#Truncated dates, like '19', refer to 1900-1999 inclusive, we simply parse
#to 1900-01-01
#
#Since no additional resolution is provided, the month is set to 1, and
#day is set to 1
if len(yearstr) == 4:
isoyear = int(yearstr)
else:
#Shift 0s in from the left to form complete year
isoyear = int(yearstr.ljust(4, '0'))
if isoyear == 0:
raise YearOutOfBoundsError('Year must be between 1..9999.')
return datetime.date(isoyear, 1, 1)
def _parse_calendar_day(datestr):
#datestr is of the format YYYY-MM-DD or YYYYMMDD
if len(datestr) == 10:
#YYYY-MM-DD
strformat = '%Y-%m-%d'
elif len(datestr) == 8:
#YYYYMMDD
strformat = '%Y%m%d'
else:
raise ISOFormatError('"{0}" is not a valid ISO 8601 calendar day.'.format(datestr))
parseddatetime = datetime.datetime.strptime(datestr, strformat)
#Since no 'time' is given, cast to a date
return parseddatetime.date()
def _parse_calendar_month(datestr):
#datestr is of the format YYYY-MM
if len(datestr) != 7:
raise ISOFormatError('"{0}" is not a valid ISO 8601 calendar month.'.format(datestr))
parseddatetime = datetime.datetime.strptime(datestr, '%Y-%m')
#Since no 'time' is given, cast to a date
return parseddatetime.date()
def _parse_week_day(datestr):
#datestr is of the format YYYY-Www-D, YYYYWwwD
#
#W is the week number prefix, ww is the week number, between 1 and 53
#0 is not a valid week number, which differs from the Python implementation
#
#D is the weekday number, between 1 and 7, which differs from the Python
#implementation which is between 0 and 6
isoyear = int(datestr[0:4])
gregorianyearstart = _iso_year_start(isoyear)
#Week number will be the two characters after the W
windex = datestr.find('W')
isoweeknumber = int(datestr[windex + 1:windex + 3])
if isoweeknumber == 0 or isoweeknumber > 53:
raise WeekOutOfBoundsError('Week number must be between 1..53.')
if datestr.find('-') != -1 and len(datestr) == 10:
#YYYY-Www-D
isoday = int(datestr[9:10])
elif len(datestr) == 8:
#YYYYWwwD
isoday = int(datestr[7:8])
else:
raise ISOFormatError('"{0}" is not a valid ISO 8601 week date.'.format(datestr))
if isoday == 0 or isoday > 7:
raise DayOutOfBoundsError('Weekday number must be between 1..7.')
return gregorianyearstart + datetime.timedelta(weeks=isoweeknumber - 1, days=isoday - 1)
def _parse_week(datestr):
#datestr is of the format YYYY-Www, YYYYWww
#
#W is the week number prefix, ww is the week number, between 1 and 53
#0 is not a valid week number, which differs from the Python implementation
isoyear = int(datestr[0:4])
gregorianyearstart = _iso_year_start(isoyear)
#Week number will be the two characters after the W
windex = datestr.find('W')
isoweeknumber = int(datestr[windex + 1:windex + 3])
if isoweeknumber == 0 or isoweeknumber > 53:
raise WeekOutOfBoundsError('Week number must be between 1..53.')
return gregorianyearstart + datetime.timedelta(weeks=isoweeknumber - 1, days=0)
def _parse_ordinal_date(datestr):
#datestr is of the format YYYY-DDD or YYYYDDD
#DDD can be from 1 - 36[5,6], this matches Python's definition
isoyear = int(datestr[0:4])
if datestr.find('-') != -1:
#YYYY-DDD
isoday = int(datestr[(datestr.find('-') + 1):])
else:
#YYYYDDD
isoday = int(datestr[4:])
parseddate = datetime.date(isoyear, 1, 1) + datetime.timedelta(days=isoday - 1)
#Enforce ordinal day limitation
#https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow
if isoday == 0 or parseddate.year != isoyear:
raise DayOutOfBoundsError('Day of year must be from 1..365, 1..366 for leap year.')
return parseddate
def _iso_year_start(isoyear):
#Given an ISO year, returns the equivalent of the start of the year
#on the Gregorian calendar (which is used by Python)
#Stolen from:
#http://stackoverflow.com/questions/304256/whats-the-best-way-to-find-the-inverse-of-datetime-isocalendar
#Determine the location of the 4th of January, the first week of
#the ISO year is the week containing the 4th of January
#http://en.wikipedia.org/wiki/ISO_week_date
fourth_jan = datetime.date(isoyear, 1, 4)
#Note the conversion from ISO day (1 - 7) and Python day (0 - 6)
delta = datetime.timedelta(fourth_jan.isoweekday() - 1)
#Return the start of the year
return fourth_jan - delta
_RESOLUTION_MAP = {
DateResolution.Day: _parse_calendar_day,
DateResolution.Ordinal: _parse_ordinal_date,
DateResolution.Month: _parse_calendar_month,
DateResolution.Week: _parse_week,
DateResolution.Weekday: _parse_week_day,
DateResolution.Year: _parse_year
}

View File

@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
import datetime
from aniso8601.date import parse_date
from aniso8601.exceptions import ISOFormatError, RelativeValueError
from aniso8601.time import parse_time
from aniso8601 import compat
def parse_duration(isodurationstr, relative=False):
#Given a string representing an ISO 8601 duration, return a
#datetime.timedelta (or dateutil.relativedelta.relativedelta
#if relative=True) that matches the given duration. Valid formats are:
#
#PnYnMnDTnHnMnS (or any reduced precision equivalent)
#P<date>T<time>
if isodurationstr[0] != 'P':
raise ISOFormatError('ISO 8601 duration must start with a P.')
#If Y, M, D, H, S, or W are in the string, assume it is a specified duration
if _has_any_component(isodurationstr, ['Y', 'M', 'D', 'H', 'S', 'W']) is True:
return _parse_duration_prescribed(isodurationstr, relative)
return _parse_duration_combined(isodurationstr, relative)
def _parse_duration_prescribed(durationstr, relative):
#durationstr can be of the form PnYnMnDTnHnMnS or PnW
#Make sure the end character is valid
#https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed
if durationstr[-1] not in ['Y', 'M', 'D', 'H', 'S', 'W']:
raise ISOFormatError('ISO 8601 duration must end with a valid character.')
#Make sure only the lowest order element has decimal precision
if durationstr.count('.') > 1:
raise ISOFormatError('ISO 8601 allows only lowest order element to have a decimal fraction.')
elif durationstr.count('.') == 1:
#There should only ever be 1 letter after a decimal if there is more
#then one, the string is invalid
lettercount = 0
for character in durationstr.split('.')[1]:
if character.isalpha() is True:
lettercount += 1
if lettercount > 1:
raise ISOFormatError('ISO 8601 duration must end with a single valid character.')
#Do not allow W in combination with other designators
#https://bitbucket.org/nielsenb/aniso8601/issues/2/week-designators-should-not-be-combinable
if durationstr.find('W') != -1 and _has_any_component(durationstr, ['Y', 'M', 'D', 'H', 'S']) is True:
raise ISOFormatError('ISO 8601 week designators may not be combined with other time designators.')
#Parse the elements of the duration
if durationstr.find('T') == -1:
years, months, weeks, days, hours, minutes, seconds = _parse_duration_prescribed_notime(durationstr)
else:
years, months, weeks, days, hours, minutes, seconds = _parse_duration_prescribed_time(durationstr)
if relative is True:
try:
import dateutil.relativedelta
if int(years) != years or int(months) != months:
#https://github.com/dateutil/dateutil/issues/40
raise RelativeValueError('Fractional months and years are not defined for relative intervals.')
return dateutil.relativedelta.relativedelta(years=int(years), months=int(months), weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds)
except ImportError:
raise RuntimeError('dateutil must be installed for relative duration support.')
#Note that weeks can be handled without conversion to days
totaldays = years * 365 + months * 30 + days
return datetime.timedelta(weeks=weeks, days=totaldays, hours=hours, minutes=minutes, seconds=seconds)
def _parse_duration_prescribed_notime(durationstr):
#durationstr can be of the form PnYnMnD or PnW
#Make sure no time portion is included
#https://bitbucket.org/nielsenb/aniso8601/issues/7/durations-with-time-components-before-t
if _has_any_component(durationstr, ['H', 'S']):
raise ISOFormatError('ISO 8601 time components not allowed in duration without prescribed time.')
if _component_order_correct(durationstr, ['P', 'Y', 'M', 'D', 'W']) is False:
raise ISOFormatError('ISO 8601 duration components must be in the correct order.')
if durationstr.find('Y') != -1:
years = _parse_duration_element(durationstr, 'Y')
else:
years = 0
if durationstr.find('M') != -1:
months = _parse_duration_element(durationstr, 'M')
else:
months = 0
if durationstr.find('W') != -1:
weeks = _parse_duration_element(durationstr, 'W')
else:
weeks = 0
if durationstr.find('D') != -1:
days = _parse_duration_element(durationstr, 'D')
else:
days = 0
#No hours, minutes or seconds
hours = 0
minutes = 0
seconds = 0
return (years, months, weeks, days, hours, minutes, seconds)
def _parse_duration_prescribed_time(durationstr):
#durationstr can be of the form PnYnMnDTnHnMnS
firsthalf = durationstr[:durationstr.find('T')]
secondhalf = durationstr[durationstr.find('T'):]
#Make sure no time portion is included in the date half
#https://bitbucket.org/nielsenb/aniso8601/issues/7/durations-with-time-components-before-t
if _has_any_component(firsthalf, ['H', 'S']):
raise ISOFormatError('ISO 8601 time components not allowed in date portion of duration.')
if _component_order_correct(firsthalf, ['P', 'Y', 'M', 'D', 'W']) is False:
raise ISOFormatError('ISO 8601 duration components must be in the correct order.')
#Make sure no date component is included in the time half
if _has_any_component(secondhalf, ['Y', 'D']):
raise ISOFormatError('ISO 8601 time components not allowed in date portion of duration.')
if _component_order_correct(secondhalf, ['T', 'H', 'M', 'S']) is False:
raise ISOFormatError('ISO 8601 time components in duration must be in the correct order.')
if firsthalf.find('Y') != -1:
years = _parse_duration_element(firsthalf, 'Y')
else:
years = 0
if firsthalf.find('M') != -1:
months = _parse_duration_element(firsthalf, 'M')
else:
months = 0
if firsthalf.find('D') != -1:
days = _parse_duration_element(firsthalf, 'D')
else:
days = 0
if secondhalf.find('H') != -1:
hours = _parse_duration_element(secondhalf, 'H')
else:
hours = 0
if secondhalf.find('M') != -1:
minutes = _parse_duration_element(secondhalf, 'M')
else:
minutes = 0
if secondhalf.find('S') != -1:
seconds = _parse_duration_element(secondhalf, 'S')
else:
seconds = 0
#Weeks can't be included
weeks = 0
return (years, months, weeks, days, hours, minutes, seconds)
def _parse_duration_combined(durationstr, relative):
#Period of the form P<date>T<time>
#Split the string in to its component parts
datepart, timepart = durationstr[1:].split('T') #We skip the 'P'
datevalue = parse_date(datepart)
timevalue = parse_time(timepart)
if relative is True:
try:
import dateutil.relativedelta
return dateutil.relativedelta.relativedelta(years=datevalue.year, months=datevalue.month, days=datevalue.day, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond)
except ImportError:
raise RuntimeError('dateutil must be installed for relative duration support.')
else:
totaldays = datevalue.year * 365 + datevalue.month * 30 + datevalue.day
return datetime.timedelta(days=totaldays, hours=timevalue.hour, minutes=timevalue.minute, seconds=timevalue.second, microseconds=timevalue.microsecond)
def _parse_duration_element(durationstr, elementstr):
#Extracts the specified portion of a duration, for instance, given:
#durationstr = 'T4H5M6.1234S'
#elementstr = 'H'
#
#returns 4
#
#Note that the string must start with a character, so its assumed the
#full duration string would be split at the 'T'
durationstartindex = 0
durationendindex = durationstr.find(elementstr)
for characterindex in compat.range(durationendindex - 1, 0, -1):
if durationstr[characterindex].isalpha() is True:
durationstartindex = characterindex
break
durationstartindex += 1
if ',' in durationstr:
#Replace the comma with a 'full-stop'
durationstr = durationstr.replace(',', '.')
if elementstr == 'S':
#We truncate seconds to avoid precision issues with microseconds
#https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is
if '.' in durationstr[durationstartindex:durationendindex]:
stopindex = durationstr.index('.')
if durationendindex - stopindex > 7:
durationendindex = stopindex + 7
return float(durationstr[durationstartindex:durationendindex])
def _has_any_component(durationstr, components):
#Given a duration string, and a list of components, returns True
#if any of the listed components are present, False otherwise.
#
#For instance:
#durationstr = 'P1Y'
#components = ['Y', 'M']
#
#returns True
#
#durationstr = 'P1Y'
#components = ['M', 'D']
#
#returns False
for component in components:
if durationstr.find(component) != -1:
return True
return False
def _component_order_correct(durationstr, componentorder):
#Given a duration string, and a list of components, returns
#True if the components are in the same order as the
#component order list, False otherwise. Characters that
#are present in the component order list but not in the
#duration string are ignored.
#
#https://bitbucket.org/nielsenb/aniso8601/issues/8/durations-with-components-in-wrong-order
#
#durationstr = 'P1Y1M1D'
#components = ['P', 'Y', 'M', 'D']
#
#returns True
#
#durationstr = 'P1Y1M'
#components = ['P', 'Y', 'M', 'D']
#
#returns True
#
#durationstr = 'P1D1Y1M'
#components = ['P', 'Y', 'M', 'D']
#
#returns False
componentindex = 0
for characterindex in compat.range(len(durationstr)):
character = durationstr[characterindex]
if character in componentorder:
#This is a character we need to check the order of
if character in componentorder[componentindex:]:
componentindex = componentorder.index(character)
else:
#A character is out of order
return False
return True

View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
class ISOFormatError(ValueError):
"""Raised when ISO 8601 string fails a format check."""
class RelativeValueError(ValueError):
"""Raised when an invalid value is given for calendar level accuracy."""
class YearOutOfBoundsError(ValueError):
"""Raised when year exceeds limits."""
class WeekOutOfBoundsError(ValueError):
"""Raised when week exceeds a year."""
class DayOutOfBoundsError(ValueError):
"""Raised when day is outside of 1..365, 1..366 for leap year."""
class HoursOutOfBoundsError(ValueError):
"""Raise when parsed hours are greater than 24."""
class MinutesOutOfBoundsError(ValueError):
"""Raise when parsed seconds are greater than 60."""
class SecondsOutOfBoundsError(ValueError):
"""Raise when parsed seconds are greater than 60."""
class MidnightBoundsError(ValueError):
"""Raise when parsed time has an hour of 24 but is not midnight."""
class LeapSecondError(NotImplementedError):
"""Raised when attempting to parse a leap second"""

View File

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
from datetime import datetime
from aniso8601.duration import parse_duration
from aniso8601.exceptions import ISOFormatError
from aniso8601.time import parse_datetime
from aniso8601.date import parse_date
def parse_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False):
#Given a string representing an ISO 8601 interval, return a
#tuple of datetime.date or date.datetime objects representing the beginning
#and end of the specified interval. Valid formats are:
#
#<start>/<end>
#<start>/<duration>
#<duration>/<end>
#
#The <start> and <end> values can represent dates, or datetimes,
#not times.
#
#The format:
#
#<duration>
#
#Is expressly not supported as there is no way to provide the addtional
#required context.
if isointervalstr[0] == 'R':
raise ISOFormatError('ISO 8601 repeating intervals must be parsed with parse_repeating_interval.')
interval_parts = _parse_interval_parts(isointervalstr, intervaldelimiter, datetimedelimiter, relative)
return (interval_parts[0], interval_parts[1])
def parse_repeating_interval(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False):
#Given a string representing an ISO 8601 interval repating, return a
#generator of datetime.date or date.datetime objects representing the
#dates specified by the repeating interval. Valid formats are:
#
#Rnn/<interval>
#R/<interval>
if isointervalstr[0] != 'R':
raise ISOFormatError('ISO 8601 repeating interval must start with an R.')
#Parse the number of iterations
iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1)
if len(iterationpart) > 1:
iterations = int(iterationpart[1:])
else:
iterations = None
interval_parts = _parse_interval_parts(intervalpart, intervaldelimiter, datetimedelimiter, relative=relative)
#Now, build and return the generator
if iterations is not None:
return _date_generator(interval_parts[0], interval_parts[2], iterations)
return _date_generator_unbounded(interval_parts[0], interval_parts[2])
def _parse_interval_parts(isointervalstr, intervaldelimiter='/', datetimedelimiter='T', relative=False):
#Returns a tuple containing the start of the interval, the end of the interval, and the interval timedelta
firstpart, secondpart = isointervalstr.split(intervaldelimiter)
if firstpart[0] == 'P':
#<duration>/<end>
#Notice that these are not returned 'in order' (earlier to later), this
#is to maintain consistency with parsing <start>/<end> durations, as
#well as making repeating interval code cleaner. Users who desire
#durations to be in order can use the 'sorted' operator.
#We need to figure out if <end> is a date, or a datetime
if secondpart.find(datetimedelimiter) != -1:
#<end> is a datetime
duration = parse_duration(firstpart, relative=relative)
enddatetime = parse_datetime(secondpart, delimiter=datetimedelimiter)
return (enddatetime, enddatetime - duration, -duration)
#<end> must just be a date
duration = parse_duration(firstpart, relative=relative)
enddate = parse_date(secondpart)
#See if we need to upconvert to datetime to preserve resolution
if firstpart.find(datetimedelimiter) != -1:
return (enddate, datetime.combine(enddate, datetime.min.time()) - duration, -duration)
return (enddate, enddate - duration, -duration)
elif secondpart[0] == 'P':
#<start>/<duration>
#We need to figure out if <start> is a date, or a datetime
if firstpart.find(datetimedelimiter) != -1:
#<start> is a datetime
duration = parse_duration(secondpart, relative=relative)
startdatetime = parse_datetime(firstpart, delimiter=datetimedelimiter)
return (startdatetime, startdatetime + duration, duration)
#<start> must just be a date
duration = parse_duration(secondpart, relative=relative)
startdate = parse_date(firstpart)
#See if we need to upconvert to datetime to preserve resolution
if secondpart.find(datetimedelimiter) != -1:
return (startdate, datetime.combine(startdate, datetime.min.time()) + duration, duration)
return (startdate, startdate + duration, duration)
#<start>/<end>
if firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) != -1:
#Both parts are datetimes
start_datetime = parse_datetime(firstpart, delimiter=datetimedelimiter)
end_datetime = parse_datetime(secondpart, delimiter=datetimedelimiter)
return (start_datetime, end_datetime, end_datetime - start_datetime)
elif firstpart.find(datetimedelimiter) != -1 and secondpart.find(datetimedelimiter) == -1:
#First part is a datetime, second part is a date
start_datetime = parse_datetime(firstpart, delimiter=datetimedelimiter)
end_date = parse_date(secondpart)
return (start_datetime, end_date, datetime.combine(end_date, datetime.min.time()) - start_datetime)
elif firstpart.find(datetimedelimiter) == -1 and secondpart.find(datetimedelimiter) != -1:
#First part is a date, second part is a datetime
start_date = parse_date(firstpart)
end_datetime = parse_datetime(secondpart, delimiter=datetimedelimiter)
return (start_date, end_datetime, end_datetime - datetime.combine(start_date, datetime.min.time()))
#Both parts are dates
start_date = parse_date(firstpart)
end_date = parse_date(secondpart)
return (start_date, end_date, end_date - start_date)
def _date_generator(startdate, timedelta, iterations):
currentdate = startdate
currentiteration = 0
while currentiteration < iterations:
yield currentdate
#Update the values
currentdate += timedelta
currentiteration += 1
def _date_generator_unbounded(startdate, timedelta):
currentdate = startdate
while True:
yield currentdate
#Update the value
currentdate += timedelta

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
from aniso8601 import compat
class DateResolution(object):
Year, Month, Week, Weekday, Day, Ordinal = list(compat.range(6))
class TimeResolution(object):
Seconds, Minutes, Hours = list(compat.range(3))

View File

@ -0,0 +1,257 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
import datetime
from aniso8601.date import parse_date
from aniso8601.exceptions import HoursOutOfBoundsError, ISOFormatError, \
LeapSecondError, MidnightBoundsError, MinutesOutOfBoundsError, \
SecondsOutOfBoundsError
from aniso8601.resolution import TimeResolution
from aniso8601.timezone import parse_timezone
def get_time_resolution(isotimestr):
#Valid time formats are:
#
#hh:mm:ss
#hhmmss
#hh:mm
#hhmm
#hh
#hh:mm:ssZ
#hhmmssZ
#hh:mmZ
#hhmmZ
#hhZ
#hh:mm:ss±hh:mm
#hhmmss±hh:mm
#hh:mm±hh:mm
#hhmm±hh:mm
#hh±hh:mm
#hh:mm:ss±hhmm
#hhmmss±hhmm
#hh:mm±hhmm
#hhmm±hhmm
#hh±hhmm
#hh:mm:ss±hh
#hhmmss±hh
#hh:mm±hh
#hhmm±hh
#hh±hh
timestr = _split_tz(isotimestr)[0]
if timestr.count(':') == 2:
#hh:mm:ss
return TimeResolution.Seconds
elif timestr.count(':') == 1:
#hh:mm
return TimeResolution.Minutes
#Format must be hhmmss, hhmm, or hh
if timestr.find('.') == -1:
#No time fractions
timestrlen = len(timestr)
else:
#The lowest order element is a fraction
timestrlen = len(timestr.split('.')[0])
if timestrlen == 6:
#hhmmss
return TimeResolution.Seconds
elif timestrlen == 4:
#hhmm
return TimeResolution.Minutes
elif timestrlen == 2:
#hh
return TimeResolution.Hours
raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
def parse_time(isotimestr):
#Given a string in any ISO 8601 time format, return a datetime.time object
#that corresponds to the given time. Fixed offset tzdata will be included
#if UTC offset is given in the input string. Valid time formats are:
#
#hh:mm:ss
#hhmmss
#hh:mm
#hhmm
#hh
#hh:mm:ssZ
#hhmmssZ
#hh:mmZ
#hhmmZ
#hhZ
#hh:mm:ss±hh:mm
#hhmmss±hh:mm
#hh:mm±hh:mm
#hhmm±hh:mm
#hh±hh:mm
#hh:mm:ss±hhmm
#hhmmss±hhmm
#hh:mm±hhmm
#hhmm±hhmm
#hh±hhmm
#hh:mm:ss±hh
#hhmmss±hh
#hh:mm±hh
#hhmm±hh
#hh±hh
(timestr, tzstr) = _split_tz(isotimestr)
if tzstr is None:
return _parse_time_naive(timestr)
else:
tzinfo = parse_timezone(tzstr)
return _parse_time_naive(timestr).replace(tzinfo=tzinfo)
def parse_datetime(isodatetimestr, delimiter='T'):
#Given a string in ISO 8601 date time format, return a datetime.datetime
#object that corresponds to the given date time.
#By default, the ISO 8601 specified T delimiter is used to split the
#date and time (<date>T<time>). Fixed offset tzdata will be included
#if UTC offset is given in the input string.
isodatestr, isotimestr = isodatetimestr.split(delimiter)
datepart = parse_date(isodatestr)
timepart = parse_time(isotimestr)
return datetime.datetime.combine(datepart, timepart)
def _parse_time_naive(timestr):
#timestr is of the format hh:mm:ss, hh:mm, hhmmss, hhmm, hh
#
#hh is between 0 and 24, 24 is not allowed in the Python time format, since
#it represents midnight, a time of 00:00:00 is returned
#
#mm is between 0 and 60, with 60 used to denote a leap second
#
#No tzinfo will be included
return _RESOLUTION_MAP[get_time_resolution(timestr)](timestr)
def _parse_hour(timestr):
#Format must be hh or hh.
isohour = float(timestr)
if isohour == 24:
return datetime.time(hour=0, minute=0)
elif isohour > 24:
raise HoursOutOfBoundsError('Hour must be between 0..24 with 24 representing midnight.')
#Since the time constructor doesn't handle fractional hours, we put
#the hours in to a timedelta, and add it to the time before returning
hoursdelta = datetime.timedelta(hours=isohour)
return _build_time(datetime.time(hour=0), hoursdelta)
def _parse_minute_time(timestr):
#Format must be hhmm, hhmm., hh:mm or hh:mm.
if timestr.count(':') == 1:
#hh:mm or hh:mm.
timestrarray = timestr.split(':')
isohour = int(timestrarray[0])
isominute = float(timestrarray[1]) #Minute may now be a fraction
else:
#hhmm or hhmm.
isohour = int(timestr[0:2])
isominute = float(timestr[2:])
if isominute >= 60:
raise MinutesOutOfBoundsError('Minutes must be less than 60.')
if isohour == 24:
if isominute != 0:
raise MidnightBoundsError('Hour 24 may only represent midnight.')
return datetime.time(hour=0, minute=0)
#Since the time constructor doesn't handle fractional minutes, we put
#the minutes in to a timedelta, and add it to the time before returning
minutesdelta = datetime.timedelta(minutes=isominute)
return _build_time(datetime.time(hour=isohour), minutesdelta)
def _parse_second_time(timestr):
#Format must be hhmmss, hhmmss., hh:mm:ss or hh:mm:ss.
if timestr.count(':') == 2:
#hh:mm:ss or hh:mm:ss.
timestrarray = timestr.split(':')
isohour = int(timestrarray[0])
isominute = int(timestrarray[1])
#Since the time constructor doesn't handle fractional seconds, we put
#the seconds in to a timedelta, and add it to the time before returning
#The seconds value is truncated to microsecond resolution before
#conversion:
#https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is
secondsdelta = datetime.timedelta(seconds=float(timestrarray[2][:9]))
else:
#hhmmss or hhmmss.
isohour = int(timestr[0:2])
isominute = int(timestr[2:4])
#Since the time constructor doesn't handle fractional seconds, we put
#the seconds in to a timedelta, and add it to the time before returning
#The seconds value is truncated to microsecond resolution before
#conversion:
#https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is
secondsdelta = datetime.timedelta(seconds=float(timestr[4:13]))
if isohour == 23 and isominute == 59 and secondsdelta.seconds == 60:
#https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is
raise LeapSecondError('Leap seconds are not supported.')
elif secondsdelta.seconds >= 60:
#https://bitbucket.org/nielsenb/aniso8601/issues/13/parsing-of-leap-second-gives-wildly
raise SecondsOutOfBoundsError('Seconds must be less than 60.')
if isominute >= 60:
raise MinutesOutOfBoundsError('Minutes must be less than 60.')
if isohour == 24:
#Midnight, see 4.2.1, 4.2.3
if isominute != 0 or secondsdelta.total_seconds() != 0:
raise MidnightBoundsError('Hour 24 may only represent midnight.')
return datetime.time(hour=0, minute=0)
return _build_time(datetime.time(hour=isohour, minute=isominute),
secondsdelta)
def _build_time(time, delta):
#Combine today's date (just so we have a date object), the time, the
#delta, and return the time component
base_datetime = datetime.datetime.combine(datetime.date.today(), time)
return (base_datetime + delta).time()
def _split_tz(isotimestr):
if isotimestr.find('+') != -1:
timestr = isotimestr[0:isotimestr.find('+')]
tzstr = isotimestr[isotimestr.find('+'):]
elif isotimestr.find('-') != -1:
timestr = isotimestr[0:isotimestr.find('-')]
tzstr = isotimestr[isotimestr.find('-'):]
elif isotimestr.endswith('Z'):
timestr = isotimestr[:-1]
tzstr = 'Z'
else:
timestr = isotimestr
tzstr = None
return (timestr, tzstr)
_RESOLUTION_MAP = {
TimeResolution.Hours: _parse_hour,
TimeResolution.Minutes: _parse_minute_time,
TimeResolution.Seconds: _parse_second_time
}

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
import datetime
from aniso8601.exceptions import ISOFormatError
def parse_timezone(tzstr):
#tzstr can be Z, ±hh:mm, ±hhmm, ±hh
if 'Z' in tzstr:
if len(tzstr) != 1:
raise ISOFormatError('"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))
#Z -> UTC
return UTCOffset(name='UTC', minutes=0)
elif len(tzstr) == 6:
#±hh:mm
tzhour = int(tzstr[1:3])
tzminute = int(tzstr[4:6])
elif len(tzstr) == 5:
#±hhmm
tzhour = int(tzstr[1:3])
tzminute = int(tzstr[3:5])
elif len(tzstr) == 3:
#±hh
tzhour = int(tzstr[1:3])
tzminute = 0
else:
raise ISOFormatError('"{0}" is not a valid ISO 8601 time offset.'.format(tzstr))
if tzstr[0] == '+':
return UTCOffset(name=tzstr, minutes=(tzhour * 60 + tzminute))
elif tzhour == 0 and tzminute == 0:
raise ISOFormatError('Negative ISO 8601 time offset must not be 0.')
return UTCOffset(name=tzstr, minutes=-(tzhour * 60 + tzminute))
class UTCOffset(datetime.tzinfo):
def __init__(self, name=None, minutes=None):
#We build an offset in this manner since the
#tzinfo class must have an init that can
#"method that can be called with no arguments"
self._name = name
if minutes is not None:
self._utcdelta = datetime.timedelta(minutes=minutes)
else:
self._utcdelta = None
def __repr__(self):
if self._utcdelta >= datetime.timedelta(hours=0):
return '+{0} UTC'.format(self._utcdelta)
#From the docs:
#String representations of timedelta objects are normalized
#similarly to their internal representation. This leads to
#somewhat unusual results for negative timedeltas.
#Clean this up for printing purposes
correcteddays = abs(self._utcdelta.days + 1) #Negative deltas start at -1 day
deltaseconds = (24 * 60 * 60) - self._utcdelta.seconds #Negative deltas have a positive seconds
days, remainder = divmod(deltaseconds, 24 * 60 * 60) #(24 hours / day) * (60 minutes / hour) * (60 seconds / hour)
hours, remainder = divmod(remainder, 1 * 60 * 60) #(1 hour) * (60 minutes / hour) * (60 seconds / hour)
minutes, seconds = divmod(remainder, 1 * 60) #(1 minute) * (60 seconds / minute)
#Add any remaining days to the correctedDays count
correcteddays += days
if correcteddays == 0:
return '-{0}:{1:02}:{2:02} UTC'.format(hours, minutes, seconds)
elif correcteddays == 1:
return '-1 day, {0}:{1:02}:{2:02} UTC'.format(hours, minutes, seconds)
return '-{0} days, {1}:{2:02}:{3:02} UTC'.format(correcteddays, hours, minutes, seconds)
def utcoffset(self, dt):
return self._utcdelta
def tzname(self, dt):
return self._name
def dst(self, dt):
#ISO 8601 specifies offsets should be different if DST is required,
#instead of allowing for a DST to be specified
# https://docs.python.org/2/library/datetime.html#datetime.tzinfo.dst
return datetime.timedelta(0)

View File

@ -0,0 +1,10 @@
from pkg_resources import get_distribution, DistributionNotFound
try:
release = get_distribution('APScheduler').version.split('-')[0]
except DistributionNotFound:
release = '3.5.0'
version_info = tuple(int(x) if x.isdigit() else x for x in release.split('.'))
version = __version__ = '.'.join(str(x) for x in version_info[:3])
del get_distribution, DistributionNotFound

View File

@ -0,0 +1,94 @@
__all__ = ('EVENT_SCHEDULER_STARTED', 'EVENT_SCHEDULER_SHUTDOWN', 'EVENT_SCHEDULER_PAUSED',
'EVENT_SCHEDULER_RESUMED', 'EVENT_EXECUTOR_ADDED', 'EVENT_EXECUTOR_REMOVED',
'EVENT_JOBSTORE_ADDED', 'EVENT_JOBSTORE_REMOVED', 'EVENT_ALL_JOBS_REMOVED',
'EVENT_JOB_ADDED', 'EVENT_JOB_REMOVED', 'EVENT_JOB_MODIFIED', 'EVENT_JOB_EXECUTED',
'EVENT_JOB_ERROR', 'EVENT_JOB_MISSED', 'EVENT_JOB_SUBMITTED', 'EVENT_JOB_MAX_INSTANCES',
'SchedulerEvent', 'JobEvent', 'JobExecutionEvent')
EVENT_SCHEDULER_STARTED = EVENT_SCHEDULER_START = 2 ** 0
EVENT_SCHEDULER_SHUTDOWN = 2 ** 1
EVENT_SCHEDULER_PAUSED = 2 ** 2
EVENT_SCHEDULER_RESUMED = 2 ** 3
EVENT_EXECUTOR_ADDED = 2 ** 4
EVENT_EXECUTOR_REMOVED = 2 ** 5
EVENT_JOBSTORE_ADDED = 2 ** 6
EVENT_JOBSTORE_REMOVED = 2 ** 7
EVENT_ALL_JOBS_REMOVED = 2 ** 8
EVENT_JOB_ADDED = 2 ** 9
EVENT_JOB_REMOVED = 2 ** 10
EVENT_JOB_MODIFIED = 2 ** 11
EVENT_JOB_EXECUTED = 2 ** 12
EVENT_JOB_ERROR = 2 ** 13
EVENT_JOB_MISSED = 2 ** 14
EVENT_JOB_SUBMITTED = 2 ** 15
EVENT_JOB_MAX_INSTANCES = 2 ** 16
EVENT_ALL = (EVENT_SCHEDULER_STARTED | EVENT_SCHEDULER_SHUTDOWN | EVENT_SCHEDULER_PAUSED |
EVENT_SCHEDULER_RESUMED | EVENT_EXECUTOR_ADDED | EVENT_EXECUTOR_REMOVED |
EVENT_JOBSTORE_ADDED | EVENT_JOBSTORE_REMOVED | EVENT_ALL_JOBS_REMOVED |
EVENT_JOB_ADDED | EVENT_JOB_REMOVED | EVENT_JOB_MODIFIED | EVENT_JOB_EXECUTED |
EVENT_JOB_ERROR | EVENT_JOB_MISSED | EVENT_JOB_SUBMITTED | EVENT_JOB_MAX_INSTANCES)
class SchedulerEvent(object):
"""
An event that concerns the scheduler itself.
:ivar code: the type code of this event
:ivar alias: alias of the job store or executor that was added or removed (if applicable)
"""
def __init__(self, code, alias=None):
super(SchedulerEvent, self).__init__()
self.code = code
self.alias = alias
def __repr__(self):
return '<%s (code=%d)>' % (self.__class__.__name__, self.code)
class JobEvent(SchedulerEvent):
"""
An event that concerns a job.
:ivar code: the type code of this event
:ivar job_id: identifier of the job in question
:ivar jobstore: alias of the job store containing the job in question
"""
def __init__(self, code, job_id, jobstore):
super(JobEvent, self).__init__(code)
self.code = code
self.job_id = job_id
self.jobstore = jobstore
class JobSubmissionEvent(JobEvent):
"""
An event that concerns the submission of a job to its executor.
:ivar scheduled_run_times: a list of datetimes when the job was intended to run
"""
def __init__(self, code, job_id, jobstore, scheduled_run_times):
super(JobSubmissionEvent, self).__init__(code, job_id, jobstore)
self.scheduled_run_times = scheduled_run_times
class JobExecutionEvent(JobEvent):
"""
An event that concerns the running of a job within its executor.
:ivar scheduled_run_time: the time when the job was scheduled to be run
:ivar retval: the return value of the successfully executed job
:ivar exception: the exception raised by the job
:ivar traceback: a formatted traceback for the exception
"""
def __init__(self, code, job_id, jobstore, scheduled_run_time, retval=None, exception=None,
traceback=None):
super(JobExecutionEvent, self).__init__(code, job_id, jobstore)
self.scheduled_run_time = scheduled_run_time
self.retval = retval
self.exception = exception
self.traceback = traceback

View File

@ -0,0 +1,60 @@
from __future__ import absolute_import
import sys
from apscheduler.executors.base import BaseExecutor, run_job
try:
from asyncio import iscoroutinefunction
from apscheduler.executors.base_py3 import run_coroutine_job
except ImportError:
from trollius import iscoroutinefunction
run_coroutine_job = None
class AsyncIOExecutor(BaseExecutor):
"""
Runs jobs in the default executor of the event loop.
If the job function is a native coroutine function, it is scheduled to be run directly in the
event loop as soon as possible. All other functions are run in the event loop's default
executor which is usually a thread pool.
Plugin alias: ``asyncio``
"""
def start(self, scheduler, alias):
super(AsyncIOExecutor, self).start(scheduler, alias)
self._eventloop = scheduler._eventloop
self._pending_futures = set()
def shutdown(self, wait=True):
# There is no way to honor wait=True without converting this method into a coroutine method
for f in self._pending_futures:
if not f.done():
f.cancel()
self._pending_futures.clear()
def _do_submit_job(self, job, run_times):
def callback(f):
self._pending_futures.discard(f)
try:
events = f.result()
except BaseException:
self._run_job_error(job.id, *sys.exc_info()[1:])
else:
self._run_job_success(job.id, events)
if iscoroutinefunction(job.func):
if run_coroutine_job is not None:
coro = run_coroutine_job(job, job._jobstore_alias, run_times, self._logger.name)
f = self._eventloop.create_task(coro)
else:
raise Exception('Executing coroutine based jobs is not supported with Trollius')
else:
f = self._eventloop.run_in_executor(None, run_job, job, job._jobstore_alias, run_times,
self._logger.name)
f.add_done_callback(callback)
self._pending_futures.add(f)

View File

@ -0,0 +1,146 @@
from abc import ABCMeta, abstractmethod
from collections import defaultdict
from datetime import datetime, timedelta
from traceback import format_tb
import logging
import sys
from pytz import utc
import six
from apscheduler.events import (
JobExecutionEvent, EVENT_JOB_MISSED, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED)
class MaxInstancesReachedError(Exception):
def __init__(self, job):
super(MaxInstancesReachedError, self).__init__(
'Job "%s" has already reached its maximum number of instances (%d)' %
(job.id, job.max_instances))
class BaseExecutor(six.with_metaclass(ABCMeta, object)):
"""Abstract base class that defines the interface that every executor must implement."""
_scheduler = None
_lock = None
_logger = logging.getLogger('apscheduler.executors')
def __init__(self):
super(BaseExecutor, self).__init__()
self._instances = defaultdict(lambda: 0)
def start(self, scheduler, alias):
"""
Called by the scheduler when the scheduler is being started or when the executor is being
added to an already running scheduler.
:param apscheduler.schedulers.base.BaseScheduler scheduler: the scheduler that is starting
this executor
:param str|unicode alias: alias of this executor as it was assigned to the scheduler
"""
self._scheduler = scheduler
self._lock = scheduler._create_lock()
self._logger = logging.getLogger('apscheduler.executors.%s' % alias)
def shutdown(self, wait=True):
"""
Shuts down this executor.
:param bool wait: ``True`` to wait until all submitted jobs
have been executed
"""
def submit_job(self, job, run_times):
"""
Submits job for execution.
:param Job job: job to execute
:param list[datetime] run_times: list of datetimes specifying
when the job should have been run
:raises MaxInstancesReachedError: if the maximum number of
allowed instances for this job has been reached
"""
assert self._lock is not None, 'This executor has not been started yet'
with self._lock:
if self._instances[job.id] >= job.max_instances:
raise MaxInstancesReachedError(job)
self._do_submit_job(job, run_times)
self._instances[job.id] += 1
@abstractmethod
def _do_submit_job(self, job, run_times):
"""Performs the actual task of scheduling `run_job` to be called."""
def _run_job_success(self, job_id, events):
"""
Called by the executor with the list of generated events when :func:`run_job` has been
successfully called.
"""
with self._lock:
self._instances[job_id] -= 1
if self._instances[job_id] == 0:
del self._instances[job_id]
for event in events:
self._scheduler._dispatch_event(event)
def _run_job_error(self, job_id, exc, traceback=None):
"""Called by the executor with the exception if there is an error calling `run_job`."""
with self._lock:
self._instances[job_id] -= 1
if self._instances[job_id] == 0:
del self._instances[job_id]
exc_info = (exc.__class__, exc, traceback)
self._logger.error('Error running job %s', job_id, exc_info=exc_info)
def run_job(job, jobstore_alias, run_times, logger_name):
"""
Called by executors to run the job. Returns a list of scheduler events to be dispatched by the
scheduler.
"""
events = []
logger = logging.getLogger(logger_name)
for run_time in run_times:
# See if the job missed its run time window, and handle
# possible misfires accordingly
if job.misfire_grace_time is not None:
difference = datetime.now(utc) - run_time
grace_time = timedelta(seconds=job.misfire_grace_time)
if difference > grace_time:
events.append(JobExecutionEvent(EVENT_JOB_MISSED, job.id, jobstore_alias,
run_time))
logger.warning('Run time of job "%s" was missed by %s', job, difference)
continue
logger.info('Running job "%s" (scheduled at %s)', job, run_time)
try:
retval = job.func(*job.args, **job.kwargs)
except BaseException:
exc, tb = sys.exc_info()[1:]
formatted_tb = ''.join(format_tb(tb))
events.append(JobExecutionEvent(EVENT_JOB_ERROR, job.id, jobstore_alias, run_time,
exception=exc, traceback=formatted_tb))
logger.exception('Job "%s" raised an exception', job)
# This is to prevent cyclic references that would lead to memory leaks
if six.PY2:
sys.exc_clear()
del tb
else:
import traceback
traceback.clear_frames(tb)
del tb
else:
events.append(JobExecutionEvent(EVENT_JOB_EXECUTED, job.id, jobstore_alias, run_time,
retval=retval))
logger.info('Job "%s" executed successfully', job)
return events

View File

@ -0,0 +1,41 @@
import logging
import sys
from datetime import datetime, timedelta
from traceback import format_tb
from pytz import utc
from apscheduler.events import (
JobExecutionEvent, EVENT_JOB_MISSED, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED)
async def run_coroutine_job(job, jobstore_alias, run_times, logger_name):
"""Coroutine version of run_job()."""
events = []
logger = logging.getLogger(logger_name)
for run_time in run_times:
# See if the job missed its run time window, and handle possible misfires accordingly
if job.misfire_grace_time is not None:
difference = datetime.now(utc) - run_time
grace_time = timedelta(seconds=job.misfire_grace_time)
if difference > grace_time:
events.append(JobExecutionEvent(EVENT_JOB_MISSED, job.id, jobstore_alias,
run_time))
logger.warning('Run time of job "%s" was missed by %s', job, difference)
continue
logger.info('Running job "%s" (scheduled at %s)', job, run_time)
try:
retval = await job.func(*job.args, **job.kwargs)
except BaseException:
exc, tb = sys.exc_info()[1:]
formatted_tb = ''.join(format_tb(tb))
events.append(JobExecutionEvent(EVENT_JOB_ERROR, job.id, jobstore_alias, run_time,
exception=exc, traceback=formatted_tb))
logger.exception('Job "%s" raised an exception', job)
else:
events.append(JobExecutionEvent(EVENT_JOB_EXECUTED, job.id, jobstore_alias, run_time,
retval=retval))
logger.info('Job "%s" executed successfully', job)
return events

View File

@ -0,0 +1,20 @@
import sys
from apscheduler.executors.base import BaseExecutor, run_job
class DebugExecutor(BaseExecutor):
"""
A special executor that executes the target callable directly instead of deferring it to a
thread or process.
Plugin alias: ``debug``
"""
def _do_submit_job(self, job, run_times):
try:
events = run_job(job, job._jobstore_alias, run_times, self._logger.name)
except BaseException:
self._run_job_error(job.id, *sys.exc_info()[1:])
else:
self._run_job_success(job.id, events)

View File

@ -0,0 +1,30 @@
from __future__ import absolute_import
import sys
from apscheduler.executors.base import BaseExecutor, run_job
try:
import gevent
except ImportError: # pragma: nocover
raise ImportError('GeventExecutor requires gevent installed')
class GeventExecutor(BaseExecutor):
"""
Runs jobs as greenlets.
Plugin alias: ``gevent``
"""
def _do_submit_job(self, job, run_times):
def callback(greenlet):
try:
events = greenlet.get()
except BaseException:
self._run_job_error(job.id, *sys.exc_info()[1:])
else:
self._run_job_success(job.id, events)
gevent.spawn(run_job, job, job._jobstore_alias, run_times, self._logger.name).\
link(callback)

View File

@ -0,0 +1,54 @@
from abc import abstractmethod
import concurrent.futures
from apscheduler.executors.base import BaseExecutor, run_job
class BasePoolExecutor(BaseExecutor):
@abstractmethod
def __init__(self, pool):
super(BasePoolExecutor, self).__init__()
self._pool = pool
def _do_submit_job(self, job, run_times):
def callback(f):
exc, tb = (f.exception_info() if hasattr(f, 'exception_info') else
(f.exception(), getattr(f.exception(), '__traceback__', None)))
if exc:
self._run_job_error(job.id, exc, tb)
else:
self._run_job_success(job.id, f.result())
f = self._pool.submit(run_job, job, job._jobstore_alias, run_times, self._logger.name)
f.add_done_callback(callback)
def shutdown(self, wait=True):
self._pool.shutdown(wait)
class ThreadPoolExecutor(BasePoolExecutor):
"""
An executor that runs jobs in a concurrent.futures thread pool.
Plugin alias: ``threadpool``
:param max_workers: the maximum number of spawned threads.
"""
def __init__(self, max_workers=10):
pool = concurrent.futures.ThreadPoolExecutor(int(max_workers))
super(ThreadPoolExecutor, self).__init__(pool)
class ProcessPoolExecutor(BasePoolExecutor):
"""
An executor that runs jobs in a concurrent.futures process pool.
Plugin alias: ``processpool``
:param max_workers: the maximum number of spawned processes.
"""
def __init__(self, max_workers=10):
pool = concurrent.futures.ProcessPoolExecutor(int(max_workers))
super(ProcessPoolExecutor, self).__init__(pool)

View File

@ -0,0 +1,54 @@
from __future__ import absolute_import
import sys
from concurrent.futures import ThreadPoolExecutor
from tornado.gen import convert_yielded
from apscheduler.executors.base import BaseExecutor, run_job
try:
from inspect import iscoroutinefunction
from apscheduler.executors.base_py3 import run_coroutine_job
except ImportError:
def iscoroutinefunction(func):
return False
class TornadoExecutor(BaseExecutor):
"""
Runs jobs either in a thread pool or directly on the I/O loop.
If the job function is a native coroutine function, it is scheduled to be run directly in the
I/O loop as soon as possible. All other functions are run in a thread pool.
Plugin alias: ``tornado``
:param int max_workers: maximum number of worker threads in the thread pool
"""
def __init__(self, max_workers=10):
super(TornadoExecutor, self).__init__()
self.executor = ThreadPoolExecutor(max_workers)
def start(self, scheduler, alias):
super(TornadoExecutor, self).start(scheduler, alias)
self._ioloop = scheduler._ioloop
def _do_submit_job(self, job, run_times):
def callback(f):
try:
events = f.result()
except BaseException:
self._run_job_error(job.id, *sys.exc_info()[1:])
else:
self._run_job_success(job.id, events)
if iscoroutinefunction(job.func):
f = run_coroutine_job(job, job._jobstore_alias, run_times, self._logger.name)
else:
f = self.executor.submit(run_job, job, job._jobstore_alias, run_times,
self._logger.name)
f = convert_yielded(f)
f.add_done_callback(callback)

View File

@ -0,0 +1,25 @@
from __future__ import absolute_import
from apscheduler.executors.base import BaseExecutor, run_job
class TwistedExecutor(BaseExecutor):
"""
Runs jobs in the reactor's thread pool.
Plugin alias: ``twisted``
"""
def start(self, scheduler, alias):
super(TwistedExecutor, self).start(scheduler, alias)
self._reactor = scheduler._reactor
def _do_submit_job(self, job, run_times):
def callback(success, result):
if success:
self._run_job_success(job.id, result)
else:
self._run_job_error(job.id, result.value, result.tb)
self._reactor.getThreadPool().callInThreadWithCallback(
callback, run_job, job, job._jobstore_alias, run_times, self._logger.name)

Some files were not shown because too many files have changed in this diff Show More