merge
This commit is contained in:
commit
aac0e16002
4
.idea/encodings.xml
generated
4
.idea/encodings.xml
generated
@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/save.p" charset="windows-1252" />
|
||||
</component>
|
||||
</project>
|
0
scr/McRoll-reactions
Normal file
0
scr/McRoll-reactions
Normal file
@ -1,102 +1,138 @@
|
||||
import asyncio
|
||||
import collections
|
||||
from typing import Union
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import math
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import discord
|
||||
import pymysql
|
||||
from discord.ext import commands
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
password = open("../sqlPass.txt", 'r').read()
|
||||
|
||||
scheduler = AsyncIOScheduler({
|
||||
'apscheduler.jobstores.default': {
|
||||
'type': 'sqlalchemy',
|
||||
'url': f'mysql+pymysql://Quentin:{password}@192.168.1.52:5618/discord_bot?charset=utf8mb4',
|
||||
},
|
||||
'apscheduler.job_defaults.coalesce': 'True',
|
||||
'apscheduler.timezone': 'America/Chicago'
|
||||
})
|
||||
|
||||
scheduler.start()
|
||||
|
||||
mcSelf = None
|
||||
|
||||
|
||||
# password = open("../../../sqlPass.txt", 'r')
|
||||
|
||||
def get_con():
|
||||
def get_con() -> pymysql.Connection:
|
||||
return pymysql.connect(host='192.168.1.52',
|
||||
port=5618,
|
||||
user='Quentin',
|
||||
password='kaPl0wskii',
|
||||
password=f'{password}',
|
||||
db='mc',
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor)
|
||||
|
||||
|
||||
async def get_poll_results(choices: dict, pollMessageId: int, channelId: int):
|
||||
print("run")
|
||||
|
||||
getPollResultsReactions = (await McRoll.get_message(mcSelf, pollMessageId, channelId)).reactions
|
||||
for reaction in getPollResultsReactions:
|
||||
async for user in reaction.users():
|
||||
serverIP = reaction_to_serverip(reaction)
|
||||
if not user.bot:
|
||||
choices[serverIP]['votes'] = choices[serverIP]['votes'] + 1
|
||||
|
||||
choices[serverIP]['score'] = choices[serverIP]['votes'] * round(
|
||||
2 * math.log10(choices[serverIP]['lastActivated'] + 1) + 1, 2)
|
||||
prune_results(choices)
|
||||
if len(choices) >= 3:
|
||||
await McRoll.main_poll_recursion(mcSelf, choices, channelId)
|
||||
else:
|
||||
await McRoll.declare_winner(mcSelf, choices, channelId)
|
||||
|
||||
|
||||
def reaction_to_serverip(reaction: Union[discord.Reaction, str]) -> int:
|
||||
con = get_con()
|
||||
reaction = str(reaction.emoji.id) \
|
||||
if isinstance(reaction.emoji, discord.Emoji) else reaction.emoji.encode('unicode_escape').decode('utf-8')
|
||||
|
||||
with con.cursor() as cursor:
|
||||
cursor.execute("SELECT serverIP FROM mc.server_list WHERE reaction=%s;", reaction)
|
||||
serverIp = cursor.fetchone()
|
||||
con.close()
|
||||
return serverIp['serverIP']
|
||||
|
||||
|
||||
def get_choices() -> dict:
|
||||
con = get_con()
|
||||
choices = {}
|
||||
with con.cursor() as cursor:
|
||||
cursor.execute("SELECT serverIP, serverName, lastActivated, reaction, getEmoji "
|
||||
"FROM mc.server_list WHERE lastActivated is not null")
|
||||
row = cursor.fetchone()
|
||||
while row is not None:
|
||||
row['votes'] = 0
|
||||
row['score'] = 0
|
||||
choices[row['serverIP']] = row
|
||||
row = cursor.fetchone()
|
||||
con.close()
|
||||
return choices
|
||||
|
||||
|
||||
def pop_lowest(choices: dict):
|
||||
lowest = {'score': 10}
|
||||
pop = False
|
||||
for choice in list(choices):
|
||||
if choices[choice]['score'] < lowest['score']:
|
||||
lowest = choices[choice]
|
||||
pop = True
|
||||
if pop:
|
||||
choices.pop(lowest['serverIP'])
|
||||
|
||||
|
||||
def prune_results(choices: dict):
|
||||
for serverIp, choice in list(choices.items()):
|
||||
if choice['votes'] <= 0:
|
||||
del choices[serverIp]
|
||||
if len(choices) >= 4:
|
||||
pop_lowest(choices)
|
||||
pop_lowest(choices)
|
||||
elif len(choices) == 3:
|
||||
pop_lowest(choices)
|
||||
|
||||
|
||||
class McRoll(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.currentPoll = None
|
||||
self.embedInfo = """Each server has a multiplier based on how long it has been sense that server has been rolled.
|
||||
When this poll is over all servers with 0 votes will be disregarded.
|
||||
If there are 4 or more servers left the two with the lowest votes will be disregarded.
|
||||
Poll is rerun until there are only 2 servers, these two are the winners. Ties reroll the poll.
|
||||
You can only vote for one server per poll. """
|
||||
self.embedInfo = """Each server has a multiplier based on how long it has been sense that server has been
|
||||
rolled. When this poll is over all servers with 0 votes will be disregarded. If there are 4 or more servers
|
||||
left the two with the lowest votes will be disregarded. Poll is rerun until there are only 2 servers,
|
||||
these two are the winners. Ties reroll the poll. You can only vote for one server per poll. """
|
||||
self.pollMessageId = None
|
||||
global mcSelf
|
||||
mcSelf = self # This is a bad way of doing things but it works
|
||||
|
||||
@commands.command()
|
||||
async def mc_roll(self, ctx):
|
||||
if ctx.message.author.id != 305589587215122432:
|
||||
return
|
||||
choices = get_choices()
|
||||
await self.main_poll_recursion(choices, ctx.channel.id)
|
||||
|
||||
async def start_poll(self, ctx, choices):
|
||||
embed = discord.Embed(title="Poll", description=self.embedInfo, timestamp=datetime.now() + timedelta(hours=12))
|
||||
for choice in choices.items():
|
||||
choice = choice[1]
|
||||
embed.add_field(
|
||||
name=choice['serverName'] + ' ' + (str(self.client.get_emoji(int(choice['reaction'])))
|
||||
if choice['getEmoji']
|
||||
else bytes(choice['reaction'], "utf-8").decode("unicode_escape")),
|
||||
value="Multiplier : " + str(round(2 * math.log10(choice['lastActivated'] + 1) + 1, 2)) + '\n' +
|
||||
"Last Rolled : " + str(choice['lastActivated'] * 2) + " weeks ago.")
|
||||
self.currentPoll = await ctx.channel.send(embed=embed)
|
||||
await ctx.channel.send("@everyone")
|
||||
for choice in choices.items():
|
||||
choice = choice[1]
|
||||
await self.currentPoll.add_reaction(self.client.get_emoji(int(choice['reaction']))
|
||||
if choice['getEmoji']
|
||||
else bytes(choice['reaction'], "utf-8").decode("unicode_escape"))
|
||||
await asyncio.sleep(43200)
|
||||
|
||||
for reaction in self.currentPoll.reactions:
|
||||
async for user in reaction.users():
|
||||
serverIp = self.reaction_to_serverip(reaction)
|
||||
if user.id != 533427166193385494:
|
||||
choices[serverIp]['votes'] = choices[serverIp]['votes'] + 1
|
||||
|
||||
choices[serverIp]['score'] = choices[serverIp]['votes'] * round(
|
||||
2 * math.log10(choices[serverIp]['lastActivated'] + 1) + 1, 2)
|
||||
|
||||
for choice in list(choices):
|
||||
if choices[choice]['score'] <= 0:
|
||||
choices.pop(choice)
|
||||
continue
|
||||
|
||||
if len(choices) >= 4:
|
||||
choices = self.pop_lowest(choices)
|
||||
choices = self.pop_lowest(choices)
|
||||
elif len(choices) == 3:
|
||||
choices = self.pop_lowest(choices)
|
||||
|
||||
return choices
|
||||
|
||||
|
||||
def pop_lowest(self, choices):
|
||||
lowest = {'score': 10}
|
||||
pop = False
|
||||
for choice in list(choices):
|
||||
if choices[choice]['score'] < lowest['score']:
|
||||
lowest = choices[choice]
|
||||
pop = True
|
||||
if pop:
|
||||
choices.pop(lowest['serverIP'])
|
||||
return choices
|
||||
|
||||
def reaction_to_serverip(self, reaction):
|
||||
con = get_con()
|
||||
escapedReaction = reaction.emoji.encode('unicode_escape').decode('utf-8')
|
||||
with con.cursor() as cursor:
|
||||
cursor.execute("SELECT serverIP FROM mc.server_list WHERE reaction=%s;", escapedReaction)
|
||||
serverIp = cursor.fetchone()
|
||||
con.close()
|
||||
return serverIp['serverIP']
|
||||
|
||||
async def main_poll_recursion(self, choices, channelId):
|
||||
self.pollMessageId = (await self.poll(choices, channelId)).id
|
||||
scheduler.add_job(get_poll_results, 'date',
|
||||
run_date=(datetime.now() + timedelta(seconds=10)),
|
||||
args=[choices, self.pollMessageId, channelId],
|
||||
coalesce=True)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_reaction_add(self, reaction, user):
|
||||
if self.pollMessageId is None:
|
||||
return
|
||||
reactedUsers = {}
|
||||
if reaction.message.id == self.currentPoll.id and user.id != 533427166193385494:
|
||||
if reaction.message.id == self.pollMessageId and user.id != 533427166193385494:
|
||||
for iReaction in reaction.message.reactions:
|
||||
async for iUser in iReaction.users():
|
||||
if iUser.id != 533427166193385494:
|
||||
@ -104,41 +140,37 @@ class McRoll(commands.Cog):
|
||||
reactedUsers[iUser.id] = False
|
||||
if reactedUsers[iUser.id]:
|
||||
await reaction.remove(user)
|
||||
self.currentPoll = reaction.message
|
||||
self.pollMessageId = reaction.message.id
|
||||
await user.send(
|
||||
"You can only vote for one option, please remove your previous vote to change it.")
|
||||
return
|
||||
self.currentPoll = reaction.message
|
||||
self.pollMessageId = reaction.message.id
|
||||
reactedUsers[iUser.id] = True
|
||||
|
||||
async def poll(self, choices: dict, channel: int) -> discord.Message:
|
||||
channel = await self.client.fetch_channel(channel)
|
||||
embed = discord.Embed(title="Poll", description=self.embedInfo, timestamp=(datetime.utcnow() + timedelta(days=1)))
|
||||
for choice in choices.values():
|
||||
embed.add_field(
|
||||
name=choice['serverName'] + ' ' + str(self.client.get_emoji(int(choice['reaction'])))
|
||||
if choice['getEmoji']
|
||||
else bytes(choice['reaction'], "utf-8").decode("unicode_escape"),
|
||||
value="Multiplier : " + str(round(2 * math.log10(choice['lastActivated'] + 1) + 1, 2)) + '\n' +
|
||||
"Last Rolled : " + str(choice['lastActivated'] * 2) + " weeks ago.")
|
||||
|
||||
@commands.command()
|
||||
async def mc_roll(self, ctx):
|
||||
if ctx.message.author.id != 305589587215122432:
|
||||
return
|
||||
pollMessage = await channel.send(embed=embed)
|
||||
await channel.send('@everyone')
|
||||
|
||||
con = get_con()
|
||||
with con.cursor() as cursor:
|
||||
cursor.execute("SELECT serverIP, serverName, lastActivated, reaction, getEmoji "
|
||||
"FROM mc.server_list WHERE lastActivated is not null")
|
||||
choices = cursor.fetchall()
|
||||
con.close()
|
||||
|
||||
dictChoices = {}
|
||||
for choice in choices:
|
||||
choice['score'] = 0
|
||||
choice['votes'] = 0
|
||||
dictChoices[choice['serverIP']] = choice
|
||||
|
||||
dictChoices = await self.start_poll(ctx, dictChoices)
|
||||
|
||||
while len(dictChoices) > 2:
|
||||
print(dictChoices)
|
||||
dictChoices = await self.start_poll(ctx, dictChoices)
|
||||
for choice in choices.values():
|
||||
await pollMessage.add_reaction(self.client.get_emoji(int(choice['reaction']))
|
||||
if choice['getEmoji']
|
||||
else bytes(choice['reaction'], "utf-8").decode("unicode_escape"))
|
||||
return pollMessage
|
||||
|
||||
async def declare_winner(self, choices, channelId):
|
||||
embed = discord.Embed(title="Winner", description="Congratulations")
|
||||
for item in list(dictChoices):
|
||||
winner = dictChoices[item]
|
||||
for item in list(choices):
|
||||
winner = choices[item]
|
||||
embed.add_field(
|
||||
name=winner['serverName'],
|
||||
value="Multiplier : " + str(round(2 * math.log10(winner['lastActivated'] + 1) + 1, 2)) + '\n' +
|
||||
@ -146,9 +178,13 @@ class McRoll(commands.Cog):
|
||||
"Votes : " + str(winner['votes']) + '\n' +
|
||||
"Calculated Votes: " + str(winner['votes'] * round(2 * math.log10(winner['lastActivated'] + 1) + 1, 2))
|
||||
)
|
||||
channel = await self.client.fetch_channel(channelId)
|
||||
await channel.send(embed=embed)
|
||||
await channel.send("@everyone")
|
||||
|
||||
await ctx.channel.send(embed=embed)
|
||||
await ctx.channel.send("@everyone")
|
||||
async def get_message(self, messageId, channelId):
|
||||
channel = await self.client.fetch_channel(channelId)
|
||||
return await channel.fetch_message(messageId)
|
||||
|
||||
|
||||
def setup(client):
|
||||
|
38
scr/Modules/TMC/apschedulerTest.py
Normal file
38
scr/Modules/TMC/apschedulerTest.py
Normal file
@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime, timedelta, date
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
|
||||
scheduler = AsyncIOScheduler({
|
||||
'apscheduler.jobstores.default': {
|
||||
'type': 'sqlalchemy',
|
||||
'url': f'mysql+pymysql://Quentin:kaPl0wskii@192.168.1.52:5618/discord_bot?charset=utf8mb4'
|
||||
},
|
||||
'apscheduler.executors.default': {
|
||||
'class': 'apscheduler.executors.pool:ThreadPoolExecutor',
|
||||
'max_workers': '20'
|
||||
},
|
||||
'apscheduler.executors.processpool': {
|
||||
'type': 'processpool',
|
||||
'max_workers': '5'
|
||||
},
|
||||
'apscheduler.job_defaults.coalesce': 'true',
|
||||
'apscheduler.job_defaults.max_instances': '3',
|
||||
'apscheduler.timezone': 'America/Chicago',
|
||||
})
|
||||
|
||||
scheduler.start()
|
||||
|
||||
async def hello():
|
||||
print("hello world")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# scheduler.add_job(hello, 'date', run_date=datetime.now()+timedelta(seconds=20))
|
||||
|
||||
try:
|
||||
asyncio.get_event_loop().run_forever()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
@ -0,0 +1,19 @@
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Copyright (c) Alex Grönholm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
133
venv/Lib/site-packages/APScheduler-3.6.3.dist-info/METADATA
Normal file
133
venv/Lib/site-packages/APScheduler-3.6.3.dist-info/METADATA
Normal file
@ -0,0 +1,133 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: APScheduler
|
||||
Version: 3.6.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
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
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: doc
|
||||
Requires-Dist: sphinx ; extra == 'doc'
|
||||
Requires-Dist: sphinx-rtd-theme ; extra == 'doc'
|
||||
Provides-Extra: gevent
|
||||
Requires-Dist: gevent ; extra == 'gevent'
|
||||
Provides-Extra: mongodb
|
||||
Requires-Dist: pymongo (>=2.8) ; extra == 'mongodb'
|
||||
Provides-Extra: redis
|
||||
Requires-Dist: redis (>=3.0) ; extra == 'redis'
|
||||
Provides-Extra: rethinkdb
|
||||
Requires-Dist: rethinkdb (>=2.4.0) ; extra == 'rethinkdb'
|
||||
Provides-Extra: sqlalchemy
|
||||
Requires-Dist: sqlalchemy (>=0.8) ; extra == 'sqlalchemy'
|
||||
Provides-Extra: testing
|
||||
Requires-Dist: pytest ; extra == 'testing'
|
||||
Requires-Dist: pytest-cov ; extra == 'testing'
|
||||
Requires-Dist: pytest-tornado5 ; extra == 'testing'
|
||||
Requires-Dist: mock ; (python_version == "2.7") and extra == 'testing'
|
||||
Requires-Dist: pytest-asyncio (<0.6) ; (python_version == "3.4") and extra == 'testing'
|
||||
Requires-Dist: pytest-asyncio ; (python_version >= "3.5") 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.com/agronholm/apscheduler.svg?branch=master
|
||||
:target: https://travis-ci.com/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
|
||||
|
||||
|
84
venv/Lib/site-packages/APScheduler-3.6.3.dist-info/RECORD
Normal file
84
venv/Lib/site-packages/APScheduler-3.6.3.dist-info/RECORD
Normal file
@ -0,0 +1,84 @@
|
||||
APScheduler-3.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
APScheduler-3.6.3.dist-info/LICENSE.txt,sha256=YWP3mH37ONa8MgzitwsvArhivEESZRbVUu8c1DJH51g,1130
|
||||
APScheduler-3.6.3.dist-info/METADATA,sha256=VHah1X4AqMCGgcvEm06M-pAqmNC9q4tOQRbUv3b0Jh0,5398
|
||||
APScheduler-3.6.3.dist-info/RECORD,,
|
||||
APScheduler-3.6.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
APScheduler-3.6.3.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
|
||||
APScheduler-3.6.3.dist-info/entry_points.txt,sha256=7RgkYN_OYyCUQtIGhj-UNcelnIjsNm7nC9rogdMQh3U,1148
|
||||
APScheduler-3.6.3.dist-info/top_level.txt,sha256=O3oMCWxG-AHkecUoO6Ze7-yYjWrttL95uHO8-RFdYvE,12
|
||||
apscheduler/__init__.py,sha256=qFEK2ysRBcLiYmm3deyJJ1avUOugaM_nCGHMD42WMBw,380
|
||||
apscheduler/__pycache__/__init__.cpython-36.pyc,,
|
||||
apscheduler/__pycache__/events.cpython-36.pyc,,
|
||||
apscheduler/__pycache__/job.cpython-36.pyc,,
|
||||
apscheduler/__pycache__/util.cpython-36.pyc,,
|
||||
apscheduler/events.py,sha256=KRMTDQUS6d2uVnrQvPoz3ZPV5V9XKsCAZLsgx913FFo,3593
|
||||
apscheduler/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
apscheduler/executors/__pycache__/__init__.cpython-36.pyc,,
|
||||
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/asyncio.py,sha256=ji5f6Qm2uGhov-3w52CXHZi8jc5U_gS56lisQylKTBQ,2087
|
||||
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=DU75VaQ9R6nBuy8lbPUvDKUgsuJcZqwAvURC5vg3r6w,1780
|
||||
apscheduler/executors/twisted.py,sha256=bRoU0C4BoVcS6_BjKD5wfUs0IJpGkmLsRAcMH2rJJss,778
|
||||
apscheduler/job.py,sha256=zT9_GuOpxuxEPVZU38tantw9383tAPRBPoH6dd4uHGA,11088
|
||||
apscheduler/jobstores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
apscheduler/jobstores/__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/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=kjQDIzPXz-Yq976U9HK3aMkcCI_QRLKgTADQWKewtik,5483
|
||||
apscheduler/jobstores/rethinkdb.py,sha256=k1rSLYJqejuhQxJY3pXwHAQYcpZ1QFJsoQ8n0oEu5MM,5863
|
||||
apscheduler/jobstores/sqlalchemy.py,sha256=5H5T05cQ2ZtkRuRb8hKkcLzZSQneAT13NMKXby3nzWE,6122
|
||||
apscheduler/jobstores/zookeeper.py,sha256=BzyqZ08XIDcbu5frQWGmDVEHAEScNxjt8oML6Tty8j8,6406
|
||||
apscheduler/schedulers/__init__.py,sha256=jM63xA_K7GSToBenhsz-SCcqfhk1pdEVb6ajwoO5Kqg,406
|
||||
apscheduler/schedulers/__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/asyncio.py,sha256=0j0mcDpf-zI_vQHcUCZZtBfEEZEiocEOZ767efIZ5YM,2082
|
||||
apscheduler/schedulers/background.py,sha256=dGX0T0z6T6HzZHG7njWgp90SFHpetZ4ZBUV2gGOSqoc,1505
|
||||
apscheduler/schedulers/base.py,sha256=EUGbQ5R2jGA4PEEehU2ASuKVe0SsLqtWESAtTqAJW50,42863
|
||||
apscheduler/schedulers/blocking.py,sha256=c-5YR-dKn3D82tPt38t50KGPJrAiC852v8ai2Vwanmg,924
|
||||
apscheduler/schedulers/gevent.py,sha256=csPBvV75FGcboXXsdex6fCD7J54QgBddYNdWj62ZO9g,1031
|
||||
apscheduler/schedulers/qt.py,sha256=AhHU62ybOOVSD4OhMwoPRRUCoM5cf5q26uD3hPglfnc,1297
|
||||
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/__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/base.py,sha256=WMo5f2g14fjO5VzpIxFQtk47Z9VEUDDPSxjoPL9FGSQ,1837
|
||||
apscheduler/triggers/combining.py,sha256=WTEnaEkBHysF1009sCvBaQa99hiy9l5Oz-hHyjy3jv8,3473
|
||||
apscheduler/triggers/cron/__init__.py,sha256=a8ASzvM7ci-djOI2jIL2XErL6zEx4Wr1012aD1XJw_w,9246
|
||||
apscheduler/triggers/cron/__pycache__/__init__.cpython-36.pyc,,
|
||||
apscheduler/triggers/cron/__pycache__/expressions.cpython-36.pyc,,
|
||||
apscheduler/triggers/cron/__pycache__/fields.cpython-36.pyc,,
|
||||
apscheduler/triggers/cron/expressions.py,sha256=hu1kq0mKvivIw7U0D0Nnrbuk3q01dCuhZ7SHRPw6qhI,9184
|
||||
apscheduler/triggers/cron/fields.py,sha256=NWPClh1NgSOpTlJ3sm1TXM_ViC2qJGKWkd_vg0xsw7o,3510
|
||||
apscheduler/triggers/date.py,sha256=RrfB1PNO9G9e91p1BOf-y_TseVHQQR-KJPhNdPpAHcU,1705
|
||||
apscheduler/triggers/interval.py,sha256=LiIunGOd96yaiAceG1XGP8eY3JxSyHDWCipVhQWMzDU,4381
|
||||
apscheduler/util.py,sha256=bQLVYP-RHtjypxol40a_JPT1Ta9BYSlTNdsDTc7dNMU,13963
|
@ -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
|
||||
|
@ -0,0 +1 @@
|
||||
apscheduler
|
19
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/LICENSE
Normal file
19
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright 2005-2020 SQLAlchemy authors and contributors <see AUTHORS file>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
210
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/METADATA
Normal file
210
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/METADATA
Normal file
@ -0,0 +1,210 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: SQLAlchemy
|
||||
Version: 1.3.22
|
||||
Summary: Database Abstraction Library
|
||||
Home-page: http://www.sqlalchemy.org
|
||||
Author: Mike Bayer
|
||||
Author-email: mike_mp@zzzcomputing.com
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://docs.sqlalchemy.org
|
||||
Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/
|
||||
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
|
||||
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: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Database :: Front-Ends
|
||||
Classifier: Operating System :: OS Independent
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Provides-Extra: mssql
|
||||
Requires-Dist: pyodbc ; extra == 'mssql'
|
||||
Provides-Extra: mssql_pymssql
|
||||
Requires-Dist: pymssql ; extra == 'mssql_pymssql'
|
||||
Provides-Extra: mssql_pyodbc
|
||||
Requires-Dist: pyodbc ; extra == 'mssql_pyodbc'
|
||||
Provides-Extra: mysql
|
||||
Requires-Dist: mysqlclient ; extra == 'mysql'
|
||||
Provides-Extra: oracle
|
||||
Requires-Dist: cx-oracle ; extra == 'oracle'
|
||||
Provides-Extra: postgresql
|
||||
Requires-Dist: psycopg2 ; extra == 'postgresql'
|
||||
Provides-Extra: postgresql_pg8000
|
||||
Requires-Dist: pg8000 ; extra == 'postgresql_pg8000'
|
||||
Provides-Extra: postgresql_psycopg2binary
|
||||
Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary'
|
||||
Provides-Extra: postgresql_psycopg2cffi
|
||||
Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi'
|
||||
Provides-Extra: pymysql
|
||||
Requires-Dist: pymysql ; extra == 'pymysql'
|
||||
|
||||
SQLAlchemy
|
||||
==========
|
||||
|
||||
|PyPI| |Python| |Downloads|
|
||||
|
||||
.. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy
|
||||
:target: https://pypi.org/project/sqlalchemy
|
||||
:alt: PyPI
|
||||
|
||||
.. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy
|
||||
:target: https://pypi.org/project/sqlalchemy
|
||||
:alt: PyPI - Python Version
|
||||
|
||||
.. |Downloads| image:: https://img.shields.io/pypi/dm/sqlalchemy
|
||||
:target: https://pypi.org/project/sqlalchemy
|
||||
:alt: PyPI - Downloads
|
||||
|
||||
|
||||
The Python SQL Toolkit and Object Relational Mapper
|
||||
|
||||
Introduction
|
||||
-------------
|
||||
|
||||
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper
|
||||
that gives application developers the full power and
|
||||
flexibility of SQL. SQLAlchemy provides a full suite
|
||||
of well known enterprise-level persistence patterns,
|
||||
designed for efficient and high-performing database
|
||||
access, adapted into a simple and Pythonic domain
|
||||
language.
|
||||
|
||||
Major SQLAlchemy features include:
|
||||
|
||||
* An industrial strength ORM, built
|
||||
from the core on the identity map, unit of work,
|
||||
and data mapper patterns. These patterns
|
||||
allow transparent persistence of objects
|
||||
using a declarative configuration system.
|
||||
Domain models
|
||||
can be constructed and manipulated naturally,
|
||||
and changes are synchronized with the
|
||||
current transaction automatically.
|
||||
* A relationally-oriented query system, exposing
|
||||
the full range of SQL's capabilities
|
||||
explicitly, including joins, subqueries,
|
||||
correlation, and most everything else,
|
||||
in terms of the object model.
|
||||
Writing queries with the ORM uses the same
|
||||
techniques of relational composition you use
|
||||
when writing SQL. While you can drop into
|
||||
literal SQL at any time, it's virtually never
|
||||
needed.
|
||||
* A comprehensive and flexible system
|
||||
of eager loading for related collections and objects.
|
||||
Collections are cached within a session,
|
||||
and can be loaded on individual access, all
|
||||
at once using joins, or by query per collection
|
||||
across the full result set.
|
||||
* A Core SQL construction system and DBAPI
|
||||
interaction layer. The SQLAlchemy Core is
|
||||
separate from the ORM and is a full database
|
||||
abstraction layer in its own right, and includes
|
||||
an extensible Python-based SQL expression
|
||||
language, schema metadata, connection pooling,
|
||||
type coercion, and custom types.
|
||||
* All primary and foreign key constraints are
|
||||
assumed to be composite and natural. Surrogate
|
||||
integer primary keys are of course still the
|
||||
norm, but SQLAlchemy never assumes or hardcodes
|
||||
to this model.
|
||||
* Database introspection and generation. Database
|
||||
schemas can be "reflected" in one step into
|
||||
Python structures representing database metadata;
|
||||
those same structures can then generate
|
||||
CREATE statements right back out - all within
|
||||
the Core, independent of the ORM.
|
||||
|
||||
SQLAlchemy's philosophy:
|
||||
|
||||
* SQL databases behave less and less like object
|
||||
collections the more size and performance start to
|
||||
matter; object collections behave less and less like
|
||||
tables and rows the more abstraction starts to matter.
|
||||
SQLAlchemy aims to accommodate both of these
|
||||
principles.
|
||||
* An ORM doesn't need to hide the "R". A relational
|
||||
database provides rich, set-based functionality
|
||||
that should be fully exposed. SQLAlchemy's
|
||||
ORM provides an open-ended set of patterns
|
||||
that allow a developer to construct a custom
|
||||
mediation layer between a domain model and
|
||||
a relational schema, turning the so-called
|
||||
"object relational impedance" issue into
|
||||
a distant memory.
|
||||
* The developer, in all cases, makes all decisions
|
||||
regarding the design, structure, and naming conventions
|
||||
of both the object model as well as the relational
|
||||
schema. SQLAlchemy only provides the means
|
||||
to automate the execution of these decisions.
|
||||
* With SQLAlchemy, there's no such thing as
|
||||
"the ORM generated a bad query" - you
|
||||
retain full control over the structure of
|
||||
queries, including how joins are organized,
|
||||
how subqueries and correlation is used, what
|
||||
columns are requested. Everything SQLAlchemy
|
||||
does is ultimately the result of a developer-
|
||||
initiated decision.
|
||||
* Don't use an ORM if the problem doesn't need one.
|
||||
SQLAlchemy consists of a Core and separate ORM
|
||||
component. The Core offers a full SQL expression
|
||||
language that allows Pythonic construction
|
||||
of SQL constructs that render directly to SQL
|
||||
strings for a target database, returning
|
||||
result sets that are essentially enhanced DBAPI
|
||||
cursors.
|
||||
* Transactions should be the norm. With SQLAlchemy's
|
||||
ORM, nothing goes to permanent storage until
|
||||
commit() is called. SQLAlchemy encourages applications
|
||||
to create a consistent means of delineating
|
||||
the start and end of a series of operations.
|
||||
* Never render a literal value in a SQL statement.
|
||||
Bound parameters are used to the greatest degree
|
||||
possible, allowing query optimizers to cache
|
||||
query plans effectively and making SQL injection
|
||||
attacks a non-issue.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Latest documentation is at:
|
||||
|
||||
http://www.sqlalchemy.org/docs/
|
||||
|
||||
Installation / Requirements
|
||||
---------------------------
|
||||
|
||||
Full documentation for installation is at
|
||||
`Installation <http://www.sqlalchemy.org/docs/intro.html#installation>`_.
|
||||
|
||||
Getting Help / Development / Bug reporting
|
||||
------------------------------------------
|
||||
|
||||
Please refer to the `SQLAlchemy Community Guide <http://www.sqlalchemy.org/support.html>`_.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Above all, SQLAlchemy places great emphasis on polite, thoughtful, and
|
||||
constructive communication between users and developers.
|
||||
Please see our current Code of Conduct at
|
||||
`Code of Conduct <http://www.sqlalchemy.org/codeofconduct.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
SQLAlchemy is distributed under the `MIT license
|
||||
<http://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
|
||||
|
408
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/RECORD
Normal file
408
venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/RECORD
Normal file
@ -0,0 +1,408 @@
|
||||
SQLAlchemy-1.3.22.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
SQLAlchemy-1.3.22.dist-info/LICENSE,sha256=JYpB5k2IR1Y8Ym9F--J9Vsa25hcfRa01rPMRLUZR7eM,1119
|
||||
SQLAlchemy-1.3.22.dist-info/METADATA,sha256=CoXBOklkuv5C54I0lpgAWD3eo1WsfsnGvz59qYFRGlo,7789
|
||||
SQLAlchemy-1.3.22.dist-info/RECORD,,
|
||||
SQLAlchemy-1.3.22.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
SQLAlchemy-1.3.22.dist-info/WHEEL,sha256=epucrC2yyYTysDCMzXuz8eGMTMKryzRfNOvMGdslbjc,101
|
||||
SQLAlchemy-1.3.22.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
|
||||
sqlalchemy/__init__.py,sha256=oGbsCCcNv7nfI2dHYxm9SnD2SIFGeMKQq-u6hwQuTxs,4940
|
||||
sqlalchemy/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/events.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/exc.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/inspection.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/interfaces.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/log.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/processors.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/schema.cpython-36.pyc,,
|
||||
sqlalchemy/__pycache__/types.cpython-36.pyc,,
|
||||
sqlalchemy/connectors/__init__.py,sha256=D7659JWloMtbnvSb-MkRJjs8-Ecgwa7uI2XQbmMfLYg,288
|
||||
sqlalchemy/connectors/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/mxodbc.cpython-36.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/pyodbc.cpython-36.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/zxJDBC.cpython-36.pyc,,
|
||||
sqlalchemy/connectors/mxodbc.py,sha256=KNOaaXrHzFfIEUgbXDyslCpNfBVQbCkUIPO8eCSRIQA,5504
|
||||
sqlalchemy/connectors/pyodbc.py,sha256=x0-C7TxZAbM6NAiBD-wazJqi9GSqqCk9F0aex76Nd4s,6167
|
||||
sqlalchemy/connectors/zxJDBC.py,sha256=vuWKN_K8DDLp_lBwzeIJkexgXJQrwtqDdeZqabQu_5I,1946
|
||||
sqlalchemy/cprocessors.cp36-win_amd64.pyd,sha256=avUe-wnlnSJS5cmuR5ucOAAoSa5z57mi9sBBiUnPyJ8,17408
|
||||
sqlalchemy/cresultproxy.cp36-win_amd64.pyd,sha256=XOvcyT-TdwxcII6txz_Ln9jkyqBPPVbanJx47Wkd2Rw,18432
|
||||
sqlalchemy/cutils.cp36-win_amd64.pyd,sha256=sZzj-sIrsBWAPiw_xHIJiIes1s5FDoY-t8kbTZ6Iq1s,11264
|
||||
sqlalchemy/databases/__init__.py,sha256=6fOESInuF0zWrGeomJyeO3eZ27yx0bvPfNLDwMFQuyo,851
|
||||
sqlalchemy/databases/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/__init__.py,sha256=tuHFeqV6ldzs8J_AwgVb19BNzvN1KkbK1kQqghXgtZs,1981
|
||||
sqlalchemy/dialects/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/firebird/__init__.py,sha256=xX-oB-TsUHwwQcz3_lDpUUpN1hK0aoYN7aFs3yaYaug,1193
|
||||
sqlalchemy/dialects/firebird/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/fdb.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/firebird/__pycache__/kinterbasdb.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/firebird/base.py,sha256=fAKJL4xr0XnWPsuYg4OYWMwVaoigV4T8ia5L8zSWvV8,31278
|
||||
sqlalchemy/dialects/firebird/fdb.py,sha256=962JWKObYNHp_gm5Yp6rw1kGhKyTY9xmftfBqkyAHZA,4189
|
||||
sqlalchemy/dialects/firebird/kinterbasdb.py,sha256=Nc90QW_ieIr7UXf3niFS0s1T9rtHCMcrVYTu9UeyY50,6638
|
||||
sqlalchemy/dialects/mssql/__init__.py,sha256=0DbqS4ifYplCCsxj00PKZKIK945fbTeXQPatTkFek_w,1897
|
||||
sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/adodbapi.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/mxodbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/zxjdbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mssql/adodbapi.py,sha256=2iTqjUMZhGX2EcDRvxXKcDJPvrB0H-1mZOtmahuDIvA,2809
|
||||
sqlalchemy/dialects/mssql/base.py,sha256=tmNAFdUg2UIMn9WklQx2dRtfwC2eIix_8z52XRfK0Zs,93907
|
||||
sqlalchemy/dialects/mssql/information_schema.py,sha256=VlG-2McJ1m81k5Q5FWNGXLcIJ_VZ333bGNy_vTZ4wnk,5814
|
||||
sqlalchemy/dialects/mssql/mxodbc.py,sha256=DRqhxaMNvnaW8PeSqx9QikkoLaYznIapdHUdJGXz5GQ,4756
|
||||
sqlalchemy/dialects/mssql/provision.py,sha256=GcMMMQYeRKDYV5YOThDaytZ6Z-zTThFkhuOOzXfloxM,2865
|
||||
sqlalchemy/dialects/mssql/pymssql.py,sha256=gk7O2RWn3ShkIzjnbNWuX_PAO3Vp_gh5M_MRFK7juiE,4921
|
||||
sqlalchemy/dialects/mssql/pyodbc.py,sha256=TMl11g3ZIS2N6iiPKrw6WZp-HuL-5QyXOhGnxEn_c44,16782
|
||||
sqlalchemy/dialects/mssql/zxjdbc.py,sha256=ZLOq6QNPV_ljOFo5uDMq99MhqX1yqUNtTAQppc4V4IA,2382
|
||||
sqlalchemy/dialects/mysql/__init__.py,sha256=X3KjSF_mLNSiCo_bB2GdfZ-0337jskSm6UxIUnrOo1g,2155
|
||||
sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/dml.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/gaerdbms.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/json.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/oursql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/types.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/zxjdbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/mysql/base.py,sha256=qvN_IrLrStClGFxq1iMAEGUI8UTglvAkyp9enH4k-fg,109610
|
||||
sqlalchemy/dialects/mysql/cymysql.py,sha256=smZp4gH5Js-GWi5QeW0mV76CQoqVTteAr_FZwjcGE7k,2326
|
||||
sqlalchemy/dialects/mysql/dml.py,sha256=iap_GsuLA_KVdav_aOLPP9tHmXo3gP7IIoBhCRcZlTE,4903
|
||||
sqlalchemy/dialects/mysql/enumerated.py,sha256=OKO-xo7RBVSvcJxC5tTTJqbu0KzCDmGL-5xlvTRejEU,11621
|
||||
sqlalchemy/dialects/mysql/gaerdbms.py,sha256=FNNG3WAK_R92wLxgfpixtV1cZ-WawSalDDxnvL8LnTQ,3477
|
||||
sqlalchemy/dialects/mysql/json.py,sha256=tP1xhztSdXybTiI-MjgNJGt6Y3xDZM_ZmGn2n3RcyQY,2121
|
||||
sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=3vawcKdZ5CPWDb_nacbanc5V9VKCwDdFyy-XCjo5AHg,8135
|
||||
sqlalchemy/dialects/mysql/mysqldb.py,sha256=1C4rq0pMv_3HuoVN5CLw5pagZD4YtxDt5TFRfu4M4_Q,8651
|
||||
sqlalchemy/dialects/mysql/oursql.py,sha256=APJcOSq8P6ZVkWcPzeVo0021AGr6LxlqMEMfJpEX7EA,8348
|
||||
sqlalchemy/dialects/mysql/provision.py,sha256=8TsTjBn0mKOg5KtxoSxS5RjWnTAZmx2D-QsMtWAhBRo,1309
|
||||
sqlalchemy/dialects/mysql/pymysql.py,sha256=qHZNSGjhPu6RXnyjZA_jIwSgDVnr9FGqcx-8gLvDsqE,2525
|
||||
sqlalchemy/dialects/mysql/pyodbc.py,sha256=J_SiU7l5f0MOu3ZE9nm5GuXhNV6kPrBHKvkAZUAsILU,3580
|
||||
sqlalchemy/dialects/mysql/reflection.py,sha256=YXTnwNtLys9BOq395d4rGj8CcpJT6ME9P2Bxu6WjSf4,18818
|
||||
sqlalchemy/dialects/mysql/types.py,sha256=L1qrnZLLfwvemhs5c7dqckA6058miMFphhgxKYRhlVs,25362
|
||||
sqlalchemy/dialects/mysql/zxjdbc.py,sha256=FcP6Wo-oNbhJc2uMS7JK_MGz_OlsOoe4vu_EIUbSD0I,4090
|
||||
sqlalchemy/dialects/oracle/__init__.py,sha256=BV4VRUkPudVQHxKWGAWF0GJpFSy8D2R11i_ITxD2NMg,1316
|
||||
sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/zxjdbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/oracle/base.py,sha256=hu_YwI1IFLXvq7OAQGWO55RlEeCUdTT_9mTGVQggwts,80046
|
||||
sqlalchemy/dialects/oracle/cx_oracle.py,sha256=RptY_Wmc5SaurrljUTTMd6i2-RVwSqk-7WVteMqjYX0,48218
|
||||
sqlalchemy/dialects/oracle/provision.py,sha256=C6d0eGCJnrEp0ttDtBapmGiG3vVT4QDP1H2nF5KlLIM,3972
|
||||
sqlalchemy/dialects/oracle/zxjdbc.py,sha256=hZEP9oglXP7sYjCwsNkyDJ3HWNmxZC4aclK1dfJfv64,8461
|
||||
sqlalchemy/dialects/postgresql/__init__.py,sha256=GCrneOsxBcbkmiJMNMiKJJGfcJqMkKYKJLrme8qFTX8,2577
|
||||
sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/array.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/json.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pygresql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pypostgresql.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/zxjdbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/postgresql/array.py,sha256=33Heu13kSDZlDfJqPOCXNKt9KzIZ276vhCWecNSnovM,12428
|
||||
sqlalchemy/dialects/postgresql/base.py,sha256=y14qLd2rYBLQLeSkEj5oav9fYFfK6KqZbEIhyAFWAcI,131828
|
||||
sqlalchemy/dialects/postgresql/dml.py,sha256=fYDe7w79jgLknblFwGr3arVstq-daaqftAUyfGK0zkk,8022
|
||||
sqlalchemy/dialects/postgresql/ext.py,sha256=ae0lgzCkCMzz_7MSAQNyhyK0G9XyGdNAz4K9q84O2XE,7768
|
||||
sqlalchemy/dialects/postgresql/hstore.py,sha256=8dePOlF-7k8i4ixfNMzE4LLc227jdEHNTFCrpkMcBAY,12861
|
||||
sqlalchemy/dialects/postgresql/json.py,sha256=UB-eM0FK965v5sJTG3kFfqbt4_zj9Z7wsEapUNhgkhY,10418
|
||||
sqlalchemy/dialects/postgresql/pg8000.py,sha256=EPiyPEdFZ5xeF-3lN9bEuxd1KpPUxIRphR44rMrkU4A,10054
|
||||
sqlalchemy/dialects/postgresql/provision.py,sha256=zZUgATRaS2qMCL2vpzX5KepX9Adu5aWVyHsSGK-b7_4,2072
|
||||
sqlalchemy/dialects/postgresql/psycopg2.py,sha256=wAWYmJMuaBoBfW3Qi2g5K8uMZZkxZpMJdy3d8Eg-76M,38148
|
||||
sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=Gy9Al5AXnVUBgAwZkWbrARiSHs6V9tcBQjXwjB7Qecw,1716
|
||||
sqlalchemy/dialects/postgresql/pygresql.py,sha256=snx_vIOVR_P3sUf0IMD190dPwO8nah4oFY-Dg36Blto,8395
|
||||
sqlalchemy/dialects/postgresql/pypostgresql.py,sha256=ibku8Pw0A2rAYB_O_TMzi2gcW1bMI4l8txJ2AZTG3dY,3024
|
||||
sqlalchemy/dialects/postgresql/ranges.py,sha256=xLHMLwLuV9WnUSg-ncuKyQJw2qedxoZ9Sj5BU9t5O6M,4757
|
||||
sqlalchemy/dialects/postgresql/zxjdbc.py,sha256=qhr2-xaLcfoeCZpRyl5PCo6SNabwDE9v2q1aMw3BhkM,1463
|
||||
sqlalchemy/dialects/sqlite/__init__.py,sha256=L-3xDyZP0fj3Ri0qYs6RAHoLfceMTIzRsYbJ3b1vBWw,1093
|
||||
sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/json.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sqlite/base.py,sha256=7zaK3ww7r-drH9PBkOHhrcjWanjTWtTspnD7o46VRB8,77282
|
||||
sqlalchemy/dialects/sqlite/json.py,sha256=jTzeDsutf0mCe46kFx26J6E5O_Tmct0zpoSxiSkGbIw,2370
|
||||
sqlalchemy/dialects/sqlite/provision.py,sha256=p3tIZ9fFsv99CNkpXppPLn9VbcjtiH5IJsil816G3LM,2666
|
||||
sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=7Imed4zA9kRECvn418aaN8Cn26k--QCmS7fjiNQ0cDI,4830
|
||||
sqlalchemy/dialects/sqlite/pysqlite.py,sha256=fhLPCRmosgCnVv1anLp5AL6lu0VXnRhRoSnXqGcm3oA,21511
|
||||
sqlalchemy/dialects/sybase/__init__.py,sha256=Yi5Vq16WZ2lqih1PTuKTruSXjFvgFowYPY2QgGr-kiM,1430
|
||||
sqlalchemy/dialects/sybase/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/mxodbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/pyodbc.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sybase/__pycache__/pysybase.cpython-36.pyc,,
|
||||
sqlalchemy/dialects/sybase/base.py,sha256=0KToWw8S_8wSDB84i3qzMxvIcpw2qXbA0EO2zju-As0,32873
|
||||
sqlalchemy/dialects/sybase/mxodbc.py,sha256=KFz6sa0vd7M9Jp-agumMUT734PqaMPoGP-2T5gpUjQU,936
|
||||
sqlalchemy/dialects/sybase/pyodbc.py,sha256=Ket-YHbBAH9z-ORWTwvv8z9LqlRbm9EVznfNXukAjDA,2205
|
||||
sqlalchemy/dialects/sybase/pysybase.py,sha256=gyaL1P3RDDPvlZ7TMT8sifQasAwwntRd8lf0eQizITU,3417
|
||||
sqlalchemy/engine/__init__.py,sha256=hLxpMJZ2W-DK-I_gEEiME-bWW_NVu-RE1ny_JFE5Mp4,25632
|
||||
sqlalchemy/engine/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/default.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/interfaces.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/reflection.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/result.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/strategies.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/threadlocal.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/url.cpython-36.pyc,,
|
||||
sqlalchemy/engine/__pycache__/util.cpython-36.pyc,,
|
||||
sqlalchemy/engine/base.py,sha256=SSEsf4G63VTAEcsicx4paVu3j6m8nfKVFbSnf8FAiIE,89571
|
||||
sqlalchemy/engine/default.py,sha256=BCUfi29CXcnJkOhb9qGr8sS5yYheR4tHqnO35GTTWbQ,57293
|
||||
sqlalchemy/engine/interfaces.py,sha256=3IJ6SPjdscyYZa5mywgm7cInyQ_xPWb0tCs_1UA3idU,49990
|
||||
sqlalchemy/engine/reflection.py,sha256=csWPvH38f-a2YlpwoVEicWsMSZBlIanwnpkm6kB8eUg,35748
|
||||
sqlalchemy/engine/result.py,sha256=qA9B6iIRgrst-p3Us3mgnZKmxByapF2SHDCszJ2w60c,56319
|
||||
sqlalchemy/engine/strategies.py,sha256=UQ1gUY1Jim9EPJ1JwSVhEE0oCPuF1kbTamAN8Doe33Y,10166
|
||||
sqlalchemy/engine/threadlocal.py,sha256=RZ31qQ34oFGTzJ-0k-vxW-hwlrQhPUfxzAJnG-ODjmw,4925
|
||||
sqlalchemy/engine/url.py,sha256=bQzV-5JowgOQeqSmNYH03_pdiwMnXLABqvS5PjpCh_8,9755
|
||||
sqlalchemy/engine/util.py,sha256=Cf2yL0fLIJlMG93ZGV1CFKtPoNOfJIK1F8x2AUwKzWk,2501
|
||||
sqlalchemy/event/__init__.py,sha256=Ej6lUzFJg39963UAkSMEPP1OFSgxS0VmqqYVxEfhlqo,613
|
||||
sqlalchemy/event/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/event/__pycache__/api.cpython-36.pyc,,
|
||||
sqlalchemy/event/__pycache__/attr.cpython-36.pyc,,
|
||||
sqlalchemy/event/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/event/__pycache__/legacy.cpython-36.pyc,,
|
||||
sqlalchemy/event/__pycache__/registry.cpython-36.pyc,,
|
||||
sqlalchemy/event/api.py,sha256=ycQNjI6kZHPykchMU-k6WGMwIttx2wCUUo1JEq7MYUI,7296
|
||||
sqlalchemy/event/attr.py,sha256=kqVDB8bPPLGNmKkmV7A3EF7dMj0liFKcxZFW9d6waDE,14295
|
||||
sqlalchemy/event/base.py,sha256=SGkPPaXEd40H8F_-8pU496yWmQudhi-_cSz46PHNOk4,10047
|
||||
sqlalchemy/event/legacy.py,sha256=3QrQ0rPxLGKzwo4FeUtXvFbRxANFhJBRYj_kVGkQmz4,6081
|
||||
sqlalchemy/event/registry.py,sha256=vLFXNVdxdxAoVxb60ZPbn0H39S8pAiSlRy-iAuEaGX0,8510
|
||||
sqlalchemy/events.py,sha256=RT3Bhu_Es5cRSLjX1s9-lbFvnjpkWcUxk2Xw67xOdaw,54796
|
||||
sqlalchemy/exc.py,sha256=_tgDgu1XS3hS7zo5hDSXBDG81E52Jgxsfmt8jMQm-4s,18441
|
||||
sqlalchemy/ext/__init__.py,sha256=F-uIFRUKjn1LdWPFjy8E6AZLERfRBQNiVWQjZf1FWxM,333
|
||||
sqlalchemy/ext/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/associationproxy.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/automap.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/baked.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/compiler.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/horizontal_shard.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/hybrid.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/indexable.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/instrumentation.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/mutable.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/orderinglist.cpython-36.pyc,,
|
||||
sqlalchemy/ext/__pycache__/serializer.cpython-36.pyc,,
|
||||
sqlalchemy/ext/associationproxy.py,sha256=rZ40Wg8uU6VUFj8BLp-DAi_riPu5d_OV3vl5xX1IJ74,51583
|
||||
sqlalchemy/ext/automap.py,sha256=c1q8-DcNvK9bb_fXDQXQkLok43gbfw_Dd2BmrYlDvls,43294
|
||||
sqlalchemy/ext/baked.py,sha256=tLUjs7aR5cbHpn6mD7Zu3O5k1JPDGfd3gX6WzbltjfY,22671
|
||||
sqlalchemy/ext/compiler.py,sha256=WTLu3SZaabXzLGnHju2Sch0Wjm38qRhl0GavhAdvLok,17633
|
||||
sqlalchemy/ext/declarative/__init__.py,sha256=kBi5Tv7YWkjof4BOMSU7k0B8pt-xiS2FGPlOm-wHQG4,935
|
||||
sqlalchemy/ext/declarative/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/api.cpython-36.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/clsregistry.cpython-36.pyc,,
|
||||
sqlalchemy/ext/declarative/api.py,sha256=1SRoRJiA0-dRE5o73hyp9ArStxImOVBjzfRna2E0t4U,29577
|
||||
sqlalchemy/ext/declarative/base.py,sha256=PeL5Moulup9ma_W-4mfO0iqPjZstyeyuEBCYn7G6HRY,32945
|
||||
sqlalchemy/ext/declarative/clsregistry.py,sha256=eL0b5GmyAVGMW8Xv-DGim5o6ZJkU_aKGFDubGehBpGE,12967
|
||||
sqlalchemy/ext/horizontal_shard.py,sha256=t42Z4MwSvA00NbKwHlBea1JLmS4306wTSrov4tVRFG8,9399
|
||||
sqlalchemy/ext/hybrid.py,sha256=kSoKBMxQSlQqHzK3SNbAzdaqYeMfdj7yYZnIdT-saPk,41561
|
||||
sqlalchemy/ext/indexable.py,sha256=xAHcNTft78MteT8W7L4fwn2K9Bm6NZkcgswW3baVJsc,11606
|
||||
sqlalchemy/ext/instrumentation.py,sha256=rDk7cBPATJLzVTXgk4eiMMZOfhNXqKN0ej5uGB3L8J0,14787
|
||||
sqlalchemy/ext/mutable.py,sha256=RzKcEPcxGLlMICwllsnBXN9LMqdm8UhUG8qUiYYwcvQ,32766
|
||||
sqlalchemy/ext/orderinglist.py,sha256=rknSocVvlnKJaajrn7YBzHf0X_vtonTtK3P7TpP5H5M,14288
|
||||
sqlalchemy/ext/serializer.py,sha256=nd-Vdqr0ym0VT0QXBZzRJWB3q0hcfOpmDVcT86Lwlik,5957
|
||||
sqlalchemy/inspection.py,sha256=iQWjerm0DQkFf9RpkwSRocMSUAVdIQrje75ez5vB09s,3123
|
||||
sqlalchemy/interfaces.py,sha256=1H48NQh38EfFgZwFC7c7slmajPOrWgDRGu8G2gfIr-A,13103
|
||||
sqlalchemy/log.py,sha256=2aS2vQI6b9k5JTqVGuGxpYM3AuzNo7lTNQpdTqacY2U,6927
|
||||
sqlalchemy/orm/__init__.py,sha256=EASu-ndJDwkuheDg3Hsdg5p1I1QlHLoRXXq9cDJtX-U,10245
|
||||
sqlalchemy/orm/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/attributes.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/collections.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dependency.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/deprecated_interfaces.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/descriptor_props.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dynamic.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/evaluator.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/events.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/exc.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/identity.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/instrumentation.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/interfaces.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/loading.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapper.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/path_registry.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/persistence.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/properties.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/query.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/relationships.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/scoping.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/session.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategies.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategy_options.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/sync.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/unitofwork.cpython-36.pyc,,
|
||||
sqlalchemy/orm/__pycache__/util.cpython-36.pyc,,
|
||||
sqlalchemy/orm/attributes.py,sha256=Q88uEiT5SXog1jCUd6axkwzyzvdfxmqfBMrlamwKgNY,70139
|
||||
sqlalchemy/orm/base.py,sha256=p44V2Li8iWV691USS9JJ9ZMvKO4NRXL9_EWYzOwPqPY,15882
|
||||
sqlalchemy/orm/collections.py,sha256=CGoPDFpFoMJhpYiXcSPiBvsWpYMWki7cykSv27IqJAg,54505
|
||||
sqlalchemy/orm/dependency.py,sha256=8rKv5Ln_moi3bl-NXuGOjr-4lECScIufxVSkyDxcc3o,47840
|
||||
sqlalchemy/orm/deprecated_interfaces.py,sha256=MhCVgxy3JVfkUQ5VjduzJETg8Uvol7PhTA4ZINICK50,21331
|
||||
sqlalchemy/orm/descriptor_props.py,sha256=slkon0LbI02QwUQrnlWJUJOuNzbvIs93BrAkFDXS3DY,29181
|
||||
sqlalchemy/orm/dynamic.py,sha256=c_NBkzdFUel0vxXAjzFKrv6CKloxCEb9YKJEE020yGE,15117
|
||||
sqlalchemy/orm/evaluator.py,sha256=oJOeyfowPK9tx90oFnN3oT2Lbpm-0zBC6jD3i70q_Ug,5633
|
||||
sqlalchemy/orm/events.py,sha256=IfmKrDZ5iF2MvPpgECjNNG4mYachg7uxY2TEoYLIeLQ,107363
|
||||
sqlalchemy/orm/exc.py,sha256=453LebxTLDMC4yQEXeAmehYmcGExwyjK0L4oKJK91GE,6822
|
||||
sqlalchemy/orm/identity.py,sha256=SS1rHYGwGYBPi2_RxTNC1pKYAmM_FGDTFfq9vPI9qco,10802
|
||||
sqlalchemy/orm/instrumentation.py,sha256=z-2huzGB_BGe2X4Qfu7rUKvYTCqi2YtMiBd4xLkoEuw,19178
|
||||
sqlalchemy/orm/interfaces.py,sha256=AcMKh9IXf6P1id9cyqwo_eYOnaVBpLG-eJLr5XJy-jc,26626
|
||||
sqlalchemy/orm/loading.py,sha256=FfYKa6XjcVj0sqwSEkQs9VziMA54E-DFJm5CCY8cN_0,34855
|
||||
sqlalchemy/orm/mapper.py,sha256=PPTmTWVVIKhhEUzXhHJVuKtn0iCnSk8KCXKdZpqqeoQ,134392
|
||||
sqlalchemy/orm/path_registry.py,sha256=kS6ZP7c-gHt0M17RhZImZirJctAohQDHYNVZEcxxGWI,14203
|
||||
sqlalchemy/orm/persistence.py,sha256=vk8OJY3SONKx7Ylu_lT7ZU-jIASNKuhUXaUIf4NVmcA,68073
|
||||
sqlalchemy/orm/properties.py,sha256=H9883ECMBSN6xMi411nJZuKH9iyYX2IVAnInE7luVj0,13069
|
||||
sqlalchemy/orm/query.py,sha256=g-uvTtUOfaVUYk3xjie6rZ2HtOV1lwBtny0E-UlOQiY,186442
|
||||
sqlalchemy/orm/relationships.py,sha256=YYRycNoWNhWsAOwnwJR0zQPtLneXYSiQ-f7XjcVJVaI,141581
|
||||
sqlalchemy/orm/scoping.py,sha256=-8rmidcl_VcY6_F1FLM0UHl23IgliQMhfn-0P-VcWGU,6619
|
||||
sqlalchemy/orm/session.py,sha256=g2f60o8Oy0QaS-j7dpmjcx76llr3L8K9_KxxmBLWQJ8,135243
|
||||
sqlalchemy/orm/state.py,sha256=BKNajH3NFxoM9CXKdV9fttpCZOJ1BUpCUN9eECuIu00,31763
|
||||
sqlalchemy/orm/strategies.py,sha256=U0QkBemiOFotZI-JhgbDilFOa9_6eePXj-lTs9CjnQU,89843
|
||||
sqlalchemy/orm/strategy_options.py,sha256=MPkIUJcr079EHRTm_UlCTYhVLIenh-ztwi8msW2CMf4,59567
|
||||
sqlalchemy/orm/sync.py,sha256=C6cVB2X6Roqv2HrRIiFOBRpnEgr6oEfGNS8oFCmf4-A,5990
|
||||
sqlalchemy/orm/unitofwork.py,sha256=sDcZMx9xYDfRi2PZo9-REyribKxWwhpeMm7d9clG7Qk,25474
|
||||
sqlalchemy/orm/util.py,sha256=HYD2mztX2FqmKUd3kskUGjRlEEAoovcowAZSBMk_7Gg,46790
|
||||
sqlalchemy/pool/__init__.py,sha256=WCEmpV5aEJTcftwapYTqx9TfPHwTx8EQ2j9Gs2wyoMY,1535
|
||||
sqlalchemy/pool/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/pool/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/pool/__pycache__/dbapi_proxy.cpython-36.pyc,,
|
||||
sqlalchemy/pool/__pycache__/impl.cpython-36.pyc,,
|
||||
sqlalchemy/pool/base.py,sha256=YU_pCjvSI9JjRawKXFSB2-FgJYqQ_CO1xbM6Kd5VVoA,37557
|
||||
sqlalchemy/pool/dbapi_proxy.py,sha256=YEw0RXKNFbbvgaHwR3ZXwrPowmk1zMkCe52jdSvI9bk,4470
|
||||
sqlalchemy/pool/impl.py,sha256=j-DCjlJsBWKHuco7749ukokFeSmULFMAgwVwbc1p1wc,15446
|
||||
sqlalchemy/processors.py,sha256=olrOO9fpgp87fMHB2-pJle-CI4QSuaLZcFM7Xnop2Pk,5920
|
||||
sqlalchemy/schema.py,sha256=Ff6Tx0-UY35XGpBPuBI6RrutTiv4TgY28-TdCMcCrPA,2526
|
||||
sqlalchemy/sql/__init__.py,sha256=ued_W8_shnIaN3mFS2Il_GMLD1xmWbyCXHQPYJf8YNc,3895
|
||||
sqlalchemy/sql/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/annotation.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/base.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/compiler.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/crud.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/ddl.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/default_comparator.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/dml.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/elements.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/expression.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/functions.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/naming.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/operators.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/schema.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/selectable.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/sqltypes.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/type_api.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/util.cpython-36.pyc,,
|
||||
sqlalchemy/sql/__pycache__/visitors.cpython-36.pyc,,
|
||||
sqlalchemy/sql/annotation.py,sha256=Rc20evisG_XwBNK_uov9VOFOdO3nYLFXseihjbWNGvs,6935
|
||||
sqlalchemy/sql/base.py,sha256=opPc1CggSMq8zku3JbLIPOIDNB00D6be2c7ulW8-k1Q,22390
|
||||
sqlalchemy/sql/compiler.py,sha256=yFuSjpT0qScGO7YoJws1LcTkpoM_hWoxlKi-Bp2npoE,132128
|
||||
sqlalchemy/sql/crud.py,sha256=abF4fOtpaauGSVyXjimnHNzjTMMnzJ5rqdHjweXlzhI,26690
|
||||
sqlalchemy/sql/ddl.py,sha256=bJ_-gdduAOibfpXVA-oNuqX2Txmxcc1lGAmTDk_fnpM,42687
|
||||
sqlalchemy/sql/default_comparator.py,sha256=O0k9mS7avmu7BKeB5bhyw5TVJIAyowaH28ZqJUtEPNE,12609
|
||||
sqlalchemy/sql/dml.py,sha256=4P0XqKiKsfJtj2Pxn_4xcQEsOvlj1eoPpUpyesCfqL0,36624
|
||||
sqlalchemy/sql/elements.py,sha256=deWSv8q9N4gtWVxkSpxBNwH5xkxgB10kiLIL6se2ZIA,166190
|
||||
sqlalchemy/sql/expression.py,sha256=5MZNgs6Mv5V0K-f9gyVIsh6eUMI61O1qdZxJCJdu1BY,9476
|
||||
sqlalchemy/sql/functions.py,sha256=nUdRQn90JSnX5qUC9__JTDtcohXyO_UH6w111rRWvpM,37002
|
||||
sqlalchemy/sql/naming.py,sha256=p-BVZd3W_KIcKb7KksYWP4jBf3c1lgbfoiEd8K5t_qI,6071
|
||||
sqlalchemy/sql/operators.py,sha256=-D0mXfiROxhMh4uFFbhLf5qAyyURaqOclZD8OYk8HgQ,44066
|
||||
sqlalchemy/sql/schema.py,sha256=FZU_KqRx6DqMJBNU2Kx33_UNYYiTAxymBN6JmiKA8v0,180818
|
||||
sqlalchemy/sql/selectable.py,sha256=egPfU9Lo5sLFiG2WXzSwTfeqVxaJsZkT5BksMKnSHPU,143622
|
||||
sqlalchemy/sql/sqltypes.py,sha256=kC4gxQfOjPlGu5b2Q5O0ndmLAT6dGQnwNxueLbFYchI,104685
|
||||
sqlalchemy/sql/type_api.py,sha256=L4Q6hDHDa3jfo4gsZMOXU9pY-lf4qtwcj_2EEyyriiM,53731
|
||||
sqlalchemy/sql/util.py,sha256=0yHwgYlOJ6xmqjmsivD9BZWplq4w-ECtNvxRN2Fhidg,31006
|
||||
sqlalchemy/sql/visitors.py,sha256=npwoEq-h8u8LAAN_OIX9oBf0Htb35pORff1xwA0m96Q,16426
|
||||
sqlalchemy/testing/__init__.py,sha256=F6cKUmiLxkeYOlBjI4D8tKbVF12sJ1R5x5vKbQa7yZM,2941
|
||||
sqlalchemy/testing/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertions.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertsql.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/config.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/engines.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/entities.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/exclusions.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/fixtures.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/mock.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/pickleable.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/profiling.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/provision.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/replay_fixture.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/requirements.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/schema.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/util.cpython-36.pyc,,
|
||||
sqlalchemy/testing/__pycache__/warnings.cpython-36.pyc,,
|
||||
sqlalchemy/testing/assertions.py,sha256=vUPrnQv_HmJ2QZhAPkD9Vky25SgCyknIizSk7e3VF_g,22978
|
||||
sqlalchemy/testing/assertsql.py,sha256=l-f0MkUFKCnwQ1cj8g9WqXT-tjPIm0sLNUS0IYgMJX0,14009
|
||||
sqlalchemy/testing/config.py,sha256=vOKsy7RCVgUsjUNlayfOZ0AaLBNm_suTTOY1q721714,5693
|
||||
sqlalchemy/testing/engines.py,sha256=SxG2GXuYZgE-pSwKcs5CPvenzyff-qZVjtWz3IQiAak,10910
|
||||
sqlalchemy/testing/entities.py,sha256=2LJdshRUYWMvazvgUrAcGkTeWEnNoiI_y1NSKcfsRSw,3312
|
||||
sqlalchemy/testing/exclusions.py,sha256=GuPlFXj7NxPDJfrYbPGfuOdRYFfO28Tp945gH42FnXY,13490
|
||||
sqlalchemy/testing/fixtures.py,sha256=9jzKNlfPsD3P3UbQmPaTzPHLkuoAhXo5XNmCooT32hU,15556
|
||||
sqlalchemy/testing/mock.py,sha256=iomOaukRMIAdt2Y3TxUcmvVH-1SMwkwwIJJZvTxyblM,925
|
||||
sqlalchemy/testing/pickleable.py,sha256=Mnym0utxflQ0Htlx5hbPxTA9t_RFpuhB_zBFNuk7q-s,2834
|
||||
sqlalchemy/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/testing/plugin/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-36.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-36.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-36.pyc,,
|
||||
sqlalchemy/testing/plugin/bootstrap.py,sha256=q6FSeGdWtNutyrrwWyDdUXvVDPwI7owgiZ7sXjZUw4I,1513
|
||||
sqlalchemy/testing/plugin/plugin_base.py,sha256=9FOAdlEnudyx5VuLMA7Q15d92SWjY6LAIR1_-Tw9On0,21095
|
||||
sqlalchemy/testing/plugin/pytestplugin.py,sha256=JIiimunDVCq7stuVXBvd9FnUPc0odqAIi9kxa7AiW9M,16570
|
||||
sqlalchemy/testing/profiling.py,sha256=dHC5hL9sBUMbXTMETOH9p9s0Y7_LvkF9xTuY8cCqzt8,9018
|
||||
sqlalchemy/testing/provision.py,sha256=qfQ8i06SomPPu8Bffe68BIWDa6s44flfMpeK3fFp4DM,5698
|
||||
sqlalchemy/testing/replay_fixture.py,sha256=pQde_cIatESSw7hDM5UewB5M-K0OdqOxu7aRy-j1Yhs,6084
|
||||
sqlalchemy/testing/requirements.py,sha256=PIYtE2j1F6kEJYMZ0NGJe_358kcXjcfPTfy891G-RVE,33825
|
||||
sqlalchemy/testing/schema.py,sha256=mEBW8JKWNJoTA0dqL4IkQaLzSixsktK5T3e7SqOhX-s,3828
|
||||
sqlalchemy/testing/suite/__init__.py,sha256=y5Xv66ZdG_zCWEXRIWDWRoFtFIBivVa27Sqb6Ve_3wM,368
|
||||
sqlalchemy/testing/suite/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_cte.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_insert.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_results.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_select.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_types.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-36.pyc,,
|
||||
sqlalchemy/testing/suite/test_cte.py,sha256=f2NFiZf5UNrlERJ1GT6ZNBzKxYa8vUgiAqgNJXQA4LY,7012
|
||||
sqlalchemy/testing/suite/test_ddl.py,sha256=uzAg5Ur03bcc4XNZLWvxDH0GjBiRehm2zjaieeJ7A04,2988
|
||||
sqlalchemy/testing/suite/test_dialect.py,sha256=HDSS7zVRNaXmVpMjFXzkBNQ5t2GxNHZMH-dA4wPx-hk,7093
|
||||
sqlalchemy/testing/suite/test_insert.py,sha256=PDju2SJ5zxmBL36FfJHjKx1-ncvAQnB809c5Jkj6gLI,9991
|
||||
sqlalchemy/testing/suite/test_reflection.py,sha256=FcuT6sqAlhVnlWJLT-D92wA1NXBi8RXizKEVtYushYY,49802
|
||||
sqlalchemy/testing/suite/test_results.py,sha256=ySfNVlqK6ne4CaGL06THvNY4fD0yDYeaKqUizpxhCto,11321
|
||||
sqlalchemy/testing/suite/test_select.py,sha256=h_cNuzcolB5qEEUvd68TH_Sil5VZhC54dk63uFSuaq4,25198
|
||||
sqlalchemy/testing/suite/test_sequence.py,sha256=OdhYzm7YLAAuAwI48NZ8GwpkDPYnKmjLik5Bgv6FLaI,4817
|
||||
sqlalchemy/testing/suite/test_types.py,sha256=Ez-HJYvWmRbtmn6FcPeAhp7AYfZdmA96j4zkGjWzx-U,38359
|
||||
sqlalchemy/testing/suite/test_update_delete.py,sha256=hI1arMUwZuAjX2QoyzVCFIddKZvCyYUjOHrJo85wlio,1547
|
||||
sqlalchemy/testing/util.py,sha256=yy46wRMK3dNcvh5byIIfQSReIYzRti_jIANGaStkByU,10543
|
||||
sqlalchemy/testing/warnings.py,sha256=MD_Vq8Zzd0W2vJm1N4m4ZCf5Stbd0HYJVnK20tkbV6Q,1732
|
||||
sqlalchemy/types.py,sha256=F71xo2piMj4B2uHq5t8Xy8fVzYn0iMqWZbTJ6NQzi3A,3494
|
||||
sqlalchemy/util/__init__.py,sha256=gfTZDojrUyCMLxl_C6FmN4XsIKNKV6AlSBKJUrMCuMs,6866
|
||||
sqlalchemy/util/__pycache__/__init__.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/_collections.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/_preloaded.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/compat.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/deprecations.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/langhelpers.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/queue.cpython-36.pyc,,
|
||||
sqlalchemy/util/__pycache__/topological.cpython-36.pyc,,
|
||||
sqlalchemy/util/_collections.py,sha256=sR6_6vgJRhTAFVEg-Yv8BZuSigJLR4VOzAvkX7snwHw,30320
|
||||
sqlalchemy/util/_preloaded.py,sha256=O85O9xixY7gwlkJBILOZxC8vEnhQymOpt3uX7oQ_HOg,5973
|
||||
sqlalchemy/util/compat.py,sha256=SC5nIQlWHLW6AK4BCYKwSZnxx3ewj4tWJE0-g2DvtDw,17494
|
||||
sqlalchemy/util/deprecations.py,sha256=RyjUfbW-QcQUsjwR7gF1gtJTnNjTM1vx0wtcMbzRaWk,7733
|
||||
sqlalchemy/util/langhelpers.py,sha256=W7CX9U_-oCslZn02q1CUO4qAuvcHaZpKC5UtiKRn1ZU,49352
|
||||
sqlalchemy/util/queue.py,sha256=u58ho8_fklfw-JIuFW39DNiD9dNE91i-Iqj-ZAWlGAk,7036
|
||||
sqlalchemy/util/topological.py,sha256=XgC6Z9DhSlWwBsvkiRZ3YlvpzVNct8p80xkjc124p9w,2864
|
@ -0,0 +1 @@
|
||||
sqlalchemy
|
@ -1,5 +1,9 @@
|
||||
Wheel-Version: 1.0
|
||||
<<<<<<< HEAD:venv/Lib/site-packages/aiohttp-3.6.3.dist-info/WHEEL
|
||||
Generator: bdist_wheel (0.35.1)
|
||||
=======
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
>>>>>>> origin/rewrite3.0:venv/Lib/site-packages/SQLAlchemy-1.3.22.dist-info/WHEEL
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp36-cp36m-win_amd64
|
||||
|
||||
|
10
venv/Lib/site-packages/apscheduler/__init__.py
Normal file
10
venv/Lib/site-packages/apscheduler/__init__.py
Normal 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
|
94
venv/Lib/site-packages/apscheduler/events.py
Normal file
94
venv/Lib/site-packages/apscheduler/events.py
Normal 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', 'JobSubmissionEvent')
|
||||
|
||||
|
||||
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
|
59
venv/Lib/site-packages/apscheduler/executors/asyncio.py
Normal file
59
venv/Lib/site-packages/apscheduler/executors/asyncio.py
Normal file
@ -0,0 +1,59 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
from apscheduler.executors.base import BaseExecutor, run_job
|
||||
from apscheduler.util import iscoroutinefunction_partial
|
||||
|
||||
try:
|
||||
from apscheduler.executors.base_py3 import run_coroutine_job
|
||||
except ImportError:
|
||||
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_partial(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)
|
146
venv/Lib/site-packages/apscheduler/executors/base.py
Normal file
146
venv/Lib/site-packages/apscheduler/executors/base.py
Normal 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
|
41
venv/Lib/site-packages/apscheduler/executors/base_py3.py
Normal file
41
venv/Lib/site-packages/apscheduler/executors/base_py3.py
Normal 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
|
20
venv/Lib/site-packages/apscheduler/executors/debug.py
Normal file
20
venv/Lib/site-packages/apscheduler/executors/debug.py
Normal 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)
|
30
venv/Lib/site-packages/apscheduler/executors/gevent.py
Normal file
30
venv/Lib/site-packages/apscheduler/executors/gevent.py
Normal 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)
|
54
venv/Lib/site-packages/apscheduler/executors/pool.py
Normal file
54
venv/Lib/site-packages/apscheduler/executors/pool.py
Normal 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)
|
54
venv/Lib/site-packages/apscheduler/executors/tornado.py
Normal file
54
venv/Lib/site-packages/apscheduler/executors/tornado.py
Normal 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 apscheduler.executors.base_py3 import run_coroutine_job
|
||||
from apscheduler.util import iscoroutinefunction_partial
|
||||
except ImportError:
|
||||
def iscoroutinefunction_partial(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_partial(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)
|
25
venv/Lib/site-packages/apscheduler/executors/twisted.py
Normal file
25
venv/Lib/site-packages/apscheduler/executors/twisted.py
Normal 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)
|
301
venv/Lib/site-packages/apscheduler/job.py
Normal file
301
venv/Lib/site-packages/apscheduler/job.py
Normal file
@ -0,0 +1,301 @@
|
||||
from inspect import ismethod, isclass
|
||||
from uuid import uuid4
|
||||
|
||||
import six
|
||||
|
||||
from apscheduler.triggers.base import BaseTrigger
|
||||
from apscheduler.util import (
|
||||
ref_to_obj, obj_to_ref, datetime_repr, repr_escape, get_callable_name, check_callable_args,
|
||||
convert_to_datetime)
|
||||
|
||||
try:
|
||||
from collections.abc import Iterable, Mapping
|
||||
except ImportError:
|
||||
from collections import Iterable, Mapping
|
||||
|
||||
|
||||
class Job(object):
|
||||
"""
|
||||
Contains the options given when scheduling callables and its current schedule and other state.
|
||||
This class should never be instantiated by the user.
|
||||
|
||||
:var str id: the unique identifier of this job
|
||||
:var str name: the description of this job
|
||||
:var func: the callable to execute
|
||||
:var tuple|list args: positional arguments to the callable
|
||||
:var dict kwargs: keyword arguments to the callable
|
||||
:var bool coalesce: whether to only run the job once when several run times are due
|
||||
:var trigger: the trigger object that controls the schedule of this job
|
||||
:var str executor: the name of the executor that will run this job
|
||||
:var int misfire_grace_time: the time (in seconds) how much this job's execution is allowed to
|
||||
be late
|
||||
:var int max_instances: the maximum number of concurrently executing instances allowed for this
|
||||
job
|
||||
:var datetime.datetime next_run_time: the next scheduled run time of this job
|
||||
|
||||
.. note::
|
||||
The ``misfire_grace_time`` has some non-obvious effects on job execution. See the
|
||||
:ref:`missed-job-executions` section in the documentation for an in-depth explanation.
|
||||
"""
|
||||
|
||||
__slots__ = ('_scheduler', '_jobstore_alias', 'id', 'trigger', 'executor', 'func', 'func_ref',
|
||||
'args', 'kwargs', 'name', 'misfire_grace_time', 'coalesce', 'max_instances',
|
||||
'next_run_time')
|
||||
|
||||
def __init__(self, scheduler, id=None, **kwargs):
|
||||
super(Job, self).__init__()
|
||||
self._scheduler = scheduler
|
||||
self._jobstore_alias = None
|
||||
self._modify(id=id or uuid4().hex, **kwargs)
|
||||
|
||||
def modify(self, **changes):
|
||||
"""
|
||||
Makes the given changes to this job and saves it in the associated job store.
|
||||
|
||||
Accepted keyword arguments are the same as the variables on this class.
|
||||
|
||||
.. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.modify_job`
|
||||
|
||||
:return Job: this job instance
|
||||
|
||||
"""
|
||||
self._scheduler.modify_job(self.id, self._jobstore_alias, **changes)
|
||||
return self
|
||||
|
||||
def reschedule(self, trigger, **trigger_args):
|
||||
"""
|
||||
Shortcut for switching the trigger on this job.
|
||||
|
||||
.. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.reschedule_job`
|
||||
|
||||
:return Job: this job instance
|
||||
|
||||
"""
|
||||
self._scheduler.reschedule_job(self.id, self._jobstore_alias, trigger, **trigger_args)
|
||||
return self
|
||||
|
||||
def pause(self):
|
||||
"""
|
||||
Temporarily suspend the execution of this job.
|
||||
|
||||
.. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.pause_job`
|
||||
|
||||
:return Job: this job instance
|
||||
|
||||
"""
|
||||
self._scheduler.pause_job(self.id, self._jobstore_alias)
|
||||
return self
|
||||
|
||||
def resume(self):
|
||||
"""
|
||||
Resume the schedule of this job if previously paused.
|
||||
|
||||
.. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.resume_job`
|
||||
|
||||
:return Job: this job instance
|
||||
|
||||
"""
|
||||
self._scheduler.resume_job(self.id, self._jobstore_alias)
|
||||
return self
|
||||
|
||||
def remove(self):
|
||||
"""
|
||||
Unschedules this job and removes it from its associated job store.
|
||||
|
||||
.. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.remove_job`
|
||||
|
||||
"""
|
||||
self._scheduler.remove_job(self.id, self._jobstore_alias)
|
||||
|
||||
@property
|
||||
def pending(self):
|
||||
"""
|
||||
Returns ``True`` if the referenced job is still waiting to be added to its designated job
|
||||
store.
|
||||
|
||||
"""
|
||||
return self._jobstore_alias is None
|
||||
|
||||
#
|
||||
# Private API
|
||||
#
|
||||
|
||||
def _get_run_times(self, now):
|
||||
"""
|
||||
Computes the scheduled run times between ``next_run_time`` and ``now`` (inclusive).
|
||||
|
||||
:type now: datetime.datetime
|
||||
:rtype: list[datetime.datetime]
|
||||
|
||||
"""
|
||||
run_times = []
|
||||
next_run_time = self.next_run_time
|
||||
while next_run_time and next_run_time <= now:
|
||||
run_times.append(next_run_time)
|
||||
next_run_time = self.trigger.get_next_fire_time(next_run_time, now)
|
||||
|
||||
return run_times
|
||||
|
||||
def _modify(self, **changes):
|
||||
"""
|
||||
Validates the changes to the Job and makes the modifications if and only if all of them
|
||||
validate.
|
||||
|
||||
"""
|
||||
approved = {}
|
||||
|
||||
if 'id' in changes:
|
||||
value = changes.pop('id')
|
||||
if not isinstance(value, six.string_types):
|
||||
raise TypeError("id must be a nonempty string")
|
||||
if hasattr(self, 'id'):
|
||||
raise ValueError('The job ID may not be changed')
|
||||
approved['id'] = value
|
||||
|
||||
if 'func' in changes or 'args' in changes or 'kwargs' in changes:
|
||||
func = changes.pop('func') if 'func' in changes else self.func
|
||||
args = changes.pop('args') if 'args' in changes else self.args
|
||||
kwargs = changes.pop('kwargs') if 'kwargs' in changes else self.kwargs
|
||||
|
||||
if isinstance(func, six.string_types):
|
||||
func_ref = func
|
||||
func = ref_to_obj(func)
|
||||
elif callable(func):
|
||||
try:
|
||||
func_ref = obj_to_ref(func)
|
||||
except ValueError:
|
||||
# If this happens, this Job won't be serializable
|
||||
func_ref = None
|
||||
else:
|
||||
raise TypeError('func must be a callable or a textual reference to one')
|
||||
|
||||
if not hasattr(self, 'name') and changes.get('name', None) is None:
|
||||
changes['name'] = get_callable_name(func)
|
||||
|
||||
if isinstance(args, six.string_types) or not isinstance(args, Iterable):
|
||||
raise TypeError('args must be a non-string iterable')
|
||||
if isinstance(kwargs, six.string_types) or not isinstance(kwargs, Mapping):
|
||||
raise TypeError('kwargs must be a dict-like object')
|
||||
|
||||
check_callable_args(func, args, kwargs)
|
||||
|
||||
approved['func'] = func
|
||||
approved['func_ref'] = func_ref
|
||||
approved['args'] = args
|
||||
approved['kwargs'] = kwargs
|
||||
|
||||
if 'name' in changes:
|
||||
value = changes.pop('name')
|
||||
if not value or not isinstance(value, six.string_types):
|
||||
raise TypeError("name must be a nonempty string")
|
||||
approved['name'] = value
|
||||
|
||||
if 'misfire_grace_time' in changes:
|
||||
value = changes.pop('misfire_grace_time')
|
||||
if value is not None and (not isinstance(value, six.integer_types) or value <= 0):
|
||||
raise TypeError('misfire_grace_time must be either None or a positive integer')
|
||||
approved['misfire_grace_time'] = value
|
||||
|
||||
if 'coalesce' in changes:
|
||||
value = bool(changes.pop('coalesce'))
|
||||
approved['coalesce'] = value
|
||||
|
||||
if 'max_instances' in changes:
|
||||
value = changes.pop('max_instances')
|
||||
if not isinstance(value, six.integer_types) or value <= 0:
|
||||
raise TypeError('max_instances must be a positive integer')
|
||||
approved['max_instances'] = value
|
||||
|
||||
if 'trigger' in changes:
|
||||
trigger = changes.pop('trigger')
|
||||
if not isinstance(trigger, BaseTrigger):
|
||||
raise TypeError('Expected a trigger instance, got %s instead' %
|
||||
trigger.__class__.__name__)
|
||||
|
||||
approved['trigger'] = trigger
|
||||
|
||||
if 'executor' in changes:
|
||||
value = changes.pop('executor')
|
||||
if not isinstance(value, six.string_types):
|
||||
raise TypeError('executor must be a string')
|
||||
approved['executor'] = value
|
||||
|
||||
if 'next_run_time' in changes:
|
||||
value = changes.pop('next_run_time')
|
||||
approved['next_run_time'] = convert_to_datetime(value, self._scheduler.timezone,
|
||||
'next_run_time')
|
||||
|
||||
if changes:
|
||||
raise AttributeError('The following are not modifiable attributes of Job: %s' %
|
||||
', '.join(changes))
|
||||
|
||||
for key, value in six.iteritems(approved):
|
||||
setattr(self, key, value)
|
||||
|
||||
def __getstate__(self):
|
||||
# Don't allow this Job to be serialized if the function reference could not be determined
|
||||
if not self.func_ref:
|
||||
raise ValueError(
|
||||
'This Job cannot be serialized since the reference to its callable (%r) could not '
|
||||
'be determined. Consider giving a textual reference (module:function name) '
|
||||
'instead.' % (self.func,))
|
||||
|
||||
# Instance methods cannot survive serialization as-is, so store the "self" argument
|
||||
# explicitly
|
||||
if ismethod(self.func) and not isclass(self.func.__self__):
|
||||
args = (self.func.__self__,) + tuple(self.args)
|
||||
else:
|
||||
args = self.args
|
||||
|
||||
return {
|
||||
'version': 1,
|
||||
'id': self.id,
|
||||
'func': self.func_ref,
|
||||
'trigger': self.trigger,
|
||||
'executor': self.executor,
|
||||
'args': args,
|
||||
'kwargs': self.kwargs,
|
||||
'name': self.name,
|
||||
'misfire_grace_time': self.misfire_grace_time,
|
||||
'coalesce': self.coalesce,
|
||||
'max_instances': self.max_instances,
|
||||
'next_run_time': self.next_run_time
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state.get('version', 1) > 1:
|
||||
raise ValueError('Job has version %s, but only version 1 can be handled' %
|
||||
state['version'])
|
||||
|
||||
self.id = state['id']
|
||||
self.func_ref = state['func']
|
||||
self.func = ref_to_obj(self.func_ref)
|
||||
self.trigger = state['trigger']
|
||||
self.executor = state['executor']
|
||||
self.args = state['args']
|
||||
self.kwargs = state['kwargs']
|
||||
self.name = state['name']
|
||||
self.misfire_grace_time = state['misfire_grace_time']
|
||||
self.coalesce = state['coalesce']
|
||||
self.max_instances = state['max_instances']
|
||||
self.next_run_time = state['next_run_time']
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Job):
|
||||
return self.id == other.id
|
||||
return NotImplemented
|
||||
|
||||
def __repr__(self):
|
||||
return '<Job (id=%s name=%s)>' % (repr_escape(self.id), repr_escape(self.name))
|
||||
|
||||
def __str__(self):
|
||||
return repr_escape(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
if hasattr(self, 'next_run_time'):
|
||||
status = ('next run at: ' + datetime_repr(self.next_run_time) if
|
||||
self.next_run_time else 'paused')
|
||||
else:
|
||||
status = 'pending'
|
||||
|
||||
return u'%s (trigger: %s, %s)' % (self.name, self.trigger, status)
|
143
venv/Lib/site-packages/apscheduler/jobstores/base.py
Normal file
143
venv/Lib/site-packages/apscheduler/jobstores/base.py
Normal file
@ -0,0 +1,143 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class JobLookupError(KeyError):
|
||||
"""Raised when the job store cannot find a job for update or removal."""
|
||||
|
||||
def __init__(self, job_id):
|
||||
super(JobLookupError, self).__init__(u'No job by the id of %s was found' % job_id)
|
||||
|
||||
|
||||
class ConflictingIdError(KeyError):
|
||||
"""Raised when the uniqueness of job IDs is being violated."""
|
||||
|
||||
def __init__(self, job_id):
|
||||
super(ConflictingIdError, self).__init__(
|
||||
u'Job identifier (%s) conflicts with an existing job' % job_id)
|
||||
|
||||
|
||||
class TransientJobError(ValueError):
|
||||
"""
|
||||
Raised when an attempt to add transient (with no func_ref) job to a persistent job store is
|
||||
detected.
|
||||
"""
|
||||
|
||||
def __init__(self, job_id):
|
||||
super(TransientJobError, self).__init__(
|
||||
u'Job (%s) cannot be added to this job store because a reference to the callable '
|
||||
u'could not be determined.' % job_id)
|
||||
|
||||
|
||||
class BaseJobStore(six.with_metaclass(ABCMeta)):
|
||||
"""Abstract base class that defines the interface that every job store must implement."""
|
||||
|
||||
_scheduler = None
|
||||
_alias = None
|
||||
_logger = logging.getLogger('apscheduler.jobstores')
|
||||
|
||||
def start(self, scheduler, alias):
|
||||
"""
|
||||
Called by the scheduler when the scheduler is being started or when the job store is being
|
||||
added to an already running scheduler.
|
||||
|
||||
:param apscheduler.schedulers.base.BaseScheduler scheduler: the scheduler that is starting
|
||||
this job store
|
||||
:param str|unicode alias: alias of this job store as it was assigned to the scheduler
|
||||
"""
|
||||
|
||||
self._scheduler = scheduler
|
||||
self._alias = alias
|
||||
self._logger = logging.getLogger('apscheduler.jobstores.%s' % alias)
|
||||
|
||||
def shutdown(self):
|
||||
"""Frees any resources still bound to this job store."""
|
||||
|
||||
def _fix_paused_jobs_sorting(self, jobs):
|
||||
for i, job in enumerate(jobs):
|
||||
if job.next_run_time is not None:
|
||||
if i > 0:
|
||||
paused_jobs = jobs[:i]
|
||||
del jobs[:i]
|
||||
jobs.extend(paused_jobs)
|
||||
break
|
||||
|
||||
@abstractmethod
|
||||
def lookup_job(self, job_id):
|
||||
"""
|
||||
Returns a specific job, or ``None`` if it isn't found..
|
||||
|
||||
The job store is responsible for setting the ``scheduler`` and ``jobstore`` attributes of
|
||||
the returned job to point to the scheduler and itself, respectively.
|
||||
|
||||
:param str|unicode job_id: identifier of the job
|
||||
:rtype: Job
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_due_jobs(self, now):
|
||||
"""
|
||||
Returns the list of jobs that have ``next_run_time`` earlier or equal to ``now``.
|
||||
The returned jobs must be sorted by next run time (ascending).
|
||||
|
||||
:param datetime.datetime now: the current (timezone aware) datetime
|
||||
:rtype: list[Job]
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_next_run_time(self):
|
||||
"""
|
||||
Returns the earliest run time of all the jobs stored in this job store, or ``None`` if
|
||||
there are no active jobs.
|
||||
|
||||
:rtype: datetime.datetime
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_all_jobs(self):
|
||||
"""
|
||||
Returns a list of all jobs in this job store.
|
||||
The returned jobs should be sorted by next run time (ascending).
|
||||
Paused jobs (next_run_time == None) should be sorted last.
|
||||
|
||||
The job store is responsible for setting the ``scheduler`` and ``jobstore`` attributes of
|
||||
the returned jobs to point to the scheduler and itself, respectively.
|
||||
|
||||
:rtype: list[Job]
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_job(self, job):
|
||||
"""
|
||||
Adds the given job to this store.
|
||||
|
||||
:param Job job: the job to add
|
||||
:raises ConflictingIdError: if there is another job in this store with the same ID
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_job(self, job):
|
||||
"""
|
||||
Replaces the job in the store with the given newer version.
|
||||
|
||||
:param Job job: the job to update
|
||||
:raises JobLookupError: if the job does not exist
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def remove_job(self, job_id):
|
||||
"""
|
||||
Removes the given job from this store.
|
||||
|
||||
:param str|unicode job_id: identifier of the job
|
||||
:raises JobLookupError: if the job does not exist
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def remove_all_jobs(self):
|
||||
"""Removes all jobs from this store."""
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__class__.__name__
|
108
venv/Lib/site-packages/apscheduler/jobstores/memory.py
Normal file
108
venv/Lib/site-packages/apscheduler/jobstores/memory.py
Normal file
@ -0,0 +1,108 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import datetime_to_utc_timestamp
|
||||
|
||||
|
||||
class MemoryJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in an array in RAM. Provides no persistence support.
|
||||
|
||||
Plugin alias: ``memory``
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(MemoryJobStore, self).__init__()
|
||||
# list of (job, timestamp), sorted by next_run_time and job id (ascending)
|
||||
self._jobs = []
|
||||
self._jobs_index = {} # id -> (job, timestamp) lookup table
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
return self._jobs_index.get(job_id, (None, None))[0]
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
now_timestamp = datetime_to_utc_timestamp(now)
|
||||
pending = []
|
||||
for job, timestamp in self._jobs:
|
||||
if timestamp is None or timestamp > now_timestamp:
|
||||
break
|
||||
pending.append(job)
|
||||
|
||||
return pending
|
||||
|
||||
def get_next_run_time(self):
|
||||
return self._jobs[0][0].next_run_time if self._jobs else None
|
||||
|
||||
def get_all_jobs(self):
|
||||
return [j[0] for j in self._jobs]
|
||||
|
||||
def add_job(self, job):
|
||||
if job.id in self._jobs_index:
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
timestamp = datetime_to_utc_timestamp(job.next_run_time)
|
||||
index = self._get_job_index(timestamp, job.id)
|
||||
self._jobs.insert(index, (job, timestamp))
|
||||
self._jobs_index[job.id] = (job, timestamp)
|
||||
|
||||
def update_job(self, job):
|
||||
old_job, old_timestamp = self._jobs_index.get(job.id, (None, None))
|
||||
if old_job is None:
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
# If the next run time has not changed, simply replace the job in its present index.
|
||||
# Otherwise, reinsert the job to the list to preserve the ordering.
|
||||
old_index = self._get_job_index(old_timestamp, old_job.id)
|
||||
new_timestamp = datetime_to_utc_timestamp(job.next_run_time)
|
||||
if old_timestamp == new_timestamp:
|
||||
self._jobs[old_index] = (job, new_timestamp)
|
||||
else:
|
||||
del self._jobs[old_index]
|
||||
new_index = self._get_job_index(new_timestamp, job.id)
|
||||
self._jobs.insert(new_index, (job, new_timestamp))
|
||||
|
||||
self._jobs_index[old_job.id] = (job, new_timestamp)
|
||||
|
||||
def remove_job(self, job_id):
|
||||
job, timestamp = self._jobs_index.get(job_id, (None, None))
|
||||
if job is None:
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
index = self._get_job_index(timestamp, job_id)
|
||||
del self._jobs[index]
|
||||
del self._jobs_index[job.id]
|
||||
|
||||
def remove_all_jobs(self):
|
||||
self._jobs = []
|
||||
self._jobs_index = {}
|
||||
|
||||
def shutdown(self):
|
||||
self.remove_all_jobs()
|
||||
|
||||
def _get_job_index(self, timestamp, job_id):
|
||||
"""
|
||||
Returns the index of the given job, or if it's not found, the index where the job should be
|
||||
inserted based on the given timestamp.
|
||||
|
||||
:type timestamp: int
|
||||
:type job_id: str
|
||||
|
||||
"""
|
||||
lo, hi = 0, len(self._jobs)
|
||||
timestamp = float('inf') if timestamp is None else timestamp
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
mid_job, mid_timestamp = self._jobs[mid]
|
||||
mid_timestamp = float('inf') if mid_timestamp is None else mid_timestamp
|
||||
if mid_timestamp > timestamp:
|
||||
hi = mid
|
||||
elif mid_timestamp < timestamp:
|
||||
lo = mid + 1
|
||||
elif mid_job.id > job_id:
|
||||
hi = mid
|
||||
elif mid_job.id < job_id:
|
||||
lo = mid + 1
|
||||
else:
|
||||
return mid
|
||||
|
||||
return lo
|
141
venv/Lib/site-packages/apscheduler/jobstores/mongodb.py
Normal file
141
venv/Lib/site-packages/apscheduler/jobstores/mongodb.py
Normal file
@ -0,0 +1,141 @@
|
||||
from __future__ import absolute_import
|
||||
import warnings
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime
|
||||
from apscheduler.job import Job
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: nocover
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from bson.binary import Binary
|
||||
from pymongo.errors import DuplicateKeyError
|
||||
from pymongo import MongoClient, ASCENDING
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('MongoDBJobStore requires PyMongo installed')
|
||||
|
||||
|
||||
class MongoDBJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in a MongoDB database. Any leftover keyword arguments are directly passed to
|
||||
pymongo's `MongoClient
|
||||
<http://api.mongodb.org/python/current/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient>`_.
|
||||
|
||||
Plugin alias: ``mongodb``
|
||||
|
||||
:param str database: database to store jobs in
|
||||
:param str collection: collection to store jobs in
|
||||
:param client: a :class:`~pymongo.mongo_client.MongoClient` instance to use instead of
|
||||
providing connection arguments
|
||||
:param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the
|
||||
highest available
|
||||
"""
|
||||
|
||||
def __init__(self, database='apscheduler', collection='jobs', client=None,
|
||||
pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args):
|
||||
super(MongoDBJobStore, self).__init__()
|
||||
self.pickle_protocol = pickle_protocol
|
||||
|
||||
if not database:
|
||||
raise ValueError('The "database" parameter must not be empty')
|
||||
if not collection:
|
||||
raise ValueError('The "collection" parameter must not be empty')
|
||||
|
||||
if client:
|
||||
self.client = maybe_ref(client)
|
||||
else:
|
||||
connect_args.setdefault('w', 1)
|
||||
self.client = MongoClient(**connect_args)
|
||||
|
||||
self.collection = self.client[database][collection]
|
||||
|
||||
def start(self, scheduler, alias):
|
||||
super(MongoDBJobStore, self).start(scheduler, alias)
|
||||
self.collection.ensure_index('next_run_time', sparse=True)
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
warnings.warn('The "connection" member is deprecated -- use "client" instead',
|
||||
DeprecationWarning)
|
||||
return self.client
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
document = self.collection.find_one(job_id, ['job_state'])
|
||||
return self._reconstitute_job(document['job_state']) if document else None
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
timestamp = datetime_to_utc_timestamp(now)
|
||||
return self._get_jobs({'next_run_time': {'$lte': timestamp}})
|
||||
|
||||
def get_next_run_time(self):
|
||||
document = self.collection.find_one({'next_run_time': {'$ne': None}},
|
||||
projection=['next_run_time'],
|
||||
sort=[('next_run_time', ASCENDING)])
|
||||
return utc_timestamp_to_datetime(document['next_run_time']) if document else None
|
||||
|
||||
def get_all_jobs(self):
|
||||
jobs = self._get_jobs({})
|
||||
self._fix_paused_jobs_sorting(jobs)
|
||||
return jobs
|
||||
|
||||
def add_job(self, job):
|
||||
try:
|
||||
self.collection.insert({
|
||||
'_id': job.id,
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': Binary(pickle.dumps(job.__getstate__(), self.pickle_protocol))
|
||||
})
|
||||
except DuplicateKeyError:
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
def update_job(self, job):
|
||||
changes = {
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': Binary(pickle.dumps(job.__getstate__(), self.pickle_protocol))
|
||||
}
|
||||
result = self.collection.update({'_id': job.id}, {'$set': changes})
|
||||
if result and result['n'] == 0:
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
def remove_job(self, job_id):
|
||||
result = self.collection.remove(job_id)
|
||||
if result and result['n'] == 0:
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
def remove_all_jobs(self):
|
||||
self.collection.remove()
|
||||
|
||||
def shutdown(self):
|
||||
self.client.close()
|
||||
|
||||
def _reconstitute_job(self, job_state):
|
||||
job_state = pickle.loads(job_state)
|
||||
job = Job.__new__(Job)
|
||||
job.__setstate__(job_state)
|
||||
job._scheduler = self._scheduler
|
||||
job._jobstore_alias = self._alias
|
||||
return job
|
||||
|
||||
def _get_jobs(self, conditions):
|
||||
jobs = []
|
||||
failed_job_ids = []
|
||||
for document in self.collection.find(conditions, ['_id', 'job_state'],
|
||||
sort=[('next_run_time', ASCENDING)]):
|
||||
try:
|
||||
jobs.append(self._reconstitute_job(document['job_state']))
|
||||
except BaseException:
|
||||
self._logger.exception('Unable to restore job "%s" -- removing it',
|
||||
document['_id'])
|
||||
failed_job_ids.append(document['_id'])
|
||||
|
||||
# Remove all the jobs we failed to restore
|
||||
if failed_job_ids:
|
||||
self.collection.remove({'_id': {'$in': failed_job_ids}})
|
||||
|
||||
return jobs
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s (client=%s)>' % (self.__class__.__name__, self.client)
|
150
venv/Lib/site-packages/apscheduler/jobstores/redis.py
Normal file
150
venv/Lib/site-packages/apscheduler/jobstores/redis.py
Normal file
@ -0,0 +1,150 @@
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime
|
||||
|
||||
from pytz import utc
|
||||
import six
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import datetime_to_utc_timestamp, utc_timestamp_to_datetime
|
||||
from apscheduler.job import Job
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: nocover
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from redis import Redis
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('RedisJobStore requires redis installed')
|
||||
|
||||
|
||||
class RedisJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in a Redis database. Any leftover keyword arguments are directly passed to redis's
|
||||
:class:`~redis.StrictRedis`.
|
||||
|
||||
Plugin alias: ``redis``
|
||||
|
||||
:param int db: the database number to store jobs in
|
||||
:param str jobs_key: key to store jobs in
|
||||
:param str run_times_key: key to store the jobs' run times in
|
||||
:param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the
|
||||
highest available
|
||||
"""
|
||||
|
||||
def __init__(self, db=0, jobs_key='apscheduler.jobs', run_times_key='apscheduler.run_times',
|
||||
pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args):
|
||||
super(RedisJobStore, self).__init__()
|
||||
|
||||
if db is None:
|
||||
raise ValueError('The "db" parameter must not be empty')
|
||||
if not jobs_key:
|
||||
raise ValueError('The "jobs_key" parameter must not be empty')
|
||||
if not run_times_key:
|
||||
raise ValueError('The "run_times_key" parameter must not be empty')
|
||||
|
||||
self.pickle_protocol = pickle_protocol
|
||||
self.jobs_key = jobs_key
|
||||
self.run_times_key = run_times_key
|
||||
self.redis = Redis(db=int(db), **connect_args)
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
job_state = self.redis.hget(self.jobs_key, job_id)
|
||||
return self._reconstitute_job(job_state) if job_state else None
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
timestamp = datetime_to_utc_timestamp(now)
|
||||
job_ids = self.redis.zrangebyscore(self.run_times_key, 0, timestamp)
|
||||
if job_ids:
|
||||
job_states = self.redis.hmget(self.jobs_key, *job_ids)
|
||||
return self._reconstitute_jobs(six.moves.zip(job_ids, job_states))
|
||||
return []
|
||||
|
||||
def get_next_run_time(self):
|
||||
next_run_time = self.redis.zrange(self.run_times_key, 0, 0, withscores=True)
|
||||
if next_run_time:
|
||||
return utc_timestamp_to_datetime(next_run_time[0][1])
|
||||
|
||||
def get_all_jobs(self):
|
||||
job_states = self.redis.hgetall(self.jobs_key)
|
||||
jobs = self._reconstitute_jobs(six.iteritems(job_states))
|
||||
paused_sort_key = datetime(9999, 12, 31, tzinfo=utc)
|
||||
return sorted(jobs, key=lambda job: job.next_run_time or paused_sort_key)
|
||||
|
||||
def add_job(self, job):
|
||||
if self.redis.hexists(self.jobs_key, job.id):
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
with self.redis.pipeline() as pipe:
|
||||
pipe.multi()
|
||||
pipe.hset(self.jobs_key, job.id, pickle.dumps(job.__getstate__(),
|
||||
self.pickle_protocol))
|
||||
if job.next_run_time:
|
||||
pipe.zadd(self.run_times_key,
|
||||
{job.id: datetime_to_utc_timestamp(job.next_run_time)})
|
||||
|
||||
pipe.execute()
|
||||
|
||||
def update_job(self, job):
|
||||
if not self.redis.hexists(self.jobs_key, job.id):
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
with self.redis.pipeline() as pipe:
|
||||
pipe.hset(self.jobs_key, job.id, pickle.dumps(job.__getstate__(),
|
||||
self.pickle_protocol))
|
||||
if job.next_run_time:
|
||||
pipe.zadd(self.run_times_key,
|
||||
{job.id: datetime_to_utc_timestamp(job.next_run_time)})
|
||||
else:
|
||||
pipe.zrem(self.run_times_key, job.id)
|
||||
|
||||
pipe.execute()
|
||||
|
||||
def remove_job(self, job_id):
|
||||
if not self.redis.hexists(self.jobs_key, job_id):
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
with self.redis.pipeline() as pipe:
|
||||
pipe.hdel(self.jobs_key, job_id)
|
||||
pipe.zrem(self.run_times_key, job_id)
|
||||
pipe.execute()
|
||||
|
||||
def remove_all_jobs(self):
|
||||
with self.redis.pipeline() as pipe:
|
||||
pipe.delete(self.jobs_key)
|
||||
pipe.delete(self.run_times_key)
|
||||
pipe.execute()
|
||||
|
||||
def shutdown(self):
|
||||
self.redis.connection_pool.disconnect()
|
||||
|
||||
def _reconstitute_job(self, job_state):
|
||||
job_state = pickle.loads(job_state)
|
||||
job = Job.__new__(Job)
|
||||
job.__setstate__(job_state)
|
||||
job._scheduler = self._scheduler
|
||||
job._jobstore_alias = self._alias
|
||||
return job
|
||||
|
||||
def _reconstitute_jobs(self, job_states):
|
||||
jobs = []
|
||||
failed_job_ids = []
|
||||
for job_id, job_state in job_states:
|
||||
try:
|
||||
jobs.append(self._reconstitute_job(job_state))
|
||||
except BaseException:
|
||||
self._logger.exception('Unable to restore job "%s" -- removing it', job_id)
|
||||
failed_job_ids.append(job_id)
|
||||
|
||||
# Remove all the jobs we failed to restore
|
||||
if failed_job_ids:
|
||||
with self.redis.pipeline() as pipe:
|
||||
pipe.hdel(self.jobs_key, *failed_job_ids)
|
||||
pipe.zrem(self.run_times_key, *failed_job_ids)
|
||||
pipe.execute()
|
||||
|
||||
return jobs
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % self.__class__.__name__
|
155
venv/Lib/site-packages/apscheduler/jobstores/rethinkdb.py
Normal file
155
venv/Lib/site-packages/apscheduler/jobstores/rethinkdb.py
Normal file
@ -0,0 +1,155 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime
|
||||
from apscheduler.job import Job
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: nocover
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from rethinkdb import RethinkDB
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('RethinkDBJobStore requires rethinkdb installed')
|
||||
|
||||
|
||||
class RethinkDBJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in a RethinkDB database. Any leftover keyword arguments are directly passed to
|
||||
rethinkdb's `RethinkdbClient <http://www.rethinkdb.com/api/#connect>`_.
|
||||
|
||||
Plugin alias: ``rethinkdb``
|
||||
|
||||
:param str database: database to store jobs in
|
||||
:param str collection: collection to store jobs in
|
||||
:param client: a :class:`rethinkdb.net.Connection` instance to use instead of providing
|
||||
connection arguments
|
||||
:param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the
|
||||
highest available
|
||||
"""
|
||||
|
||||
def __init__(self, database='apscheduler', table='jobs', client=None,
|
||||
pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args):
|
||||
super(RethinkDBJobStore, self).__init__()
|
||||
|
||||
if not database:
|
||||
raise ValueError('The "database" parameter must not be empty')
|
||||
if not table:
|
||||
raise ValueError('The "table" parameter must not be empty')
|
||||
|
||||
self.database = database
|
||||
self.table_name = table
|
||||
self.table = None
|
||||
self.client = client
|
||||
self.pickle_protocol = pickle_protocol
|
||||
self.connect_args = connect_args
|
||||
self.r = RethinkDB()
|
||||
self.conn = None
|
||||
|
||||
def start(self, scheduler, alias):
|
||||
super(RethinkDBJobStore, self).start(scheduler, alias)
|
||||
|
||||
if self.client:
|
||||
self.conn = maybe_ref(self.client)
|
||||
else:
|
||||
self.conn = self.r.connect(db=self.database, **self.connect_args)
|
||||
|
||||
if self.database not in self.r.db_list().run(self.conn):
|
||||
self.r.db_create(self.database).run(self.conn)
|
||||
|
||||
if self.table_name not in self.r.table_list().run(self.conn):
|
||||
self.r.table_create(self.table_name).run(self.conn)
|
||||
|
||||
if 'next_run_time' not in self.r.table(self.table_name).index_list().run(self.conn):
|
||||
self.r.table(self.table_name).index_create('next_run_time').run(self.conn)
|
||||
|
||||
self.table = self.r.db(self.database).table(self.table_name)
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
results = list(self.table.get_all(job_id).pluck('job_state').run(self.conn))
|
||||
return self._reconstitute_job(results[0]['job_state']) if results else None
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
return self._get_jobs(self.r.row['next_run_time'] <= datetime_to_utc_timestamp(now))
|
||||
|
||||
def get_next_run_time(self):
|
||||
results = list(
|
||||
self.table
|
||||
.filter(self.r.row['next_run_time'] != None) # noqa
|
||||
.order_by(self.r.asc('next_run_time'))
|
||||
.map(lambda x: x['next_run_time'])
|
||||
.limit(1)
|
||||
.run(self.conn)
|
||||
)
|
||||
return utc_timestamp_to_datetime(results[0]) if results else None
|
||||
|
||||
def get_all_jobs(self):
|
||||
jobs = self._get_jobs()
|
||||
self._fix_paused_jobs_sorting(jobs)
|
||||
return jobs
|
||||
|
||||
def add_job(self, job):
|
||||
job_dict = {
|
||||
'id': job.id,
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': self.r.binary(pickle.dumps(job.__getstate__(), self.pickle_protocol))
|
||||
}
|
||||
results = self.table.insert(job_dict).run(self.conn)
|
||||
if results['errors'] > 0:
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
def update_job(self, job):
|
||||
changes = {
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': self.r.binary(pickle.dumps(job.__getstate__(), self.pickle_protocol))
|
||||
}
|
||||
results = self.table.get_all(job.id).update(changes).run(self.conn)
|
||||
skipped = False in map(lambda x: results[x] == 0, results.keys())
|
||||
if results['skipped'] > 0 or results['errors'] > 0 or not skipped:
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
def remove_job(self, job_id):
|
||||
results = self.table.get_all(job_id).delete().run(self.conn)
|
||||
if results['deleted'] + results['skipped'] != 1:
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
def remove_all_jobs(self):
|
||||
self.table.delete().run(self.conn)
|
||||
|
||||
def shutdown(self):
|
||||
self.conn.close()
|
||||
|
||||
def _reconstitute_job(self, job_state):
|
||||
job_state = pickle.loads(job_state)
|
||||
job = Job.__new__(Job)
|
||||
job.__setstate__(job_state)
|
||||
job._scheduler = self._scheduler
|
||||
job._jobstore_alias = self._alias
|
||||
return job
|
||||
|
||||
def _get_jobs(self, predicate=None):
|
||||
jobs = []
|
||||
failed_job_ids = []
|
||||
query = (self.table.filter(self.r.row['next_run_time'] != None).filter(predicate) # noqa
|
||||
if predicate else self.table)
|
||||
query = query.order_by('next_run_time', 'id').pluck('id', 'job_state')
|
||||
|
||||
for document in query.run(self.conn):
|
||||
try:
|
||||
jobs.append(self._reconstitute_job(document['job_state']))
|
||||
except Exception:
|
||||
self._logger.exception('Unable to restore job "%s" -- removing it', document['id'])
|
||||
failed_job_ids.append(document['id'])
|
||||
|
||||
# Remove all the jobs we failed to restore
|
||||
if failed_job_ids:
|
||||
self.r.expr(failed_job_ids).for_each(
|
||||
lambda job_id: self.table.get_all(job_id).delete()).run(self.conn)
|
||||
|
||||
return jobs
|
||||
|
||||
def __repr__(self):
|
||||
connection = self.conn
|
||||
return '<%s (connection=%s)>' % (self.__class__.__name__, connection)
|
154
venv/Lib/site-packages/apscheduler/jobstores/sqlalchemy.py
Normal file
154
venv/Lib/site-packages/apscheduler/jobstores/sqlalchemy.py
Normal file
@ -0,0 +1,154 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime
|
||||
from apscheduler.job import Job
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: nocover
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from sqlalchemy import (
|
||||
create_engine, Table, Column, MetaData, Unicode, Float, LargeBinary, select)
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.sql.expression import null
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('SQLAlchemyJobStore requires SQLAlchemy installed')
|
||||
|
||||
|
||||
class SQLAlchemyJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in a database table using SQLAlchemy.
|
||||
The table will be created if it doesn't exist in the database.
|
||||
|
||||
Plugin alias: ``sqlalchemy``
|
||||
|
||||
:param str url: connection string (see
|
||||
:ref:`SQLAlchemy documentation <sqlalchemy:database_urls>` on this)
|
||||
:param engine: an SQLAlchemy :class:`~sqlalchemy.engine.Engine` to use instead of creating a
|
||||
new one based on ``url``
|
||||
:param str tablename: name of the table to store jobs in
|
||||
:param metadata: a :class:`~sqlalchemy.schema.MetaData` instance to use instead of creating a
|
||||
new one
|
||||
:param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the
|
||||
highest available
|
||||
:param str tableschema: name of the (existing) schema in the target database where the table
|
||||
should be
|
||||
:param dict engine_options: keyword arguments to :func:`~sqlalchemy.create_engine`
|
||||
(ignored if ``engine`` is given)
|
||||
"""
|
||||
|
||||
def __init__(self, url=None, engine=None, tablename='apscheduler_jobs', metadata=None,
|
||||
pickle_protocol=pickle.HIGHEST_PROTOCOL, tableschema=None, engine_options=None):
|
||||
super(SQLAlchemyJobStore, self).__init__()
|
||||
self.pickle_protocol = pickle_protocol
|
||||
metadata = maybe_ref(metadata) or MetaData()
|
||||
|
||||
if engine:
|
||||
self.engine = maybe_ref(engine)
|
||||
elif url:
|
||||
self.engine = create_engine(url, **(engine_options or {}))
|
||||
else:
|
||||
raise ValueError('Need either "engine" or "url" defined')
|
||||
|
||||
# 191 = max key length in MySQL for InnoDB/utf8mb4 tables,
|
||||
# 25 = precision that translates to an 8-byte float
|
||||
self.jobs_t = Table(
|
||||
tablename, metadata,
|
||||
Column('id', Unicode(191, _warn_on_bytestring=False), primary_key=True),
|
||||
Column('next_run_time', Float(25), index=True),
|
||||
Column('job_state', LargeBinary, nullable=False),
|
||||
schema=tableschema
|
||||
)
|
||||
|
||||
def start(self, scheduler, alias):
|
||||
super(SQLAlchemyJobStore, self).start(scheduler, alias)
|
||||
self.jobs_t.create(self.engine, True)
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
selectable = select([self.jobs_t.c.job_state]).where(self.jobs_t.c.id == job_id)
|
||||
job_state = self.engine.execute(selectable).scalar()
|
||||
return self._reconstitute_job(job_state) if job_state else None
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
timestamp = datetime_to_utc_timestamp(now)
|
||||
return self._get_jobs(self.jobs_t.c.next_run_time <= timestamp)
|
||||
|
||||
def get_next_run_time(self):
|
||||
selectable = select([self.jobs_t.c.next_run_time]).\
|
||||
where(self.jobs_t.c.next_run_time != null()).\
|
||||
order_by(self.jobs_t.c.next_run_time).limit(1)
|
||||
next_run_time = self.engine.execute(selectable).scalar()
|
||||
return utc_timestamp_to_datetime(next_run_time)
|
||||
|
||||
def get_all_jobs(self):
|
||||
jobs = self._get_jobs()
|
||||
self._fix_paused_jobs_sorting(jobs)
|
||||
return jobs
|
||||
|
||||
def add_job(self, job):
|
||||
insert = self.jobs_t.insert().values(**{
|
||||
'id': job.id,
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': pickle.dumps(job.__getstate__(), self.pickle_protocol)
|
||||
})
|
||||
try:
|
||||
self.engine.execute(insert)
|
||||
except IntegrityError:
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
def update_job(self, job):
|
||||
update = self.jobs_t.update().values(**{
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': pickle.dumps(job.__getstate__(), self.pickle_protocol)
|
||||
}).where(self.jobs_t.c.id == job.id)
|
||||
result = self.engine.execute(update)
|
||||
if result.rowcount == 0:
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
def remove_job(self, job_id):
|
||||
delete = self.jobs_t.delete().where(self.jobs_t.c.id == job_id)
|
||||
result = self.engine.execute(delete)
|
||||
if result.rowcount == 0:
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
def remove_all_jobs(self):
|
||||
delete = self.jobs_t.delete()
|
||||
self.engine.execute(delete)
|
||||
|
||||
def shutdown(self):
|
||||
self.engine.dispose()
|
||||
|
||||
def _reconstitute_job(self, job_state):
|
||||
job_state = pickle.loads(job_state)
|
||||
job_state['jobstore'] = self
|
||||
job = Job.__new__(Job)
|
||||
job.__setstate__(job_state)
|
||||
job._scheduler = self._scheduler
|
||||
job._jobstore_alias = self._alias
|
||||
return job
|
||||
|
||||
def _get_jobs(self, *conditions):
|
||||
jobs = []
|
||||
selectable = select([self.jobs_t.c.id, self.jobs_t.c.job_state]).\
|
||||
order_by(self.jobs_t.c.next_run_time)
|
||||
selectable = selectable.where(*conditions) if conditions else selectable
|
||||
failed_job_ids = set()
|
||||
for row in self.engine.execute(selectable):
|
||||
try:
|
||||
jobs.append(self._reconstitute_job(row.job_state))
|
||||
except BaseException:
|
||||
self._logger.exception('Unable to restore job "%s" -- removing it', row.id)
|
||||
failed_job_ids.add(row.id)
|
||||
|
||||
# Remove all the jobs we failed to restore
|
||||
if failed_job_ids:
|
||||
delete = self.jobs_t.delete().where(self.jobs_t.c.id.in_(failed_job_ids))
|
||||
self.engine.execute(delete)
|
||||
|
||||
return jobs
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s (url=%s)>' % (self.__class__.__name__, self.engine.url)
|
179
venv/Lib/site-packages/apscheduler/jobstores/zookeeper.py
Normal file
179
venv/Lib/site-packages/apscheduler/jobstores/zookeeper.py
Normal file
@ -0,0 +1,179 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from pytz import utc
|
||||
from kazoo.exceptions import NoNodeError, NodeExistsError
|
||||
|
||||
from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError
|
||||
from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime
|
||||
from apscheduler.job import Job
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: nocover
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from kazoo.client import KazooClient
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('ZooKeeperJobStore requires Kazoo installed')
|
||||
|
||||
|
||||
class ZooKeeperJobStore(BaseJobStore):
|
||||
"""
|
||||
Stores jobs in a ZooKeeper tree. Any leftover keyword arguments are directly passed to
|
||||
kazoo's `KazooClient
|
||||
<http://kazoo.readthedocs.io/en/latest/api/client.html>`_.
|
||||
|
||||
Plugin alias: ``zookeeper``
|
||||
|
||||
:param str path: path to store jobs in
|
||||
:param client: a :class:`~kazoo.client.KazooClient` instance to use instead of
|
||||
providing connection arguments
|
||||
:param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the
|
||||
highest available
|
||||
"""
|
||||
|
||||
def __init__(self, path='/apscheduler', client=None, close_connection_on_exit=False,
|
||||
pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args):
|
||||
super(ZooKeeperJobStore, self).__init__()
|
||||
self.pickle_protocol = pickle_protocol
|
||||
self.close_connection_on_exit = close_connection_on_exit
|
||||
|
||||
if not path:
|
||||
raise ValueError('The "path" parameter must not be empty')
|
||||
|
||||
self.path = path
|
||||
|
||||
if client:
|
||||
self.client = maybe_ref(client)
|
||||
else:
|
||||
self.client = KazooClient(**connect_args)
|
||||
self._ensured_path = False
|
||||
|
||||
def _ensure_paths(self):
|
||||
if not self._ensured_path:
|
||||
self.client.ensure_path(self.path)
|
||||
self._ensured_path = True
|
||||
|
||||
def start(self, scheduler, alias):
|
||||
super(ZooKeeperJobStore, self).start(scheduler, alias)
|
||||
if not self.client.connected:
|
||||
self.client.start()
|
||||
|
||||
def lookup_job(self, job_id):
|
||||
self._ensure_paths()
|
||||
node_path = os.path.join(self.path, job_id)
|
||||
try:
|
||||
content, _ = self.client.get(node_path)
|
||||
doc = pickle.loads(content)
|
||||
job = self._reconstitute_job(doc['job_state'])
|
||||
return job
|
||||
except BaseException:
|
||||
return None
|
||||
|
||||
def get_due_jobs(self, now):
|
||||
timestamp = datetime_to_utc_timestamp(now)
|
||||
jobs = [job_def['job'] for job_def in self._get_jobs()
|
||||
if job_def['next_run_time'] is not None and job_def['next_run_time'] <= timestamp]
|
||||
return jobs
|
||||
|
||||
def get_next_run_time(self):
|
||||
next_runs = [job_def['next_run_time'] for job_def in self._get_jobs()
|
||||
if job_def['next_run_time'] is not None]
|
||||
return utc_timestamp_to_datetime(min(next_runs)) if len(next_runs) > 0 else None
|
||||
|
||||
def get_all_jobs(self):
|
||||
jobs = [job_def['job'] for job_def in self._get_jobs()]
|
||||
self._fix_paused_jobs_sorting(jobs)
|
||||
return jobs
|
||||
|
||||
def add_job(self, job):
|
||||
self._ensure_paths()
|
||||
node_path = os.path.join(self.path, str(job.id))
|
||||
value = {
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': job.__getstate__()
|
||||
}
|
||||
data = pickle.dumps(value, self.pickle_protocol)
|
||||
try:
|
||||
self.client.create(node_path, value=data)
|
||||
except NodeExistsError:
|
||||
raise ConflictingIdError(job.id)
|
||||
|
||||
def update_job(self, job):
|
||||
self._ensure_paths()
|
||||
node_path = os.path.join(self.path, str(job.id))
|
||||
changes = {
|
||||
'next_run_time': datetime_to_utc_timestamp(job.next_run_time),
|
||||
'job_state': job.__getstate__()
|
||||
}
|
||||
data = pickle.dumps(changes, self.pickle_protocol)
|
||||
try:
|
||||
self.client.set(node_path, value=data)
|
||||
except NoNodeError:
|
||||
raise JobLookupError(job.id)
|
||||
|
||||
def remove_job(self, job_id):
|
||||
self._ensure_paths()
|
||||
node_path = os.path.join(self.path, str(job_id))
|
||||
try:
|
||||
self.client.delete(node_path)
|
||||
except NoNodeError:
|
||||
raise JobLookupError(job_id)
|
||||
|
||||
def remove_all_jobs(self):
|
||||
try:
|
||||
self.client.delete(self.path, recursive=True)
|
||||
except NoNodeError:
|
||||
pass
|
||||
self._ensured_path = False
|
||||
|
||||
def shutdown(self):
|
||||
if self.close_connection_on_exit:
|
||||
self.client.stop()
|
||||
self.client.close()
|
||||
|
||||
def _reconstitute_job(self, job_state):
|
||||
job_state = job_state
|
||||
job = Job.__new__(Job)
|
||||
job.__setstate__(job_state)
|
||||
job._scheduler = self._scheduler
|
||||
job._jobstore_alias = self._alias
|
||||
return job
|
||||
|
||||
def _get_jobs(self):
|
||||
self._ensure_paths()
|
||||
jobs = []
|
||||
failed_job_ids = []
|
||||
all_ids = self.client.get_children(self.path)
|
||||
for node_name in all_ids:
|
||||
try:
|
||||
node_path = os.path.join(self.path, node_name)
|
||||
content, _ = self.client.get(node_path)
|
||||
doc = pickle.loads(content)
|
||||
job_def = {
|
||||
'job_id': node_name,
|
||||
'next_run_time': doc['next_run_time'] if doc['next_run_time'] else None,
|
||||
'job_state': doc['job_state'],
|
||||
'job': self._reconstitute_job(doc['job_state']),
|
||||
'creation_time': _.ctime
|
||||
}
|
||||
jobs.append(job_def)
|
||||
except BaseException:
|
||||
self._logger.exception('Unable to restore job "%s" -- removing it' % node_name)
|
||||
failed_job_ids.append(node_name)
|
||||
|
||||
# Remove all the jobs we failed to restore
|
||||
if failed_job_ids:
|
||||
for failed_id in failed_job_ids:
|
||||
self.remove_job(failed_id)
|
||||
paused_sort_key = datetime(9999, 12, 31, tzinfo=utc)
|
||||
return sorted(jobs, key=lambda job_def: (job_def['job'].next_run_time or paused_sort_key,
|
||||
job_def['creation_time']))
|
||||
|
||||
def __repr__(self):
|
||||
self._logger.exception('<%s (client=%s)>' % (self.__class__.__name__, self.client))
|
||||
return '<%s (client=%s)>' % (self.__class__.__name__, self.client)
|
12
venv/Lib/site-packages/apscheduler/schedulers/__init__.py
Normal file
12
venv/Lib/site-packages/apscheduler/schedulers/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
class SchedulerAlreadyRunningError(Exception):
|
||||
"""Raised when attempting to start or configure the scheduler when it's already running."""
|
||||
|
||||
def __str__(self):
|
||||
return 'Scheduler is already running'
|
||||
|
||||
|
||||
class SchedulerNotRunningError(Exception):
|
||||
"""Raised when attempting to shutdown the scheduler when it's not running."""
|
||||
|
||||
def __str__(self):
|
||||
return 'Scheduler is not running'
|
68
venv/Lib/site-packages/apscheduler/schedulers/asyncio.py
Normal file
68
venv/Lib/site-packages/apscheduler/schedulers/asyncio.py
Normal file
@ -0,0 +1,68 @@
|
||||
from __future__ import absolute_import
|
||||
from functools import wraps, partial
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
from apscheduler.util import maybe_ref
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError: # pragma: nocover
|
||||
try:
|
||||
import trollius as asyncio
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'AsyncIOScheduler requires either Python 3.4 or the asyncio package installed')
|
||||
|
||||
|
||||
def run_in_event_loop(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
wrapped = partial(func, self, *args, **kwargs)
|
||||
self._eventloop.call_soon_threadsafe(wrapped)
|
||||
return wrapper
|
||||
|
||||
|
||||
class AsyncIOScheduler(BaseScheduler):
|
||||
"""
|
||||
A scheduler that runs on an asyncio (:pep:`3156`) event loop.
|
||||
|
||||
The default executor can run jobs based on native coroutines (``async def``).
|
||||
|
||||
Extra options:
|
||||
|
||||
============== =============================================================
|
||||
``event_loop`` AsyncIO event loop to use (defaults to the global event loop)
|
||||
============== =============================================================
|
||||
"""
|
||||
|
||||
_eventloop = None
|
||||
_timeout = None
|
||||
|
||||
@run_in_event_loop
|
||||
def shutdown(self, wait=True):
|
||||
super(AsyncIOScheduler, self).shutdown(wait)
|
||||
self._stop_timer()
|
||||
|
||||
def _configure(self, config):
|
||||
self._eventloop = maybe_ref(config.pop('event_loop', None)) or asyncio.get_event_loop()
|
||||
super(AsyncIOScheduler, self)._configure(config)
|
||||
|
||||
def _start_timer(self, wait_seconds):
|
||||
self._stop_timer()
|
||||
if wait_seconds is not None:
|
||||
self._timeout = self._eventloop.call_later(wait_seconds, self.wakeup)
|
||||
|
||||
def _stop_timer(self):
|
||||
if self._timeout:
|
||||
self._timeout.cancel()
|
||||
del self._timeout
|
||||
|
||||
@run_in_event_loop
|
||||
def wakeup(self):
|
||||
self._stop_timer()
|
||||
wait_seconds = self._process_jobs()
|
||||
self._start_timer(wait_seconds)
|
||||
|
||||
def _create_default_executor(self):
|
||||
from apscheduler.executors.asyncio import AsyncIOExecutor
|
||||
return AsyncIOExecutor()
|
41
venv/Lib/site-packages/apscheduler/schedulers/background.py
Normal file
41
venv/Lib/site-packages/apscheduler/schedulers/background.py
Normal file
@ -0,0 +1,41 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from threading import Thread, Event
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
from apscheduler.util import asbool
|
||||
|
||||
|
||||
class BackgroundScheduler(BlockingScheduler):
|
||||
"""
|
||||
A scheduler that runs in the background using a separate thread
|
||||
(:meth:`~apscheduler.schedulers.base.BaseScheduler.start` will return immediately).
|
||||
|
||||
Extra options:
|
||||
|
||||
========== =============================================================================
|
||||
``daemon`` Set the ``daemon`` option in the background thread (defaults to ``True``, see
|
||||
`the documentation
|
||||
<https://docs.python.org/3.4/library/threading.html#thread-objects>`_
|
||||
for further details)
|
||||
========== =============================================================================
|
||||
"""
|
||||
|
||||
_thread = None
|
||||
|
||||
def _configure(self, config):
|
||||
self._daemon = asbool(config.pop('daemon', True))
|
||||
super(BackgroundScheduler, self)._configure(config)
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
self._event = Event()
|
||||
BaseScheduler.start(self, *args, **kwargs)
|
||||
self._thread = Thread(target=self._main_loop, name='APScheduler')
|
||||
self._thread.daemon = self._daemon
|
||||
self._thread.start()
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
super(BackgroundScheduler, self).shutdown(*args, **kwargs)
|
||||
self._thread.join()
|
||||
del self._thread
|
1022
venv/Lib/site-packages/apscheduler/schedulers/base.py
Normal file
1022
venv/Lib/site-packages/apscheduler/schedulers/base.py
Normal file
File diff suppressed because it is too large
Load Diff
33
venv/Lib/site-packages/apscheduler/schedulers/blocking.py
Normal file
33
venv/Lib/site-packages/apscheduler/schedulers/blocking.py
Normal file
@ -0,0 +1,33 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from threading import Event
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler, STATE_STOPPED
|
||||
from apscheduler.util import TIMEOUT_MAX
|
||||
|
||||
|
||||
class BlockingScheduler(BaseScheduler):
|
||||
"""
|
||||
A scheduler that runs in the foreground
|
||||
(:meth:`~apscheduler.schedulers.base.BaseScheduler.start` will block).
|
||||
"""
|
||||
_event = None
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
self._event = Event()
|
||||
super(BlockingScheduler, self).start(*args, **kwargs)
|
||||
self._main_loop()
|
||||
|
||||
def shutdown(self, wait=True):
|
||||
super(BlockingScheduler, self).shutdown(wait)
|
||||
self._event.set()
|
||||
|
||||
def _main_loop(self):
|
||||
wait_seconds = TIMEOUT_MAX
|
||||
while self.state != STATE_STOPPED:
|
||||
self._event.wait(wait_seconds)
|
||||
self._event.clear()
|
||||
wait_seconds = self._process_jobs()
|
||||
|
||||
def wakeup(self):
|
||||
self._event.set()
|
35
venv/Lib/site-packages/apscheduler/schedulers/gevent.py
Normal file
35
venv/Lib/site-packages/apscheduler/schedulers/gevent.py
Normal file
@ -0,0 +1,35 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
|
||||
try:
|
||||
from gevent.event import Event
|
||||
from gevent.lock import RLock
|
||||
import gevent
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('GeventScheduler requires gevent installed')
|
||||
|
||||
|
||||
class GeventScheduler(BlockingScheduler):
|
||||
"""A scheduler that runs as a Gevent greenlet."""
|
||||
|
||||
_greenlet = None
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
self._event = Event()
|
||||
BaseScheduler.start(self, *args, **kwargs)
|
||||
self._greenlet = gevent.spawn(self._main_loop)
|
||||
return self._greenlet
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
super(GeventScheduler, self).shutdown(*args, **kwargs)
|
||||
self._greenlet.join()
|
||||
del self._greenlet
|
||||
|
||||
def _create_lock(self):
|
||||
return RLock()
|
||||
|
||||
def _create_default_executor(self):
|
||||
from apscheduler.executors.gevent import GeventExecutor
|
||||
return GeventExecutor()
|
43
venv/Lib/site-packages/apscheduler/schedulers/qt.py
Normal file
43
venv/Lib/site-packages/apscheduler/schedulers/qt.py
Normal file
@ -0,0 +1,43 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
|
||||
try:
|
||||
from PyQt5.QtCore import QObject, QTimer
|
||||
except (ImportError, RuntimeError): # pragma: nocover
|
||||
try:
|
||||
from PyQt4.QtCore import QObject, QTimer
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide.QtCore import QObject, QTimer # noqa
|
||||
except ImportError:
|
||||
raise ImportError('QtScheduler requires either PyQt5, PyQt4 or PySide installed')
|
||||
|
||||
|
||||
class QtScheduler(BaseScheduler):
|
||||
"""A scheduler that runs in a Qt event loop."""
|
||||
|
||||
_timer = None
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
super(QtScheduler, self).shutdown(*args, **kwargs)
|
||||
self._stop_timer()
|
||||
|
||||
def _start_timer(self, wait_seconds):
|
||||
self._stop_timer()
|
||||
if wait_seconds is not None:
|
||||
wait_time = min(wait_seconds * 1000, 2147483647)
|
||||
self._timer = QTimer.singleShot(wait_time, self._process_jobs)
|
||||
|
||||
def _stop_timer(self):
|
||||
if self._timer:
|
||||
if self._timer.isActive():
|
||||
self._timer.stop()
|
||||
del self._timer
|
||||
|
||||
def wakeup(self):
|
||||
self._start_timer(0)
|
||||
|
||||
def _process_jobs(self):
|
||||
wait_seconds = super(QtScheduler, self)._process_jobs()
|
||||
self._start_timer(wait_seconds)
|
63
venv/Lib/site-packages/apscheduler/schedulers/tornado.py
Normal file
63
venv/Lib/site-packages/apscheduler/schedulers/tornado.py
Normal file
@ -0,0 +1,63 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from datetime import timedelta
|
||||
from functools import wraps
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
from apscheduler.util import maybe_ref
|
||||
|
||||
try:
|
||||
from tornado.ioloop import IOLoop
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('TornadoScheduler requires tornado installed')
|
||||
|
||||
|
||||
def run_in_ioloop(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
self._ioloop.add_callback(func, self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class TornadoScheduler(BaseScheduler):
|
||||
"""
|
||||
A scheduler that runs on a Tornado IOLoop.
|
||||
|
||||
The default executor can run jobs based on native coroutines (``async def``).
|
||||
|
||||
=========== ===============================================================
|
||||
``io_loop`` Tornado IOLoop instance to use (defaults to the global IO loop)
|
||||
=========== ===============================================================
|
||||
"""
|
||||
|
||||
_ioloop = None
|
||||
_timeout = None
|
||||
|
||||
@run_in_ioloop
|
||||
def shutdown(self, wait=True):
|
||||
super(TornadoScheduler, self).shutdown(wait)
|
||||
self._stop_timer()
|
||||
|
||||
def _configure(self, config):
|
||||
self._ioloop = maybe_ref(config.pop('io_loop', None)) or IOLoop.current()
|
||||
super(TornadoScheduler, self)._configure(config)
|
||||
|
||||
def _start_timer(self, wait_seconds):
|
||||
self._stop_timer()
|
||||
if wait_seconds is not None:
|
||||
self._timeout = self._ioloop.add_timeout(timedelta(seconds=wait_seconds), self.wakeup)
|
||||
|
||||
def _stop_timer(self):
|
||||
if self._timeout:
|
||||
self._ioloop.remove_timeout(self._timeout)
|
||||
del self._timeout
|
||||
|
||||
def _create_default_executor(self):
|
||||
from apscheduler.executors.tornado import TornadoExecutor
|
||||
return TornadoExecutor()
|
||||
|
||||
@run_in_ioloop
|
||||
def wakeup(self):
|
||||
self._stop_timer()
|
||||
wait_seconds = self._process_jobs()
|
||||
self._start_timer(wait_seconds)
|
62
venv/Lib/site-packages/apscheduler/schedulers/twisted.py
Normal file
62
venv/Lib/site-packages/apscheduler/schedulers/twisted.py
Normal file
@ -0,0 +1,62 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from apscheduler.schedulers.base import BaseScheduler
|
||||
from apscheduler.util import maybe_ref
|
||||
|
||||
try:
|
||||
from twisted.internet import reactor as default_reactor
|
||||
except ImportError: # pragma: nocover
|
||||
raise ImportError('TwistedScheduler requires Twisted installed')
|
||||
|
||||
|
||||
def run_in_reactor(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
self._reactor.callFromThread(func, self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class TwistedScheduler(BaseScheduler):
|
||||
"""
|
||||
A scheduler that runs on a Twisted reactor.
|
||||
|
||||
Extra options:
|
||||
|
||||
=========== ========================================================
|
||||
``reactor`` Reactor instance to use (defaults to the global reactor)
|
||||
=========== ========================================================
|
||||
"""
|
||||
|
||||
_reactor = None
|
||||
_delayedcall = None
|
||||
|
||||
def _configure(self, config):
|
||||
self._reactor = maybe_ref(config.pop('reactor', default_reactor))
|
||||
super(TwistedScheduler, self)._configure(config)
|
||||
|
||||
@run_in_reactor
|
||||
def shutdown(self, wait=True):
|
||||
super(TwistedScheduler, self).shutdown(wait)
|
||||
self._stop_timer()
|
||||
|
||||
def _start_timer(self, wait_seconds):
|
||||
self._stop_timer()
|
||||
if wait_seconds is not None:
|
||||
self._delayedcall = self._reactor.callLater(wait_seconds, self.wakeup)
|
||||
|
||||
def _stop_timer(self):
|
||||
if self._delayedcall and self._delayedcall.active():
|
||||
self._delayedcall.cancel()
|
||||
del self._delayedcall
|
||||
|
||||
@run_in_reactor
|
||||
def wakeup(self):
|
||||
self._stop_timer()
|
||||
wait_seconds = self._process_jobs()
|
||||
self._start_timer(wait_seconds)
|
||||
|
||||
def _create_default_executor(self):
|
||||
from apscheduler.executors.twisted import TwistedExecutor
|
||||
return TwistedExecutor()
|
48
venv/Lib/site-packages/apscheduler/triggers/base.py
Normal file
48
venv/Lib/site-packages/apscheduler/triggers/base.py
Normal file
@ -0,0 +1,48 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from datetime import timedelta
|
||||
import random
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class BaseTrigger(six.with_metaclass(ABCMeta)):
|
||||
"""Abstract base class that defines the interface that every trigger must implement."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@abstractmethod
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
"""
|
||||
Returns the next datetime to fire on, If no such datetime can be calculated, returns
|
||||
``None``.
|
||||
|
||||
:param datetime.datetime previous_fire_time: the previous time the trigger was fired
|
||||
:param datetime.datetime now: current datetime
|
||||
"""
|
||||
|
||||
def _apply_jitter(self, next_fire_time, jitter, now):
|
||||
"""
|
||||
Randomize ``next_fire_time`` by adding or subtracting a random value (the jitter). If the
|
||||
resulting datetime is in the past, returns the initial ``next_fire_time`` without jitter.
|
||||
|
||||
``next_fire_time - jitter <= result <= next_fire_time + jitter``
|
||||
|
||||
:param datetime.datetime|None next_fire_time: next fire time without jitter applied. If
|
||||
``None``, returns ``None``.
|
||||
:param int|None jitter: maximum number of seconds to add or subtract to
|
||||
``next_fire_time``. If ``None`` or ``0``, returns ``next_fire_time``
|
||||
:param datetime.datetime now: current datetime
|
||||
:return datetime.datetime|None: next fire time with a jitter.
|
||||
"""
|
||||
if next_fire_time is None or not jitter:
|
||||
return next_fire_time
|
||||
|
||||
next_fire_time_with_jitter = next_fire_time + timedelta(
|
||||
seconds=random.uniform(-jitter, jitter))
|
||||
|
||||
if next_fire_time_with_jitter < now:
|
||||
# Next fire time with jitter is in the past.
|
||||
# Ignore jitter to avoid false misfire.
|
||||
return next_fire_time
|
||||
|
||||
return next_fire_time_with_jitter
|
95
venv/Lib/site-packages/apscheduler/triggers/combining.py
Normal file
95
venv/Lib/site-packages/apscheduler/triggers/combining.py
Normal file
@ -0,0 +1,95 @@
|
||||
from apscheduler.triggers.base import BaseTrigger
|
||||
from apscheduler.util import obj_to_ref, ref_to_obj
|
||||
|
||||
|
||||
class BaseCombiningTrigger(BaseTrigger):
|
||||
__slots__ = ('triggers', 'jitter')
|
||||
|
||||
def __init__(self, triggers, jitter=None):
|
||||
self.triggers = triggers
|
||||
self.jitter = jitter
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'version': 1,
|
||||
'triggers': [(obj_to_ref(trigger.__class__), trigger.__getstate__())
|
||||
for trigger in self.triggers],
|
||||
'jitter': self.jitter
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state.get('version', 1) > 1:
|
||||
raise ValueError(
|
||||
'Got serialized data for version %s of %s, but only versions up to 1 can be '
|
||||
'handled' % (state['version'], self.__class__.__name__))
|
||||
|
||||
self.jitter = state['jitter']
|
||||
self.triggers = []
|
||||
for clsref, state in state['triggers']:
|
||||
cls = ref_to_obj(clsref)
|
||||
trigger = cls.__new__(cls)
|
||||
trigger.__setstate__(state)
|
||||
self.triggers.append(trigger)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{}({}{})>'.format(self.__class__.__name__, self.triggers,
|
||||
', jitter={}'.format(self.jitter) if self.jitter else '')
|
||||
|
||||
|
||||
class AndTrigger(BaseCombiningTrigger):
|
||||
"""
|
||||
Always returns the earliest next fire time that all the given triggers can agree on.
|
||||
The trigger is considered to be finished when any of the given triggers has finished its
|
||||
schedule.
|
||||
|
||||
Trigger alias: ``and``
|
||||
|
||||
:param list triggers: triggers to combine
|
||||
:param int|None jitter: advance or delay the job execution by ``jitter`` seconds at most.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
while True:
|
||||
fire_times = [trigger.get_next_fire_time(previous_fire_time, now)
|
||||
for trigger in self.triggers]
|
||||
if None in fire_times:
|
||||
return None
|
||||
elif min(fire_times) == max(fire_times):
|
||||
return self._apply_jitter(fire_times[0], self.jitter, now)
|
||||
else:
|
||||
now = max(fire_times)
|
||||
|
||||
def __str__(self):
|
||||
return 'and[{}]'.format(', '.join(str(trigger) for trigger in self.triggers))
|
||||
|
||||
|
||||
class OrTrigger(BaseCombiningTrigger):
|
||||
"""
|
||||
Always returns the earliest next fire time produced by any of the given triggers.
|
||||
The trigger is considered finished when all the given triggers have finished their schedules.
|
||||
|
||||
Trigger alias: ``or``
|
||||
|
||||
:param list triggers: triggers to combine
|
||||
:param int|None jitter: advance or delay the job execution by ``jitter`` seconds at most.
|
||||
|
||||
.. note:: Triggers that depends on the previous fire time, such as the interval trigger, may
|
||||
seem to behave strangely since they are always passed the previous fire time produced by
|
||||
any of the given triggers.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
fire_times = [trigger.get_next_fire_time(previous_fire_time, now)
|
||||
for trigger in self.triggers]
|
||||
fire_times = [fire_time for fire_time in fire_times if fire_time is not None]
|
||||
if fire_times:
|
||||
return self._apply_jitter(min(fire_times), self.jitter, now)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return 'or[{}]'.format(', '.join(str(trigger) for trigger in self.triggers))
|
238
venv/Lib/site-packages/apscheduler/triggers/cron/__init__.py
Normal file
238
venv/Lib/site-packages/apscheduler/triggers/cron/__init__.py
Normal file
@ -0,0 +1,238 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from tzlocal import get_localzone
|
||||
import six
|
||||
|
||||
from apscheduler.triggers.base import BaseTrigger
|
||||
from apscheduler.triggers.cron.fields import (
|
||||
BaseField, MonthField, WeekField, DayOfMonthField, DayOfWeekField, DEFAULT_VALUES)
|
||||
from apscheduler.util import datetime_ceil, convert_to_datetime, datetime_repr, astimezone
|
||||
|
||||
|
||||
class CronTrigger(BaseTrigger):
|
||||
"""
|
||||
Triggers when current time matches all specified time constraints,
|
||||
similarly to how the UNIX cron scheduler works.
|
||||
|
||||
:param int|str year: 4-digit year
|
||||
:param int|str month: month (1-12)
|
||||
:param int|str day: day of the (1-31)
|
||||
:param int|str week: ISO week (1-53)
|
||||
:param int|str day_of_week: number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun)
|
||||
:param int|str hour: hour (0-23)
|
||||
:param int|str minute: minute (0-59)
|
||||
:param int|str second: second (0-59)
|
||||
:param datetime|str start_date: earliest possible date/time to trigger on (inclusive)
|
||||
:param datetime|str end_date: latest possible date/time to trigger on (inclusive)
|
||||
:param datetime.tzinfo|str timezone: time zone to use for the date/time calculations (defaults
|
||||
to scheduler timezone)
|
||||
:param int|None jitter: advance or delay the job execution by ``jitter`` seconds at most.
|
||||
|
||||
.. note:: The first weekday is always **monday**.
|
||||
"""
|
||||
|
||||
FIELD_NAMES = ('year', 'month', 'day', 'week', 'day_of_week', 'hour', 'minute', 'second')
|
||||
FIELDS_MAP = {
|
||||
'year': BaseField,
|
||||
'month': MonthField,
|
||||
'week': WeekField,
|
||||
'day': DayOfMonthField,
|
||||
'day_of_week': DayOfWeekField,
|
||||
'hour': BaseField,
|
||||
'minute': BaseField,
|
||||
'second': BaseField
|
||||
}
|
||||
|
||||
__slots__ = 'timezone', 'start_date', 'end_date', 'fields', 'jitter'
|
||||
|
||||
def __init__(self, year=None, month=None, day=None, week=None, day_of_week=None, hour=None,
|
||||
minute=None, second=None, start_date=None, end_date=None, timezone=None,
|
||||
jitter=None):
|
||||
if timezone:
|
||||
self.timezone = astimezone(timezone)
|
||||
elif isinstance(start_date, datetime) and start_date.tzinfo:
|
||||
self.timezone = start_date.tzinfo
|
||||
elif isinstance(end_date, datetime) and end_date.tzinfo:
|
||||
self.timezone = end_date.tzinfo
|
||||
else:
|
||||
self.timezone = get_localzone()
|
||||
|
||||
self.start_date = convert_to_datetime(start_date, self.timezone, 'start_date')
|
||||
self.end_date = convert_to_datetime(end_date, self.timezone, 'end_date')
|
||||
|
||||
self.jitter = jitter
|
||||
|
||||
values = dict((key, value) for (key, value) in six.iteritems(locals())
|
||||
if key in self.FIELD_NAMES and value is not None)
|
||||
self.fields = []
|
||||
assign_defaults = False
|
||||
for field_name in self.FIELD_NAMES:
|
||||
if field_name in values:
|
||||
exprs = values.pop(field_name)
|
||||
is_default = False
|
||||
assign_defaults = not values
|
||||
elif assign_defaults:
|
||||
exprs = DEFAULT_VALUES[field_name]
|
||||
is_default = True
|
||||
else:
|
||||
exprs = '*'
|
||||
is_default = True
|
||||
|
||||
field_class = self.FIELDS_MAP[field_name]
|
||||
field = field_class(field_name, exprs, is_default)
|
||||
self.fields.append(field)
|
||||
|
||||
@classmethod
|
||||
def from_crontab(cls, expr, timezone=None):
|
||||
"""
|
||||
Create a :class:`~CronTrigger` from a standard crontab expression.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Cron for more information on the format accepted here.
|
||||
|
||||
:param expr: minute, hour, day of month, month, day of week
|
||||
:param datetime.tzinfo|str timezone: time zone to use for the date/time calculations (
|
||||
defaults to scheduler timezone)
|
||||
:return: a :class:`~CronTrigger` instance
|
||||
|
||||
"""
|
||||
values = expr.split()
|
||||
if len(values) != 5:
|
||||
raise ValueError('Wrong number of fields; got {}, expected 5'.format(len(values)))
|
||||
|
||||
return cls(minute=values[0], hour=values[1], day=values[2], month=values[3],
|
||||
day_of_week=values[4], timezone=timezone)
|
||||
|
||||
def _increment_field_value(self, dateval, fieldnum):
|
||||
"""
|
||||
Increments the designated field and resets all less significant fields to their minimum
|
||||
values.
|
||||
|
||||
:type dateval: datetime
|
||||
:type fieldnum: int
|
||||
:return: a tuple containing the new date, and the number of the field that was actually
|
||||
incremented
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
values = {}
|
||||
i = 0
|
||||
while i < len(self.fields):
|
||||
field = self.fields[i]
|
||||
if not field.REAL:
|
||||
if i == fieldnum:
|
||||
fieldnum -= 1
|
||||
i -= 1
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if i < fieldnum:
|
||||
values[field.name] = field.get_value(dateval)
|
||||
i += 1
|
||||
elif i > fieldnum:
|
||||
values[field.name] = field.get_min(dateval)
|
||||
i += 1
|
||||
else:
|
||||
value = field.get_value(dateval)
|
||||
maxval = field.get_max(dateval)
|
||||
if value == maxval:
|
||||
fieldnum -= 1
|
||||
i -= 1
|
||||
else:
|
||||
values[field.name] = value + 1
|
||||
i += 1
|
||||
|
||||
difference = datetime(**values) - dateval.replace(tzinfo=None)
|
||||
return self.timezone.normalize(dateval + difference), fieldnum
|
||||
|
||||
def _set_field_value(self, dateval, fieldnum, new_value):
|
||||
values = {}
|
||||
for i, field in enumerate(self.fields):
|
||||
if field.REAL:
|
||||
if i < fieldnum:
|
||||
values[field.name] = field.get_value(dateval)
|
||||
elif i > fieldnum:
|
||||
values[field.name] = field.get_min(dateval)
|
||||
else:
|
||||
values[field.name] = new_value
|
||||
|
||||
return self.timezone.localize(datetime(**values))
|
||||
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
if previous_fire_time:
|
||||
start_date = min(now, previous_fire_time + timedelta(microseconds=1))
|
||||
if start_date == previous_fire_time:
|
||||
start_date += timedelta(microseconds=1)
|
||||
else:
|
||||
start_date = max(now, self.start_date) if self.start_date else now
|
||||
|
||||
fieldnum = 0
|
||||
next_date = datetime_ceil(start_date).astimezone(self.timezone)
|
||||
while 0 <= fieldnum < len(self.fields):
|
||||
field = self.fields[fieldnum]
|
||||
curr_value = field.get_value(next_date)
|
||||
next_value = field.get_next_value(next_date)
|
||||
|
||||
if next_value is None:
|
||||
# No valid value was found
|
||||
next_date, fieldnum = self._increment_field_value(next_date, fieldnum - 1)
|
||||
elif next_value > curr_value:
|
||||
# A valid, but higher than the starting value, was found
|
||||
if field.REAL:
|
||||
next_date = self._set_field_value(next_date, fieldnum, next_value)
|
||||
fieldnum += 1
|
||||
else:
|
||||
next_date, fieldnum = self._increment_field_value(next_date, fieldnum)
|
||||
else:
|
||||
# A valid value was found, no changes necessary
|
||||
fieldnum += 1
|
||||
|
||||
# Return if the date has rolled past the end date
|
||||
if self.end_date and next_date > self.end_date:
|
||||
return None
|
||||
|
||||
if fieldnum >= 0:
|
||||
next_date = self._apply_jitter(next_date, self.jitter, now)
|
||||
return min(next_date, self.end_date) if self.end_date else next_date
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'version': 2,
|
||||
'timezone': self.timezone,
|
||||
'start_date': self.start_date,
|
||||
'end_date': self.end_date,
|
||||
'fields': self.fields,
|
||||
'jitter': self.jitter,
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
# This is for compatibility with APScheduler 3.0.x
|
||||
if isinstance(state, tuple):
|
||||
state = state[1]
|
||||
|
||||
if state.get('version', 1) > 2:
|
||||
raise ValueError(
|
||||
'Got serialized data for version %s of %s, but only versions up to 2 can be '
|
||||
'handled' % (state['version'], self.__class__.__name__))
|
||||
|
||||
self.timezone = state['timezone']
|
||||
self.start_date = state['start_date']
|
||||
self.end_date = state['end_date']
|
||||
self.fields = state['fields']
|
||||
self.jitter = state.get('jitter')
|
||||
|
||||
def __str__(self):
|
||||
options = ["%s='%s'" % (f.name, f) for f in self.fields if not f.is_default]
|
||||
return 'cron[%s]' % (', '.join(options))
|
||||
|
||||
def __repr__(self):
|
||||
options = ["%s='%s'" % (f.name, f) for f in self.fields if not f.is_default]
|
||||
if self.start_date:
|
||||
options.append("start_date=%r" % datetime_repr(self.start_date))
|
||||
if self.end_date:
|
||||
options.append("end_date=%r" % datetime_repr(self.end_date))
|
||||
if self.jitter:
|
||||
options.append('jitter=%s' % self.jitter)
|
||||
|
||||
return "<%s (%s, timezone='%s')>" % (
|
||||
self.__class__.__name__, ', '.join(options), self.timezone)
|
251
venv/Lib/site-packages/apscheduler/triggers/cron/expressions.py
Normal file
251
venv/Lib/site-packages/apscheduler/triggers/cron/expressions.py
Normal file
@ -0,0 +1,251 @@
|
||||
"""This module contains the expressions applicable for CronTrigger's fields."""
|
||||
|
||||
from calendar import monthrange
|
||||
import re
|
||||
|
||||
from apscheduler.util import asint
|
||||
|
||||
__all__ = ('AllExpression', 'RangeExpression', 'WeekdayRangeExpression',
|
||||
'WeekdayPositionExpression', 'LastDayOfMonthExpression')
|
||||
|
||||
|
||||
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
||||
MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
||||
|
||||
|
||||
class AllExpression(object):
|
||||
value_re = re.compile(r'\*(?:/(?P<step>\d+))?$')
|
||||
|
||||
def __init__(self, step=None):
|
||||
self.step = asint(step)
|
||||
if self.step == 0:
|
||||
raise ValueError('Increment must be higher than 0')
|
||||
|
||||
def validate_range(self, field_name):
|
||||
from apscheduler.triggers.cron.fields import MIN_VALUES, MAX_VALUES
|
||||
|
||||
value_range = MAX_VALUES[field_name] - MIN_VALUES[field_name]
|
||||
if self.step and self.step > value_range:
|
||||
raise ValueError('the step value ({}) is higher than the total range of the '
|
||||
'expression ({})'.format(self.step, value_range))
|
||||
|
||||
def get_next_value(self, date, field):
|
||||
start = field.get_value(date)
|
||||
minval = field.get_min(date)
|
||||
maxval = field.get_max(date)
|
||||
start = max(start, minval)
|
||||
|
||||
if not self.step:
|
||||
next = start
|
||||
else:
|
||||
distance_to_next = (self.step - (start - minval)) % self.step
|
||||
next = start + distance_to_next
|
||||
|
||||
if next <= maxval:
|
||||
return next
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.step == other.step
|
||||
|
||||
def __str__(self):
|
||||
if self.step:
|
||||
return '*/%d' % self.step
|
||||
return '*'
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, self.step)
|
||||
|
||||
|
||||
class RangeExpression(AllExpression):
|
||||
value_re = re.compile(
|
||||
r'(?P<first>\d+)(?:-(?P<last>\d+))?(?:/(?P<step>\d+))?$')
|
||||
|
||||
def __init__(self, first, last=None, step=None):
|
||||
super(RangeExpression, self).__init__(step)
|
||||
first = asint(first)
|
||||
last = asint(last)
|
||||
if last is None and step is None:
|
||||
last = first
|
||||
if last is not None and first > last:
|
||||
raise ValueError('The minimum value in a range must not be higher than the maximum')
|
||||
self.first = first
|
||||
self.last = last
|
||||
|
||||
def validate_range(self, field_name):
|
||||
from apscheduler.triggers.cron.fields import MIN_VALUES, MAX_VALUES
|
||||
|
||||
super(RangeExpression, self).validate_range(field_name)
|
||||
if self.first < MIN_VALUES[field_name]:
|
||||
raise ValueError('the first value ({}) is lower than the minimum value ({})'
|
||||
.format(self.first, MIN_VALUES[field_name]))
|
||||
if self.last is not None and self.last > MAX_VALUES[field_name]:
|
||||
raise ValueError('the last value ({}) is higher than the maximum value ({})'
|
||||
.format(self.last, MAX_VALUES[field_name]))
|
||||
value_range = (self.last or MAX_VALUES[field_name]) - self.first
|
||||
if self.step and self.step > value_range:
|
||||
raise ValueError('the step value ({}) is higher than the total range of the '
|
||||
'expression ({})'.format(self.step, value_range))
|
||||
|
||||
def get_next_value(self, date, field):
|
||||
startval = field.get_value(date)
|
||||
minval = field.get_min(date)
|
||||
maxval = field.get_max(date)
|
||||
|
||||
# Apply range limits
|
||||
minval = max(minval, self.first)
|
||||
maxval = min(maxval, self.last) if self.last is not None else maxval
|
||||
nextval = max(minval, startval)
|
||||
|
||||
# Apply the step if defined
|
||||
if self.step:
|
||||
distance_to_next = (self.step - (nextval - minval)) % self.step
|
||||
nextval += distance_to_next
|
||||
|
||||
return nextval if nextval <= maxval else None
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, self.__class__) and self.first == other.first and
|
||||
self.last == other.last)
|
||||
|
||||
def __str__(self):
|
||||
if self.last != self.first and self.last is not None:
|
||||
range = '%d-%d' % (self.first, self.last)
|
||||
else:
|
||||
range = str(self.first)
|
||||
|
||||
if self.step:
|
||||
return '%s/%d' % (range, self.step)
|
||||
return range
|
||||
|
||||
def __repr__(self):
|
||||
args = [str(self.first)]
|
||||
if self.last != self.first and self.last is not None or self.step:
|
||||
args.append(str(self.last))
|
||||
if self.step:
|
||||
args.append(str(self.step))
|
||||
return "%s(%s)" % (self.__class__.__name__, ', '.join(args))
|
||||
|
||||
|
||||
class MonthRangeExpression(RangeExpression):
|
||||
value_re = re.compile(r'(?P<first>[a-z]+)(?:-(?P<last>[a-z]+))?', re.IGNORECASE)
|
||||
|
||||
def __init__(self, first, last=None):
|
||||
try:
|
||||
first_num = MONTHS.index(first.lower()) + 1
|
||||
except ValueError:
|
||||
raise ValueError('Invalid month name "%s"' % first)
|
||||
|
||||
if last:
|
||||
try:
|
||||
last_num = MONTHS.index(last.lower()) + 1
|
||||
except ValueError:
|
||||
raise ValueError('Invalid month name "%s"' % last)
|
||||
else:
|
||||
last_num = None
|
||||
|
||||
super(MonthRangeExpression, self).__init__(first_num, last_num)
|
||||
|
||||
def __str__(self):
|
||||
if self.last != self.first and self.last is not None:
|
||||
return '%s-%s' % (MONTHS[self.first - 1], MONTHS[self.last - 1])
|
||||
return MONTHS[self.first - 1]
|
||||
|
||||
def __repr__(self):
|
||||
args = ["'%s'" % MONTHS[self.first]]
|
||||
if self.last != self.first and self.last is not None:
|
||||
args.append("'%s'" % MONTHS[self.last - 1])
|
||||
return "%s(%s)" % (self.__class__.__name__, ', '.join(args))
|
||||
|
||||
|
||||
class WeekdayRangeExpression(RangeExpression):
|
||||
value_re = re.compile(r'(?P<first>[a-z]+)(?:-(?P<last>[a-z]+))?', re.IGNORECASE)
|
||||
|
||||
def __init__(self, first, last=None):
|
||||
try:
|
||||
first_num = WEEKDAYS.index(first.lower())
|
||||
except ValueError:
|
||||
raise ValueError('Invalid weekday name "%s"' % first)
|
||||
|
||||
if last:
|
||||
try:
|
||||
last_num = WEEKDAYS.index(last.lower())
|
||||
except ValueError:
|
||||
raise ValueError('Invalid weekday name "%s"' % last)
|
||||
else:
|
||||
last_num = None
|
||||
|
||||
super(WeekdayRangeExpression, self).__init__(first_num, last_num)
|
||||
|
||||
def __str__(self):
|
||||
if self.last != self.first and self.last is not None:
|
||||
return '%s-%s' % (WEEKDAYS[self.first], WEEKDAYS[self.last])
|
||||
return WEEKDAYS[self.first]
|
||||
|
||||
def __repr__(self):
|
||||
args = ["'%s'" % WEEKDAYS[self.first]]
|
||||
if self.last != self.first and self.last is not None:
|
||||
args.append("'%s'" % WEEKDAYS[self.last])
|
||||
return "%s(%s)" % (self.__class__.__name__, ', '.join(args))
|
||||
|
||||
|
||||
class WeekdayPositionExpression(AllExpression):
|
||||
options = ['1st', '2nd', '3rd', '4th', '5th', 'last']
|
||||
value_re = re.compile(r'(?P<option_name>%s) +(?P<weekday_name>(?:\d+|\w+))' %
|
||||
'|'.join(options), re.IGNORECASE)
|
||||
|
||||
def __init__(self, option_name, weekday_name):
|
||||
super(WeekdayPositionExpression, self).__init__(None)
|
||||
try:
|
||||
self.option_num = self.options.index(option_name.lower())
|
||||
except ValueError:
|
||||
raise ValueError('Invalid weekday position "%s"' % option_name)
|
||||
|
||||
try:
|
||||
self.weekday = WEEKDAYS.index(weekday_name.lower())
|
||||
except ValueError:
|
||||
raise ValueError('Invalid weekday name "%s"' % weekday_name)
|
||||
|
||||
def get_next_value(self, date, field):
|
||||
# Figure out the weekday of the month's first day and the number of days in that month
|
||||
first_day_wday, last_day = monthrange(date.year, date.month)
|
||||
|
||||
# Calculate which day of the month is the first of the target weekdays
|
||||
first_hit_day = self.weekday - first_day_wday + 1
|
||||
if first_hit_day <= 0:
|
||||
first_hit_day += 7
|
||||
|
||||
# Calculate what day of the month the target weekday would be
|
||||
if self.option_num < 5:
|
||||
target_day = first_hit_day + self.option_num * 7
|
||||
else:
|
||||
target_day = first_hit_day + ((last_day - first_hit_day) // 7) * 7
|
||||
|
||||
if target_day <= last_day and target_day >= date.day:
|
||||
return target_day
|
||||
|
||||
def __eq__(self, other):
|
||||
return (super(WeekdayPositionExpression, self).__eq__(other) and
|
||||
self.option_num == other.option_num and self.weekday == other.weekday)
|
||||
|
||||
def __str__(self):
|
||||
return '%s %s' % (self.options[self.option_num], WEEKDAYS[self.weekday])
|
||||
|
||||
def __repr__(self):
|
||||
return "%s('%s', '%s')" % (self.__class__.__name__, self.options[self.option_num],
|
||||
WEEKDAYS[self.weekday])
|
||||
|
||||
|
||||
class LastDayOfMonthExpression(AllExpression):
|
||||
value_re = re.compile(r'last', re.IGNORECASE)
|
||||
|
||||
def __init__(self):
|
||||
super(LastDayOfMonthExpression, self).__init__(None)
|
||||
|
||||
def get_next_value(self, date, field):
|
||||
return monthrange(date.year, date.month)[1]
|
||||
|
||||
def __str__(self):
|
||||
return 'last'
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
111
venv/Lib/site-packages/apscheduler/triggers/cron/fields.py
Normal file
111
venv/Lib/site-packages/apscheduler/triggers/cron/fields.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""Fields represent CronTrigger options which map to :class:`~datetime.datetime` fields."""
|
||||
|
||||
from calendar import monthrange
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from apscheduler.triggers.cron.expressions import (
|
||||
AllExpression, RangeExpression, WeekdayPositionExpression, LastDayOfMonthExpression,
|
||||
WeekdayRangeExpression, MonthRangeExpression)
|
||||
|
||||
|
||||
__all__ = ('MIN_VALUES', 'MAX_VALUES', 'DEFAULT_VALUES', 'BaseField', 'WeekField',
|
||||
'DayOfMonthField', 'DayOfWeekField')
|
||||
|
||||
|
||||
MIN_VALUES = {'year': 1970, 'month': 1, 'day': 1, 'week': 1, 'day_of_week': 0, 'hour': 0,
|
||||
'minute': 0, 'second': 0}
|
||||
MAX_VALUES = {'year': 9999, 'month': 12, 'day': 31, 'week': 53, 'day_of_week': 6, 'hour': 23,
|
||||
'minute': 59, 'second': 59}
|
||||
DEFAULT_VALUES = {'year': '*', 'month': 1, 'day': 1, 'week': '*', 'day_of_week': '*', 'hour': 0,
|
||||
'minute': 0, 'second': 0}
|
||||
SEPARATOR = re.compile(' *, *')
|
||||
|
||||
|
||||
class BaseField(object):
|
||||
REAL = True
|
||||
COMPILERS = [AllExpression, RangeExpression]
|
||||
|
||||
def __init__(self, name, exprs, is_default=False):
|
||||
self.name = name
|
||||
self.is_default = is_default
|
||||
self.compile_expressions(exprs)
|
||||
|
||||
def get_min(self, dateval):
|
||||
return MIN_VALUES[self.name]
|
||||
|
||||
def get_max(self, dateval):
|
||||
return MAX_VALUES[self.name]
|
||||
|
||||
def get_value(self, dateval):
|
||||
return getattr(dateval, self.name)
|
||||
|
||||
def get_next_value(self, dateval):
|
||||
smallest = None
|
||||
for expr in self.expressions:
|
||||
value = expr.get_next_value(dateval, self)
|
||||
if smallest is None or (value is not None and value < smallest):
|
||||
smallest = value
|
||||
|
||||
return smallest
|
||||
|
||||
def compile_expressions(self, exprs):
|
||||
self.expressions = []
|
||||
|
||||
# Split a comma-separated expression list, if any
|
||||
for expr in SEPARATOR.split(str(exprs).strip()):
|
||||
self.compile_expression(expr)
|
||||
|
||||
def compile_expression(self, expr):
|
||||
for compiler in self.COMPILERS:
|
||||
match = compiler.value_re.match(expr)
|
||||
if match:
|
||||
compiled_expr = compiler(**match.groupdict())
|
||||
|
||||
try:
|
||||
compiled_expr.validate_range(self.name)
|
||||
except ValueError as e:
|
||||
exc = ValueError('Error validating expression {!r}: {}'.format(expr, e))
|
||||
six.raise_from(exc, None)
|
||||
|
||||
self.expressions.append(compiled_expr)
|
||||
return
|
||||
|
||||
raise ValueError('Unrecognized expression "%s" for field "%s"' % (expr, self.name))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(self, self.__class__) and self.expressions == other.expressions
|
||||
|
||||
def __str__(self):
|
||||
expr_strings = (str(e) for e in self.expressions)
|
||||
return ','.join(expr_strings)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s('%s', '%s')" % (self.__class__.__name__, self.name, self)
|
||||
|
||||
|
||||
class WeekField(BaseField):
|
||||
REAL = False
|
||||
|
||||
def get_value(self, dateval):
|
||||
return dateval.isocalendar()[1]
|
||||
|
||||
|
||||
class DayOfMonthField(BaseField):
|
||||
COMPILERS = BaseField.COMPILERS + [WeekdayPositionExpression, LastDayOfMonthExpression]
|
||||
|
||||
def get_max(self, dateval):
|
||||
return monthrange(dateval.year, dateval.month)[1]
|
||||
|
||||
|
||||
class DayOfWeekField(BaseField):
|
||||
REAL = False
|
||||
COMPILERS = BaseField.COMPILERS + [WeekdayRangeExpression]
|
||||
|
||||
def get_value(self, dateval):
|
||||
return dateval.weekday()
|
||||
|
||||
|
||||
class MonthField(BaseField):
|
||||
COMPILERS = BaseField.COMPILERS + [MonthRangeExpression]
|
51
venv/Lib/site-packages/apscheduler/triggers/date.py
Normal file
51
venv/Lib/site-packages/apscheduler/triggers/date.py
Normal file
@ -0,0 +1,51 @@
|
||||
from datetime import datetime
|
||||
|
||||
from tzlocal import get_localzone
|
||||
|
||||
from apscheduler.triggers.base import BaseTrigger
|
||||
from apscheduler.util import convert_to_datetime, datetime_repr, astimezone
|
||||
|
||||
|
||||
class DateTrigger(BaseTrigger):
|
||||
"""
|
||||
Triggers once on the given datetime. If ``run_date`` is left empty, current time is used.
|
||||
|
||||
:param datetime|str run_date: the date/time to run the job at
|
||||
:param datetime.tzinfo|str timezone: time zone for ``run_date`` if it doesn't have one already
|
||||
"""
|
||||
|
||||
__slots__ = 'run_date'
|
||||
|
||||
def __init__(self, run_date=None, timezone=None):
|
||||
timezone = astimezone(timezone) or get_localzone()
|
||||
if run_date is not None:
|
||||
self.run_date = convert_to_datetime(run_date, timezone, 'run_date')
|
||||
else:
|
||||
self.run_date = datetime.now(timezone)
|
||||
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
return self.run_date if previous_fire_time is None else None
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'version': 1,
|
||||
'run_date': self.run_date
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
# This is for compatibility with APScheduler 3.0.x
|
||||
if isinstance(state, tuple):
|
||||
state = state[1]
|
||||
|
||||
if state.get('version', 1) > 1:
|
||||
raise ValueError(
|
||||
'Got serialized data for version %s of %s, but only version 1 can be handled' %
|
||||
(state['version'], self.__class__.__name__))
|
||||
|
||||
self.run_date = state['run_date']
|
||||
|
||||
def __str__(self):
|
||||
return 'date[%s]' % datetime_repr(self.run_date)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s (run_date='%s')>" % (self.__class__.__name__, datetime_repr(self.run_date))
|
106
venv/Lib/site-packages/apscheduler/triggers/interval.py
Normal file
106
venv/Lib/site-packages/apscheduler/triggers/interval.py
Normal file
@ -0,0 +1,106 @@
|
||||
from datetime import timedelta, datetime
|
||||
from math import ceil
|
||||
|
||||
from tzlocal import get_localzone
|
||||
|
||||
from apscheduler.triggers.base import BaseTrigger
|
||||
from apscheduler.util import convert_to_datetime, timedelta_seconds, datetime_repr, astimezone
|
||||
|
||||
|
||||
class IntervalTrigger(BaseTrigger):
|
||||
"""
|
||||
Triggers on specified intervals, starting on ``start_date`` if specified, ``datetime.now()`` +
|
||||
interval otherwise.
|
||||
|
||||
:param int weeks: number of weeks to wait
|
||||
:param int days: number of days to wait
|
||||
:param int hours: number of hours to wait
|
||||
:param int minutes: number of minutes to wait
|
||||
:param int seconds: number of seconds to wait
|
||||
:param datetime|str start_date: starting point for the interval calculation
|
||||
:param datetime|str end_date: latest possible date/time to trigger on
|
||||
:param datetime.tzinfo|str timezone: time zone to use for the date/time calculations
|
||||
:param int|None jitter: advance or delay the job execution by ``jitter`` seconds at most.
|
||||
"""
|
||||
|
||||
__slots__ = 'timezone', 'start_date', 'end_date', 'interval', 'interval_length', 'jitter'
|
||||
|
||||
def __init__(self, weeks=0, days=0, hours=0, minutes=0, seconds=0, start_date=None,
|
||||
end_date=None, timezone=None, jitter=None):
|
||||
self.interval = timedelta(weeks=weeks, days=days, hours=hours, minutes=minutes,
|
||||
seconds=seconds)
|
||||
self.interval_length = timedelta_seconds(self.interval)
|
||||
if self.interval_length == 0:
|
||||
self.interval = timedelta(seconds=1)
|
||||
self.interval_length = 1
|
||||
|
||||
if timezone:
|
||||
self.timezone = astimezone(timezone)
|
||||
elif isinstance(start_date, datetime) and start_date.tzinfo:
|
||||
self.timezone = start_date.tzinfo
|
||||
elif isinstance(end_date, datetime) and end_date.tzinfo:
|
||||
self.timezone = end_date.tzinfo
|
||||
else:
|
||||
self.timezone = get_localzone()
|
||||
|
||||
start_date = start_date or (datetime.now(self.timezone) + self.interval)
|
||||
self.start_date = convert_to_datetime(start_date, self.timezone, 'start_date')
|
||||
self.end_date = convert_to_datetime(end_date, self.timezone, 'end_date')
|
||||
|
||||
self.jitter = jitter
|
||||
|
||||
def get_next_fire_time(self, previous_fire_time, now):
|
||||
if previous_fire_time:
|
||||
next_fire_time = previous_fire_time + self.interval
|
||||
elif self.start_date > now:
|
||||
next_fire_time = self.start_date
|
||||
else:
|
||||
timediff_seconds = timedelta_seconds(now - self.start_date)
|
||||
next_interval_num = int(ceil(timediff_seconds / self.interval_length))
|
||||
next_fire_time = self.start_date + self.interval * next_interval_num
|
||||
|
||||
if self.jitter is not None:
|
||||
next_fire_time = self._apply_jitter(next_fire_time, self.jitter, now)
|
||||
|
||||
if not self.end_date or next_fire_time <= self.end_date:
|
||||
return self.timezone.normalize(next_fire_time)
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'version': 2,
|
||||
'timezone': self.timezone,
|
||||
'start_date': self.start_date,
|
||||
'end_date': self.end_date,
|
||||
'interval': self.interval,
|
||||
'jitter': self.jitter,
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
# This is for compatibility with APScheduler 3.0.x
|
||||
if isinstance(state, tuple):
|
||||
state = state[1]
|
||||
|
||||
if state.get('version', 1) > 2:
|
||||
raise ValueError(
|
||||
'Got serialized data for version %s of %s, but only versions up to 2 can be '
|
||||
'handled' % (state['version'], self.__class__.__name__))
|
||||
|
||||
self.timezone = state['timezone']
|
||||
self.start_date = state['start_date']
|
||||
self.end_date = state['end_date']
|
||||
self.interval = state['interval']
|
||||
self.interval_length = timedelta_seconds(self.interval)
|
||||
self.jitter = state.get('jitter')
|
||||
|
||||
def __str__(self):
|
||||
return 'interval[%s]' % str(self.interval)
|
||||
|
||||
def __repr__(self):
|
||||
options = ['interval=%r' % self.interval, 'start_date=%r' % datetime_repr(self.start_date)]
|
||||
if self.end_date:
|
||||
options.append("end_date=%r" % datetime_repr(self.end_date))
|
||||
if self.jitter:
|
||||
options.append('jitter=%s' % self.jitter)
|
||||
|
||||
return "<%s (%s, timezone='%s')>" % (
|
||||
self.__class__.__name__, ', '.join(options), self.timezone)
|
429
venv/Lib/site-packages/apscheduler/util.py
Normal file
429
venv/Lib/site-packages/apscheduler/util.py
Normal file
@ -0,0 +1,429 @@
|
||||
"""This module contains several handy functions primarily meant for internal use."""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
from datetime import date, datetime, time, timedelta, tzinfo
|
||||
from calendar import timegm
|
||||
from functools import partial
|
||||
from inspect import isclass, ismethod
|
||||
import re
|
||||
|
||||
from pytz import timezone, utc, FixedOffset
|
||||
import six
|
||||
|
||||
try:
|
||||
from inspect import signature
|
||||
except ImportError: # pragma: nocover
|
||||
from funcsigs import signature
|
||||
|
||||
try:
|
||||
from threading import TIMEOUT_MAX
|
||||
except ImportError:
|
||||
TIMEOUT_MAX = 4294967 # Maximum value accepted by Event.wait() on Windows
|
||||
|
||||
try:
|
||||
from asyncio import iscoroutinefunction
|
||||
except ImportError:
|
||||
try:
|
||||
from trollius import iscoroutinefunction
|
||||
except ImportError:
|
||||
def iscoroutinefunction(func):
|
||||
return False
|
||||
|
||||
__all__ = ('asint', 'asbool', 'astimezone', 'convert_to_datetime', 'datetime_to_utc_timestamp',
|
||||
'utc_timestamp_to_datetime', 'timedelta_seconds', 'datetime_ceil', 'get_callable_name',
|
||||
'obj_to_ref', 'ref_to_obj', 'maybe_ref', 'repr_escape', 'check_callable_args',
|
||||
'TIMEOUT_MAX')
|
||||
|
||||
|
||||
class _Undefined(object):
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return '<undefined>'
|
||||
|
||||
|
||||
undefined = _Undefined() #: a unique object that only signifies that no value is defined
|
||||
|
||||
|
||||
def asint(text):
|
||||
"""
|
||||
Safely converts a string to an integer, returning ``None`` if the string is ``None``.
|
||||
|
||||
:type text: str
|
||||
:rtype: int
|
||||
|
||||
"""
|
||||
if text is not None:
|
||||
return int(text)
|
||||
|
||||
|
||||
def asbool(obj):
|
||||
"""
|
||||
Interprets an object as a boolean value.
|
||||
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if isinstance(obj, str):
|
||||
obj = obj.strip().lower()
|
||||
if obj in ('true', 'yes', 'on', 'y', 't', '1'):
|
||||
return True
|
||||
if obj in ('false', 'no', 'off', 'n', 'f', '0'):
|
||||
return False
|
||||
raise ValueError('Unable to interpret value "%s" as boolean' % obj)
|
||||
return bool(obj)
|
||||
|
||||
|
||||
def astimezone(obj):
|
||||
"""
|
||||
Interprets an object as a timezone.
|
||||
|
||||
:rtype: tzinfo
|
||||
|
||||
"""
|
||||
if isinstance(obj, six.string_types):
|
||||
return timezone(obj)
|
||||
if isinstance(obj, tzinfo):
|
||||
if not hasattr(obj, 'localize') or not hasattr(obj, 'normalize'):
|
||||
raise TypeError('Only timezones from the pytz library are supported')
|
||||
if obj.zone == 'local':
|
||||
raise ValueError(
|
||||
'Unable to determine the name of the local timezone -- you must explicitly '
|
||||
'specify the name of the local timezone. Please refrain from using timezones like '
|
||||
'EST to prevent problems with daylight saving time. Instead, use a locale based '
|
||||
'timezone name (such as Europe/Helsinki).')
|
||||
return obj
|
||||
if obj is not None:
|
||||
raise TypeError('Expected tzinfo, got %s instead' % obj.__class__.__name__)
|
||||
|
||||
|
||||
_DATE_REGEX = re.compile(
|
||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
|
||||
r'(?:[ T](?P<hour>\d{1,2}):(?P<minute>\d{1,2}):(?P<second>\d{1,2})'
|
||||
r'(?:\.(?P<microsecond>\d{1,6}))?'
|
||||
r'(?P<timezone>Z|[+-]\d\d:\d\d)?)?$')
|
||||
|
||||
|
||||
def convert_to_datetime(input, tz, arg_name):
|
||||
"""
|
||||
Converts the given object to a timezone aware datetime object.
|
||||
|
||||
If a timezone aware datetime object is passed, it is returned unmodified.
|
||||
If a native datetime object is passed, it is given the specified timezone.
|
||||
If the input is a string, it is parsed as a datetime with the given timezone.
|
||||
|
||||
Date strings are accepted in three different forms: date only (Y-m-d), date with time
|
||||
(Y-m-d H:M:S) or with date+time with microseconds (Y-m-d H:M:S.micro). Additionally you can
|
||||
override the time zone by giving a specific offset in the format specified by ISO 8601:
|
||||
Z (UTC), +HH:MM or -HH:MM.
|
||||
|
||||
:param str|datetime input: the datetime or string to convert to a timezone aware datetime
|
||||
:param datetime.tzinfo tz: timezone to interpret ``input`` in
|
||||
:param str arg_name: the name of the argument (used in an error message)
|
||||
:rtype: datetime
|
||||
|
||||
"""
|
||||
if input is None:
|
||||
return
|
||||
elif isinstance(input, datetime):
|
||||
datetime_ = input
|
||||
elif isinstance(input, date):
|
||||
datetime_ = datetime.combine(input, time())
|
||||
elif isinstance(input, six.string_types):
|
||||
m = _DATE_REGEX.match(input)
|
||||
if not m:
|
||||
raise ValueError('Invalid date string')
|
||||
|
||||
values = m.groupdict()
|
||||
tzname = values.pop('timezone')
|
||||
if tzname == 'Z':
|
||||
tz = utc
|
||||
elif tzname:
|
||||
hours, minutes = (int(x) for x in tzname[1:].split(':'))
|
||||
sign = 1 if tzname[0] == '+' else -1
|
||||
tz = FixedOffset(sign * (hours * 60 + minutes))
|
||||
|
||||
values = {k: int(v or 0) for k, v in values.items()}
|
||||
datetime_ = datetime(**values)
|
||||
else:
|
||||
raise TypeError('Unsupported type for %s: %s' % (arg_name, input.__class__.__name__))
|
||||
|
||||
if datetime_.tzinfo is not None:
|
||||
return datetime_
|
||||
if tz is None:
|
||||
raise ValueError(
|
||||
'The "tz" argument must be specified if %s has no timezone information' % arg_name)
|
||||
if isinstance(tz, six.string_types):
|
||||
tz = timezone(tz)
|
||||
|
||||
try:
|
||||
return tz.localize(datetime_, is_dst=None)
|
||||
except AttributeError:
|
||||
raise TypeError(
|
||||
'Only pytz timezones are supported (need the localize() and normalize() methods)')
|
||||
|
||||
|
||||
def datetime_to_utc_timestamp(timeval):
|
||||
"""
|
||||
Converts a datetime instance to a timestamp.
|
||||
|
||||
:type timeval: datetime
|
||||
:rtype: float
|
||||
|
||||
"""
|
||||
if timeval is not None:
|
||||
return timegm(timeval.utctimetuple()) + timeval.microsecond / 1000000
|
||||
|
||||
|
||||
def utc_timestamp_to_datetime(timestamp):
|
||||
"""
|
||||
Converts the given timestamp to a datetime instance.
|
||||
|
||||
:type timestamp: float
|
||||
:rtype: datetime
|
||||
|
||||
"""
|
||||
if timestamp is not None:
|
||||
return datetime.fromtimestamp(timestamp, utc)
|
||||
|
||||
|
||||
def timedelta_seconds(delta):
|
||||
"""
|
||||
Converts the given timedelta to seconds.
|
||||
|
||||
:type delta: timedelta
|
||||
:rtype: float
|
||||
|
||||
"""
|
||||
return delta.days * 24 * 60 * 60 + delta.seconds + \
|
||||
delta.microseconds / 1000000.0
|
||||
|
||||
|
||||
def datetime_ceil(dateval):
|
||||
"""
|
||||
Rounds the given datetime object upwards.
|
||||
|
||||
:type dateval: datetime
|
||||
|
||||
"""
|
||||
if dateval.microsecond > 0:
|
||||
return dateval + timedelta(seconds=1, microseconds=-dateval.microsecond)
|
||||
return dateval
|
||||
|
||||
|
||||
def datetime_repr(dateval):
|
||||
return dateval.strftime('%Y-%m-%d %H:%M:%S %Z') if dateval else 'None'
|
||||
|
||||
|
||||
def get_callable_name(func):
|
||||
"""
|
||||
Returns the best available display name for the given function/callable.
|
||||
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
# the easy case (on Python 3.3+)
|
||||
if hasattr(func, '__qualname__'):
|
||||
return func.__qualname__
|
||||
|
||||
# class methods, bound and unbound methods
|
||||
f_self = getattr(func, '__self__', None) or getattr(func, 'im_self', None)
|
||||
if f_self and hasattr(func, '__name__'):
|
||||
f_class = f_self if isclass(f_self) else f_self.__class__
|
||||
else:
|
||||
f_class = getattr(func, 'im_class', None)
|
||||
|
||||
if f_class and hasattr(func, '__name__'):
|
||||
return '%s.%s' % (f_class.__name__, func.__name__)
|
||||
|
||||
# class or class instance
|
||||
if hasattr(func, '__call__'):
|
||||
# class
|
||||
if hasattr(func, '__name__'):
|
||||
return func.__name__
|
||||
|
||||
# instance of a class with a __call__ method
|
||||
return func.__class__.__name__
|
||||
|
||||
raise TypeError('Unable to determine a name for %r -- maybe it is not a callable?' % func)
|
||||
|
||||
|
||||
def obj_to_ref(obj):
|
||||
"""
|
||||
Returns the path to the given callable.
|
||||
|
||||
:rtype: str
|
||||
:raises TypeError: if the given object is not callable
|
||||
:raises ValueError: if the given object is a :class:`~functools.partial`, lambda or a nested
|
||||
function
|
||||
|
||||
"""
|
||||
if isinstance(obj, partial):
|
||||
raise ValueError('Cannot create a reference to a partial()')
|
||||
|
||||
name = get_callable_name(obj)
|
||||
if '<lambda>' in name:
|
||||
raise ValueError('Cannot create a reference to a lambda')
|
||||
if '<locals>' in name:
|
||||
raise ValueError('Cannot create a reference to a nested function')
|
||||
|
||||
if ismethod(obj):
|
||||
if hasattr(obj, 'im_self') and obj.im_self:
|
||||
# bound method
|
||||
module = obj.im_self.__module__
|
||||
elif hasattr(obj, 'im_class') and obj.im_class:
|
||||
# unbound method
|
||||
module = obj.im_class.__module__
|
||||
else:
|
||||
module = obj.__module__
|
||||
else:
|
||||
module = obj.__module__
|
||||
return '%s:%s' % (module, name)
|
||||
|
||||
|
||||
def ref_to_obj(ref):
|
||||
"""
|
||||
Returns the object pointed to by ``ref``.
|
||||
|
||||
:type ref: str
|
||||
|
||||
"""
|
||||
if not isinstance(ref, six.string_types):
|
||||
raise TypeError('References must be strings')
|
||||
if ':' not in ref:
|
||||
raise ValueError('Invalid reference')
|
||||
|
||||
modulename, rest = ref.split(':', 1)
|
||||
try:
|
||||
obj = __import__(modulename, fromlist=[rest])
|
||||
except ImportError:
|
||||
raise LookupError('Error resolving reference %s: could not import module' % ref)
|
||||
|
||||
try:
|
||||
for name in rest.split('.'):
|
||||
obj = getattr(obj, name)
|
||||
return obj
|
||||
except Exception:
|
||||
raise LookupError('Error resolving reference %s: error looking up object' % ref)
|
||||
|
||||
|
||||
def maybe_ref(ref):
|
||||
"""
|
||||
Returns the object that the given reference points to, if it is indeed a reference.
|
||||
If it is not a reference, the object is returned as-is.
|
||||
|
||||
"""
|
||||
if not isinstance(ref, str):
|
||||
return ref
|
||||
return ref_to_obj(ref)
|
||||
|
||||
|
||||
if six.PY2:
|
||||
def repr_escape(string):
|
||||
if isinstance(string, six.text_type):
|
||||
return string.encode('ascii', 'backslashreplace')
|
||||
return string
|
||||
else:
|
||||
def repr_escape(string):
|
||||
return string
|
||||
|
||||
|
||||
def check_callable_args(func, args, kwargs):
|
||||
"""
|
||||
Ensures that the given callable can be called with the given arguments.
|
||||
|
||||
:type args: tuple
|
||||
:type kwargs: dict
|
||||
|
||||
"""
|
||||
pos_kwargs_conflicts = [] # parameters that have a match in both args and kwargs
|
||||
positional_only_kwargs = [] # positional-only parameters that have a match in kwargs
|
||||
unsatisfied_args = [] # parameters in signature that don't have a match in args or kwargs
|
||||
unsatisfied_kwargs = [] # keyword-only arguments that don't have a match in kwargs
|
||||
unmatched_args = list(args) # args that didn't match any of the parameters in the signature
|
||||
# kwargs that didn't match any of the parameters in the signature
|
||||
unmatched_kwargs = list(kwargs)
|
||||
# indicates if the signature defines *args and **kwargs respectively
|
||||
has_varargs = has_var_kwargs = False
|
||||
|
||||
try:
|
||||
sig = signature(func)
|
||||
except ValueError:
|
||||
# signature() doesn't work against every kind of callable
|
||||
return
|
||||
|
||||
for param in six.itervalues(sig.parameters):
|
||||
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
||||
if param.name in unmatched_kwargs and unmatched_args:
|
||||
pos_kwargs_conflicts.append(param.name)
|
||||
elif unmatched_args:
|
||||
del unmatched_args[0]
|
||||
elif param.name in unmatched_kwargs:
|
||||
unmatched_kwargs.remove(param.name)
|
||||
elif param.default is param.empty:
|
||||
unsatisfied_args.append(param.name)
|
||||
elif param.kind == param.POSITIONAL_ONLY:
|
||||
if unmatched_args:
|
||||
del unmatched_args[0]
|
||||
elif param.name in unmatched_kwargs:
|
||||
unmatched_kwargs.remove(param.name)
|
||||
positional_only_kwargs.append(param.name)
|
||||
elif param.default is param.empty:
|
||||
unsatisfied_args.append(param.name)
|
||||
elif param.kind == param.KEYWORD_ONLY:
|
||||
if param.name in unmatched_kwargs:
|
||||
unmatched_kwargs.remove(param.name)
|
||||
elif param.default is param.empty:
|
||||
unsatisfied_kwargs.append(param.name)
|
||||
elif param.kind == param.VAR_POSITIONAL:
|
||||
has_varargs = True
|
||||
elif param.kind == param.VAR_KEYWORD:
|
||||
has_var_kwargs = True
|
||||
|
||||
# Make sure there are no conflicts between args and kwargs
|
||||
if pos_kwargs_conflicts:
|
||||
raise ValueError('The following arguments are supplied in both args and kwargs: %s' %
|
||||
', '.join(pos_kwargs_conflicts))
|
||||
|
||||
# Check if keyword arguments are being fed to positional-only parameters
|
||||
if positional_only_kwargs:
|
||||
raise ValueError('The following arguments cannot be given as keyword arguments: %s' %
|
||||
', '.join(positional_only_kwargs))
|
||||
|
||||
# Check that the number of positional arguments minus the number of matched kwargs matches the
|
||||
# argspec
|
||||
if unsatisfied_args:
|
||||
raise ValueError('The following arguments have not been supplied: %s' %
|
||||
', '.join(unsatisfied_args))
|
||||
|
||||
# Check that all keyword-only arguments have been supplied
|
||||
if unsatisfied_kwargs:
|
||||
raise ValueError(
|
||||
'The following keyword-only arguments have not been supplied in kwargs: %s' %
|
||||
', '.join(unsatisfied_kwargs))
|
||||
|
||||
# Check that the callable can accept the given number of positional arguments
|
||||
if not has_varargs and unmatched_args:
|
||||
raise ValueError(
|
||||
'The list of positional arguments is longer than the target callable can handle '
|
||||
'(allowed: %d, given in args: %d)' % (len(args) - len(unmatched_args), len(args)))
|
||||
|
||||
# Check that the callable can accept the given keyword arguments
|
||||
if not has_var_kwargs and unmatched_kwargs:
|
||||
raise ValueError(
|
||||
'The target callable does not accept the following keyword arguments: %s' %
|
||||
', '.join(unmatched_kwargs))
|
||||
|
||||
|
||||
def iscoroutinefunction_partial(f):
|
||||
while isinstance(f, partial):
|
||||
f = f.func
|
||||
|
||||
# The asyncio version of iscoroutinefunction includes testing for @coroutine
|
||||
# decorations vs. the inspect version which does not.
|
||||
return iscoroutinefunction(f)
|
598
venv/Lib/site-packages/pytz-2020.5.dist-info/DESCRIPTION.rst
Normal file
598
venv/Lib/site-packages/pytz-2020.5.dist-info/DESCRIPTION.rst
Normal file
@ -0,0 +1,598 @@
|
||||
pytz - World Timezone Definitions for Python
|
||||
============================================
|
||||
|
||||
:Author: Stuart Bishop <stuart@stuartbishop.net>
|
||||
|
||||
Introduction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
pytz brings the Olson tz database into Python. This library allows
|
||||
accurate and cross platform timezone calculations using Python 2.4
|
||||
or higher. It also solves the issue of ambiguous times at the end
|
||||
of daylight saving time, which you can read more about in the Python
|
||||
Library Reference (``datetime.tzinfo``).
|
||||
|
||||
Almost all of the Olson timezones are supported.
|
||||
|
||||
.. note::
|
||||
|
||||
This library differs from the documented Python API for
|
||||
tzinfo implementations; if you want to create local wallclock
|
||||
times you need to use the ``localize()`` method documented in this
|
||||
document. In addition, if you perform date arithmetic on local
|
||||
times that cross DST boundaries, the result may be in an incorrect
|
||||
timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get
|
||||
2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A
|
||||
``normalize()`` method is provided to correct this. Unfortunately these
|
||||
issues cannot be resolved without modifying the Python datetime
|
||||
implementation (see PEP-431).
|
||||
|
||||
|
||||
Installation
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This package can either be installed using ``pip`` or from a tarball using the
|
||||
standard Python distutils.
|
||||
|
||||
If you are installing using ``pip``, you don't need to download anything as the
|
||||
latest version will be downloaded for you from PyPI::
|
||||
|
||||
pip install pytz
|
||||
|
||||
If you are installing from a tarball, run the following command as an
|
||||
administrative user::
|
||||
|
||||
python setup.py install
|
||||
|
||||
|
||||
pytz for Enterprise
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Available as part of the Tidelift Subscription.
|
||||
|
||||
The maintainers of pytz and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. `Learn more. <https://tidelift.com/subscription/pkg/pypi-pytz?utm_source=pypi-pytz&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_.
|
||||
|
||||
|
||||
Example & Usage
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Localized times and date arithmetic
|
||||
-----------------------------------
|
||||
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> from pytz import timezone
|
||||
>>> import pytz
|
||||
>>> utc = pytz.utc
|
||||
>>> utc.zone
|
||||
'UTC'
|
||||
>>> eastern = timezone('US/Eastern')
|
||||
>>> eastern.zone
|
||||
'US/Eastern'
|
||||
>>> amsterdam = timezone('Europe/Amsterdam')
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z%z'
|
||||
|
||||
This library only supports two ways of building a localized time. The
|
||||
first is to use the ``localize()`` method provided by the pytz library.
|
||||
This is used to localize a naive datetime (datetime with no timezone
|
||||
information):
|
||||
|
||||
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
|
||||
>>> print(loc_dt.strftime(fmt))
|
||||
2002-10-27 06:00:00 EST-0500
|
||||
|
||||
The second way of building a localized time is by converting an existing
|
||||
localized time using the standard ``astimezone()`` method:
|
||||
|
||||
>>> ams_dt = loc_dt.astimezone(amsterdam)
|
||||
>>> ams_dt.strftime(fmt)
|
||||
'2002-10-27 12:00:00 CET+0100'
|
||||
|
||||
Unfortunately using the tzinfo argument of the standard datetime
|
||||
constructors ''does not work'' with pytz for many timezones.
|
||||
|
||||
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt) # /!\ Does not work this way!
|
||||
'2002-10-27 12:00:00 LMT+0020'
|
||||
|
||||
It is safe for timezones without daylight saving transitions though, such
|
||||
as UTC:
|
||||
|
||||
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt) # /!\ Not recommended except for UTC
|
||||
'2002-10-27 12:00:00 UTC+0000'
|
||||
|
||||
The preferred way of dealing with times is to always work in UTC,
|
||||
converting to localtime only when generating output to be read
|
||||
by humans.
|
||||
|
||||
>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
|
||||
>>> loc_dt = utc_dt.astimezone(eastern)
|
||||
>>> loc_dt.strftime(fmt)
|
||||
'2002-10-27 01:00:00 EST-0500'
|
||||
|
||||
This library also allows you to do date arithmetic using local
|
||||
times, although it is more complicated than working in UTC as you
|
||||
need to use the ``normalize()`` method to handle daylight saving time
|
||||
and other timezone transitions. In this example, ``loc_dt`` is set
|
||||
to the instant when daylight saving time ends in the US/Eastern
|
||||
timezone.
|
||||
|
||||
>>> before = loc_dt - timedelta(minutes=10)
|
||||
>>> before.strftime(fmt)
|
||||
'2002-10-27 00:50:00 EST-0500'
|
||||
>>> eastern.normalize(before).strftime(fmt)
|
||||
'2002-10-27 01:50:00 EDT-0400'
|
||||
>>> after = eastern.normalize(before + timedelta(minutes=20))
|
||||
>>> after.strftime(fmt)
|
||||
'2002-10-27 01:10:00 EST-0500'
|
||||
|
||||
Creating local times is also tricky, and the reason why working with
|
||||
local times is not recommended. Unfortunately, you cannot just pass
|
||||
a ``tzinfo`` argument when constructing a datetime (see the next
|
||||
section for more details)
|
||||
|
||||
>>> dt = datetime(2002, 10, 27, 1, 30, 0)
|
||||
>>> dt1 = eastern.localize(dt, is_dst=True)
|
||||
>>> dt1.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EDT-0400'
|
||||
>>> dt2 = eastern.localize(dt, is_dst=False)
|
||||
>>> dt2.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EST-0500'
|
||||
|
||||
Converting between timezones is more easily done, using the
|
||||
standard astimezone method.
|
||||
|
||||
>>> utc_dt = utc.localize(datetime.utcfromtimestamp(1143408899))
|
||||
>>> utc_dt.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> au_tz = timezone('Australia/Sydney')
|
||||
>>> au_dt = utc_dt.astimezone(au_tz)
|
||||
>>> au_dt.strftime(fmt)
|
||||
'2006-03-27 08:34:59 AEDT+1100'
|
||||
>>> utc_dt2 = au_dt.astimezone(utc)
|
||||
>>> utc_dt2.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> utc_dt == utc_dt2
|
||||
True
|
||||
|
||||
You can take shortcuts when dealing with the UTC side of timezone
|
||||
conversions. ``normalize()`` and ``localize()`` are not really
|
||||
necessary when there are no daylight saving time transitions to
|
||||
deal with.
|
||||
|
||||
>>> utc_dt = datetime.utcfromtimestamp(1143408899).replace(tzinfo=utc)
|
||||
>>> utc_dt.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> au_tz = timezone('Australia/Sydney')
|
||||
>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz))
|
||||
>>> au_dt.strftime(fmt)
|
||||
'2006-03-27 08:34:59 AEDT+1100'
|
||||
>>> utc_dt2 = au_dt.astimezone(utc)
|
||||
>>> utc_dt2.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
|
||||
|
||||
``tzinfo`` API
|
||||
--------------
|
||||
|
||||
The ``tzinfo`` instances returned by the ``timezone()`` function have
|
||||
been extended to cope with ambiguous times by adding an ``is_dst``
|
||||
parameter to the ``utcoffset()``, ``dst()`` && ``tzname()`` methods.
|
||||
|
||||
>>> tz = timezone('America/St_Johns')
|
||||
|
||||
>>> normal = datetime(2009, 9, 1)
|
||||
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
||||
|
||||
The ``is_dst`` parameter is ignored for most timestamps. It is only used
|
||||
during DST transition ambiguous periods to resolve that ambiguity.
|
||||
|
||||
>>> print(tz.utcoffset(normal, is_dst=True))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(normal, is_dst=True))
|
||||
1:00:00
|
||||
>>> tz.tzname(normal, is_dst=True)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(ambiguous, is_dst=True))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(ambiguous, is_dst=True))
|
||||
1:00:00
|
||||
>>> tz.tzname(ambiguous, is_dst=True)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(normal, is_dst=False))
|
||||
-1 day, 21:30:00
|
||||
>>> tz.dst(normal, is_dst=False)
|
||||
datetime.timedelta(0, 3600)
|
||||
>>> tz.tzname(normal, is_dst=False)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(ambiguous, is_dst=False))
|
||||
-1 day, 20:30:00
|
||||
>>> tz.dst(ambiguous, is_dst=False)
|
||||
datetime.timedelta(0)
|
||||
>>> tz.tzname(ambiguous, is_dst=False)
|
||||
'NST'
|
||||
|
||||
If ``is_dst`` is not specified, ambiguous timestamps will raise
|
||||
an ``pytz.exceptions.AmbiguousTimeError`` exception.
|
||||
|
||||
>>> print(tz.utcoffset(normal))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(normal))
|
||||
1:00:00
|
||||
>>> tz.tzname(normal)
|
||||
'NDT'
|
||||
|
||||
>>> import pytz.exceptions
|
||||
>>> try:
|
||||
... tz.utcoffset(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
>>> try:
|
||||
... tz.dst(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
>>> try:
|
||||
... tz.tzname(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
|
||||
|
||||
Problems with Localtime
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The major problem we have to deal with is that certain datetimes
|
||||
may occur twice in a year. For example, in the US/Eastern timezone
|
||||
on the last Sunday morning in October, the following sequence
|
||||
happens:
|
||||
|
||||
- 01:00 EDT occurs
|
||||
- 1 hour later, instead of 2:00am the clock is turned back 1 hour
|
||||
and 01:00 happens again (this time 01:00 EST)
|
||||
|
||||
In fact, every instant between 01:00 and 02:00 occurs twice. This means
|
||||
that if you try and create a time in the 'US/Eastern' timezone
|
||||
the standard datetime syntax, there is no way to specify if you meant
|
||||
before of after the end-of-daylight-saving-time transition. Using the
|
||||
pytz custom syntax, the best you can do is make an educated guess:
|
||||
|
||||
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 1, 30, 00))
|
||||
>>> loc_dt.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EST-0500'
|
||||
|
||||
As you can see, the system has chosen one for you and there is a 50%
|
||||
chance of it being out by one hour. For some applications, this does
|
||||
not matter. However, if you are trying to schedule meetings with people
|
||||
in different timezones or analyze log files it is not acceptable.
|
||||
|
||||
The best and simplest solution is to stick with using UTC. The pytz
|
||||
package encourages using UTC for internal timezone representation by
|
||||
including a special UTC implementation based on the standard Python
|
||||
reference implementation in the Python documentation.
|
||||
|
||||
The UTC timezone unpickles to be the same instance, and pickles to a
|
||||
smaller size than other pytz tzinfo instances. The UTC implementation
|
||||
can be obtained as pytz.utc, pytz.UTC, or pytz.timezone('UTC').
|
||||
|
||||
>>> import pickle, pytz
|
||||
>>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
|
||||
>>> naive = dt.replace(tzinfo=None)
|
||||
>>> p = pickle.dumps(dt, 1)
|
||||
>>> naive_p = pickle.dumps(naive, 1)
|
||||
>>> len(p) - len(naive_p)
|
||||
17
|
||||
>>> new = pickle.loads(p)
|
||||
>>> new == dt
|
||||
True
|
||||
>>> new is dt
|
||||
False
|
||||
>>> new.tzinfo is dt.tzinfo
|
||||
True
|
||||
>>> pytz.utc is pytz.UTC is pytz.timezone('UTC')
|
||||
True
|
||||
|
||||
Note that some other timezones are commonly thought of as the same (GMT,
|
||||
Greenwich, Universal, etc.). The definition of UTC is distinct from these
|
||||
other timezones, and they are not equivalent. For this reason, they will
|
||||
not compare the same in Python.
|
||||
|
||||
>>> utc == pytz.timezone('GMT')
|
||||
False
|
||||
|
||||
See the section `What is UTC`_, below.
|
||||
|
||||
If you insist on working with local times, this library provides a
|
||||
facility for constructing them unambiguously:
|
||||
|
||||
>>> loc_dt = datetime(2002, 10, 27, 1, 30, 00)
|
||||
>>> est_dt = eastern.localize(loc_dt, is_dst=True)
|
||||
>>> edt_dt = eastern.localize(loc_dt, is_dst=False)
|
||||
>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt))
|
||||
2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500
|
||||
|
||||
If you pass None as the is_dst flag to localize(), pytz will refuse to
|
||||
guess and raise exceptions if you try to build ambiguous or non-existent
|
||||
times.
|
||||
|
||||
For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern
|
||||
timezone when the clocks where put back at the end of Daylight Saving
|
||||
Time:
|
||||
|
||||
>>> dt = datetime(2002, 10, 27, 1, 30, 00)
|
||||
>>> try:
|
||||
... eastern.localize(dt, is_dst=None)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % dt)
|
||||
pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00
|
||||
|
||||
Similarly, 2:30am on 7th April 2002 never happened at all in the
|
||||
US/Eastern timezone, as the clocks where put forward at 2:00am skipping
|
||||
the entire hour:
|
||||
|
||||
>>> dt = datetime(2002, 4, 7, 2, 30, 00)
|
||||
>>> try:
|
||||
... eastern.localize(dt, is_dst=None)
|
||||
... except pytz.exceptions.NonExistentTimeError:
|
||||
... print('pytz.exceptions.NonExistentTimeError: %s' % dt)
|
||||
pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00
|
||||
|
||||
Both of these exceptions share a common base class to make error handling
|
||||
easier:
|
||||
|
||||
>>> isinstance(pytz.AmbiguousTimeError(), pytz.InvalidTimeError)
|
||||
True
|
||||
>>> isinstance(pytz.NonExistentTimeError(), pytz.InvalidTimeError)
|
||||
True
|
||||
|
||||
|
||||
A special case is where countries change their timezone definitions
|
||||
with no daylight savings time switch. For example, in 1915 Warsaw
|
||||
switched from Warsaw time to Central European time with no daylight savings
|
||||
transition. So at the stroke of midnight on August 5th 1915 the clocks
|
||||
were wound back 24 minutes creating an ambiguous time period that cannot
|
||||
be specified without referring to the timezone abbreviation or the
|
||||
actual UTC offset. In this case midnight happened twice, neither time
|
||||
during a daylight saving time period. pytz handles this transition by
|
||||
treating the ambiguous period before the switch as daylight savings
|
||||
time, and the ambiguous period after as standard time.
|
||||
|
||||
|
||||
>>> warsaw = pytz.timezone('Europe/Warsaw')
|
||||
>>> amb_dt1 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=True)
|
||||
>>> amb_dt1.strftime(fmt)
|
||||
'1915-08-04 23:59:59 WMT+0124'
|
||||
>>> amb_dt2 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=False)
|
||||
>>> amb_dt2.strftime(fmt)
|
||||
'1915-08-04 23:59:59 CET+0100'
|
||||
>>> switch_dt = warsaw.localize(datetime(1915, 8, 5, 00, 00, 00), is_dst=False)
|
||||
>>> switch_dt.strftime(fmt)
|
||||
'1915-08-05 00:00:00 CET+0100'
|
||||
>>> str(switch_dt - amb_dt1)
|
||||
'0:24:01'
|
||||
>>> str(switch_dt - amb_dt2)
|
||||
'0:00:01'
|
||||
|
||||
The best way of creating a time during an ambiguous time period is
|
||||
by converting from another timezone such as UTC:
|
||||
|
||||
>>> utc_dt = datetime(1915, 8, 4, 22, 36, tzinfo=pytz.utc)
|
||||
>>> utc_dt.astimezone(warsaw).strftime(fmt)
|
||||
'1915-08-04 23:36:00 CET+0100'
|
||||
|
||||
The standard Python way of handling all these ambiguities is not to
|
||||
handle them, such as demonstrated in this example using the US/Eastern
|
||||
timezone definition from the Python documentation (Note that this
|
||||
implementation only works for dates between 1987 and 2006 - it is
|
||||
included for tests only!):
|
||||
|
||||
>>> from pytz.reference import Eastern # pytz.reference only for tests
|
||||
>>> dt = datetime(2002, 10, 27, 0, 30, tzinfo=Eastern)
|
||||
>>> str(dt)
|
||||
'2002-10-27 00:30:00-04:00'
|
||||
>>> str(dt + timedelta(hours=1))
|
||||
'2002-10-27 01:30:00-05:00'
|
||||
>>> str(dt + timedelta(hours=2))
|
||||
'2002-10-27 02:30:00-05:00'
|
||||
>>> str(dt + timedelta(hours=3))
|
||||
'2002-10-27 03:30:00-05:00'
|
||||
|
||||
Notice the first two results? At first glance you might think they are
|
||||
correct, but taking the UTC offset into account you find that they are
|
||||
actually two hours appart instead of the 1 hour we asked for.
|
||||
|
||||
>>> from pytz.reference import UTC # pytz.reference only for tests
|
||||
>>> str(dt.astimezone(UTC))
|
||||
'2002-10-27 04:30:00+00:00'
|
||||
>>> str((dt + timedelta(hours=1)).astimezone(UTC))
|
||||
'2002-10-27 06:30:00+00:00'
|
||||
|
||||
|
||||
Country Information
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A mechanism is provided to access the timezones commonly in use
|
||||
for a particular country, looked up using the ISO 3166 country code.
|
||||
It returns a list of strings that can be used to retrieve the relevant
|
||||
tzinfo instance using ``pytz.timezone()``:
|
||||
|
||||
>>> print(' '.join(pytz.country_timezones['nz']))
|
||||
Pacific/Auckland Pacific/Chatham
|
||||
|
||||
The Olson database comes with a ISO 3166 country code to English country
|
||||
name mapping that pytz exposes as a dictionary:
|
||||
|
||||
>>> print(pytz.country_names['nz'])
|
||||
New Zealand
|
||||
|
||||
|
||||
What is UTC
|
||||
~~~~~~~~~~~
|
||||
|
||||
'UTC' is `Coordinated Universal Time`_. It is a successor to, but distinct
|
||||
from, Greenwich Mean Time (GMT) and the various definitions of Universal
|
||||
Time. UTC is now the worldwide standard for regulating clocks and time
|
||||
measurement.
|
||||
|
||||
All other timezones are defined relative to UTC, and include offsets like
|
||||
UTC+0800 - hours to add or subtract from UTC to derive the local time. No
|
||||
daylight saving time occurs in UTC, making it a useful timezone to perform
|
||||
date arithmetic without worrying about the confusion and ambiguities caused
|
||||
by daylight saving time transitions, your country changing its timezone, or
|
||||
mobile computers that roam through multiple timezones.
|
||||
|
||||
.. _Coordinated Universal Time: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
|
||||
|
||||
|
||||
Helpers
|
||||
~~~~~~~
|
||||
|
||||
There are two lists of timezones provided.
|
||||
|
||||
``all_timezones`` is the exhaustive list of the timezone names that can
|
||||
be used.
|
||||
|
||||
>>> from pytz import all_timezones
|
||||
>>> len(all_timezones) >= 500
|
||||
True
|
||||
>>> 'Etc/Greenwich' in all_timezones
|
||||
True
|
||||
|
||||
``common_timezones`` is a list of useful, current timezones. It doesn't
|
||||
contain deprecated zones or historical zones, except for a few I've
|
||||
deemed in common usage, such as US/Eastern (open a bug report if you
|
||||
think other timezones are deserving of being included here). It is also
|
||||
a sequence of strings.
|
||||
|
||||
>>> from pytz import common_timezones
|
||||
>>> len(common_timezones) < len(all_timezones)
|
||||
True
|
||||
>>> 'Etc/Greenwich' in common_timezones
|
||||
False
|
||||
>>> 'Australia/Melbourne' in common_timezones
|
||||
True
|
||||
>>> 'US/Eastern' in common_timezones
|
||||
True
|
||||
>>> 'Canada/Eastern' in common_timezones
|
||||
True
|
||||
>>> 'Australia/Yancowinna' in all_timezones
|
||||
True
|
||||
>>> 'Australia/Yancowinna' in common_timezones
|
||||
False
|
||||
|
||||
Both ``common_timezones`` and ``all_timezones`` are alphabetically
|
||||
sorted:
|
||||
|
||||
>>> common_timezones_dupe = common_timezones[:]
|
||||
>>> common_timezones_dupe.sort()
|
||||
>>> common_timezones == common_timezones_dupe
|
||||
True
|
||||
>>> all_timezones_dupe = all_timezones[:]
|
||||
>>> all_timezones_dupe.sort()
|
||||
>>> all_timezones == all_timezones_dupe
|
||||
True
|
||||
|
||||
``all_timezones`` and ``common_timezones`` are also available as sets.
|
||||
|
||||
>>> from pytz import all_timezones_set, common_timezones_set
|
||||
>>> 'US/Eastern' in all_timezones_set
|
||||
True
|
||||
>>> 'US/Eastern' in common_timezones_set
|
||||
True
|
||||
>>> 'Australia/Victoria' in common_timezones_set
|
||||
False
|
||||
|
||||
You can also retrieve lists of timezones used by particular countries
|
||||
using the ``country_timezones()`` function. It requires an ISO-3166
|
||||
two letter country code.
|
||||
|
||||
>>> from pytz import country_timezones
|
||||
>>> print(' '.join(country_timezones('ch')))
|
||||
Europe/Zurich
|
||||
>>> print(' '.join(country_timezones('CH')))
|
||||
Europe/Zurich
|
||||
|
||||
|
||||
Internationalization - i18n/l10n
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pytz is an interface to the IANA database, which uses ASCII names. The `Unicode Consortium's Unicode Locales (CLDR) <http://cldr.unicode.org>`_
|
||||
project provides translations. Thomas Khyn's
|
||||
`l18n <https://pypi.org/project/l18n/>`_ package can be used to access
|
||||
these translations from Python.
|
||||
|
||||
|
||||
License
|
||||
~~~~~~~
|
||||
|
||||
MIT license.
|
||||
|
||||
This code is also available as part of Zope 3 under the Zope Public
|
||||
License, Version 2.1 (ZPL).
|
||||
|
||||
I'm happy to relicense this code if necessary for inclusion in other
|
||||
open source projects.
|
||||
|
||||
|
||||
Latest Versions
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This package will be updated after releases of the Olson timezone
|
||||
database. The latest version can be downloaded from the `Python Package
|
||||
Index <https://pypi.org/project/pytz/>`_. The code that is used
|
||||
to generate this distribution is hosted on launchpad.net and available
|
||||
using git::
|
||||
|
||||
git clone https://git.launchpad.net/pytz
|
||||
|
||||
A mirror on github is also available at https://github.com/stub42/pytz
|
||||
|
||||
Announcements of new releases are made on
|
||||
`Launchpad <https://launchpad.net/pytz>`_, and the
|
||||
`Atom feed <http://feeds.launchpad.net/pytz/announcements.atom>`_
|
||||
hosted there.
|
||||
|
||||
|
||||
Bugs, Feature Requests & Patches
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Bugs can be reported using `Launchpad Bugs <https://bugs.launchpad.net/pytz>`_.
|
||||
|
||||
|
||||
Security Issues
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Reports about security issues can be made via `Tidelift <https://tidelift.com/security>`_.
|
||||
|
||||
|
||||
Issues & Limitations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Offsets from UTC are rounded to the nearest whole minute, so timezones
|
||||
such as Europe/Amsterdam pre 1937 will be up to 30 seconds out. This
|
||||
is a limitation of the Python datetime library.
|
||||
|
||||
- If you think a timezone definition is incorrect, I probably can't fix
|
||||
it. pytz is a direct translation of the Olson timezone database, and
|
||||
changes to the timezone definitions need to be made to this source.
|
||||
If you find errors they should be reported to the time zone mailing
|
||||
list, linked from http://www.iana.org/time-zones.
|
||||
|
||||
|
||||
Further Reading
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
More info than you want to know about timezones:
|
||||
http://www.twinsun.com/tz/tz-link.htm
|
||||
|
||||
|
||||
Contact
|
||||
~~~~~~~
|
||||
|
||||
Stuart Bishop <stuart@stuartbishop.net>
|
||||
|
||||
|
||||
|
||||
|
19
venv/Lib/site-packages/pytz-2020.5.dist-info/LICENSE.txt
Normal file
19
venv/Lib/site-packages/pytz-2020.5.dist-info/LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2003-2019 Stuart Bishop <stuart@stuartbishop.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
634
venv/Lib/site-packages/pytz-2020.5.dist-info/METADATA
Normal file
634
venv/Lib/site-packages/pytz-2020.5.dist-info/METADATA
Normal file
@ -0,0 +1,634 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: pytz
|
||||
Version: 2020.5
|
||||
Summary: World timezone definitions, modern and historical
|
||||
Home-page: http://pythonhosted.org/pytz
|
||||
Author: Stuart Bishop
|
||||
Author-email: stuart@stuartbishop.net
|
||||
Maintainer: Stuart Bishop
|
||||
Maintainer-email: stuart@stuartbishop.net
|
||||
License: MIT
|
||||
Download-URL: https://pypi.org/project/pytz/
|
||||
Keywords: timezone,tzinfo,datetime,olson,time
|
||||
Platform: Independent
|
||||
Classifier: Development Status :: 6 - Mature
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.4
|
||||
Classifier: Programming Language :: Python :: 2.5
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
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: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
|
||||
pytz - World Timezone Definitions for Python
|
||||
============================================
|
||||
|
||||
:Author: Stuart Bishop <stuart@stuartbishop.net>
|
||||
|
||||
Introduction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
pytz brings the Olson tz database into Python. This library allows
|
||||
accurate and cross platform timezone calculations using Python 2.4
|
||||
or higher. It also solves the issue of ambiguous times at the end
|
||||
of daylight saving time, which you can read more about in the Python
|
||||
Library Reference (``datetime.tzinfo``).
|
||||
|
||||
Almost all of the Olson timezones are supported.
|
||||
|
||||
.. note::
|
||||
|
||||
This library differs from the documented Python API for
|
||||
tzinfo implementations; if you want to create local wallclock
|
||||
times you need to use the ``localize()`` method documented in this
|
||||
document. In addition, if you perform date arithmetic on local
|
||||
times that cross DST boundaries, the result may be in an incorrect
|
||||
timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get
|
||||
2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A
|
||||
``normalize()`` method is provided to correct this. Unfortunately these
|
||||
issues cannot be resolved without modifying the Python datetime
|
||||
implementation (see PEP-431).
|
||||
|
||||
|
||||
Installation
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This package can either be installed using ``pip`` or from a tarball using the
|
||||
standard Python distutils.
|
||||
|
||||
If you are installing using ``pip``, you don't need to download anything as the
|
||||
latest version will be downloaded for you from PyPI::
|
||||
|
||||
pip install pytz
|
||||
|
||||
If you are installing from a tarball, run the following command as an
|
||||
administrative user::
|
||||
|
||||
python setup.py install
|
||||
|
||||
|
||||
pytz for Enterprise
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Available as part of the Tidelift Subscription.
|
||||
|
||||
The maintainers of pytz and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. `Learn more. <https://tidelift.com/subscription/pkg/pypi-pytz?utm_source=pypi-pytz&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_.
|
||||
|
||||
|
||||
Example & Usage
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Localized times and date arithmetic
|
||||
-----------------------------------
|
||||
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> from pytz import timezone
|
||||
>>> import pytz
|
||||
>>> utc = pytz.utc
|
||||
>>> utc.zone
|
||||
'UTC'
|
||||
>>> eastern = timezone('US/Eastern')
|
||||
>>> eastern.zone
|
||||
'US/Eastern'
|
||||
>>> amsterdam = timezone('Europe/Amsterdam')
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z%z'
|
||||
|
||||
This library only supports two ways of building a localized time. The
|
||||
first is to use the ``localize()`` method provided by the pytz library.
|
||||
This is used to localize a naive datetime (datetime with no timezone
|
||||
information):
|
||||
|
||||
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
|
||||
>>> print(loc_dt.strftime(fmt))
|
||||
2002-10-27 06:00:00 EST-0500
|
||||
|
||||
The second way of building a localized time is by converting an existing
|
||||
localized time using the standard ``astimezone()`` method:
|
||||
|
||||
>>> ams_dt = loc_dt.astimezone(amsterdam)
|
||||
>>> ams_dt.strftime(fmt)
|
||||
'2002-10-27 12:00:00 CET+0100'
|
||||
|
||||
Unfortunately using the tzinfo argument of the standard datetime
|
||||
constructors ''does not work'' with pytz for many timezones.
|
||||
|
||||
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt) # /!\ Does not work this way!
|
||||
'2002-10-27 12:00:00 LMT+0020'
|
||||
|
||||
It is safe for timezones without daylight saving transitions though, such
|
||||
as UTC:
|
||||
|
||||
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt) # /!\ Not recommended except for UTC
|
||||
'2002-10-27 12:00:00 UTC+0000'
|
||||
|
||||
The preferred way of dealing with times is to always work in UTC,
|
||||
converting to localtime only when generating output to be read
|
||||
by humans.
|
||||
|
||||
>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
|
||||
>>> loc_dt = utc_dt.astimezone(eastern)
|
||||
>>> loc_dt.strftime(fmt)
|
||||
'2002-10-27 01:00:00 EST-0500'
|
||||
|
||||
This library also allows you to do date arithmetic using local
|
||||
times, although it is more complicated than working in UTC as you
|
||||
need to use the ``normalize()`` method to handle daylight saving time
|
||||
and other timezone transitions. In this example, ``loc_dt`` is set
|
||||
to the instant when daylight saving time ends in the US/Eastern
|
||||
timezone.
|
||||
|
||||
>>> before = loc_dt - timedelta(minutes=10)
|
||||
>>> before.strftime(fmt)
|
||||
'2002-10-27 00:50:00 EST-0500'
|
||||
>>> eastern.normalize(before).strftime(fmt)
|
||||
'2002-10-27 01:50:00 EDT-0400'
|
||||
>>> after = eastern.normalize(before + timedelta(minutes=20))
|
||||
>>> after.strftime(fmt)
|
||||
'2002-10-27 01:10:00 EST-0500'
|
||||
|
||||
Creating local times is also tricky, and the reason why working with
|
||||
local times is not recommended. Unfortunately, you cannot just pass
|
||||
a ``tzinfo`` argument when constructing a datetime (see the next
|
||||
section for more details)
|
||||
|
||||
>>> dt = datetime(2002, 10, 27, 1, 30, 0)
|
||||
>>> dt1 = eastern.localize(dt, is_dst=True)
|
||||
>>> dt1.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EDT-0400'
|
||||
>>> dt2 = eastern.localize(dt, is_dst=False)
|
||||
>>> dt2.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EST-0500'
|
||||
|
||||
Converting between timezones is more easily done, using the
|
||||
standard astimezone method.
|
||||
|
||||
>>> utc_dt = utc.localize(datetime.utcfromtimestamp(1143408899))
|
||||
>>> utc_dt.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> au_tz = timezone('Australia/Sydney')
|
||||
>>> au_dt = utc_dt.astimezone(au_tz)
|
||||
>>> au_dt.strftime(fmt)
|
||||
'2006-03-27 08:34:59 AEDT+1100'
|
||||
>>> utc_dt2 = au_dt.astimezone(utc)
|
||||
>>> utc_dt2.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> utc_dt == utc_dt2
|
||||
True
|
||||
|
||||
You can take shortcuts when dealing with the UTC side of timezone
|
||||
conversions. ``normalize()`` and ``localize()`` are not really
|
||||
necessary when there are no daylight saving time transitions to
|
||||
deal with.
|
||||
|
||||
>>> utc_dt = datetime.utcfromtimestamp(1143408899).replace(tzinfo=utc)
|
||||
>>> utc_dt.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
>>> au_tz = timezone('Australia/Sydney')
|
||||
>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz))
|
||||
>>> au_dt.strftime(fmt)
|
||||
'2006-03-27 08:34:59 AEDT+1100'
|
||||
>>> utc_dt2 = au_dt.astimezone(utc)
|
||||
>>> utc_dt2.strftime(fmt)
|
||||
'2006-03-26 21:34:59 UTC+0000'
|
||||
|
||||
|
||||
``tzinfo`` API
|
||||
--------------
|
||||
|
||||
The ``tzinfo`` instances returned by the ``timezone()`` function have
|
||||
been extended to cope with ambiguous times by adding an ``is_dst``
|
||||
parameter to the ``utcoffset()``, ``dst()`` && ``tzname()`` methods.
|
||||
|
||||
>>> tz = timezone('America/St_Johns')
|
||||
|
||||
>>> normal = datetime(2009, 9, 1)
|
||||
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
||||
|
||||
The ``is_dst`` parameter is ignored for most timestamps. It is only used
|
||||
during DST transition ambiguous periods to resolve that ambiguity.
|
||||
|
||||
>>> print(tz.utcoffset(normal, is_dst=True))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(normal, is_dst=True))
|
||||
1:00:00
|
||||
>>> tz.tzname(normal, is_dst=True)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(ambiguous, is_dst=True))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(ambiguous, is_dst=True))
|
||||
1:00:00
|
||||
>>> tz.tzname(ambiguous, is_dst=True)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(normal, is_dst=False))
|
||||
-1 day, 21:30:00
|
||||
>>> tz.dst(normal, is_dst=False)
|
||||
datetime.timedelta(0, 3600)
|
||||
>>> tz.tzname(normal, is_dst=False)
|
||||
'NDT'
|
||||
|
||||
>>> print(tz.utcoffset(ambiguous, is_dst=False))
|
||||
-1 day, 20:30:00
|
||||
>>> tz.dst(ambiguous, is_dst=False)
|
||||
datetime.timedelta(0)
|
||||
>>> tz.tzname(ambiguous, is_dst=False)
|
||||
'NST'
|
||||
|
||||
If ``is_dst`` is not specified, ambiguous timestamps will raise
|
||||
an ``pytz.exceptions.AmbiguousTimeError`` exception.
|
||||
|
||||
>>> print(tz.utcoffset(normal))
|
||||
-1 day, 21:30:00
|
||||
>>> print(tz.dst(normal))
|
||||
1:00:00
|
||||
>>> tz.tzname(normal)
|
||||
'NDT'
|
||||
|
||||
>>> import pytz.exceptions
|
||||
>>> try:
|
||||
... tz.utcoffset(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
>>> try:
|
||||
... tz.dst(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
>>> try:
|
||||
... tz.tzname(ambiguous)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
|
||||
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
|
||||
|
||||
|
||||
Problems with Localtime
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The major problem we have to deal with is that certain datetimes
|
||||
may occur twice in a year. For example, in the US/Eastern timezone
|
||||
on the last Sunday morning in October, the following sequence
|
||||
happens:
|
||||
|
||||
- 01:00 EDT occurs
|
||||
- 1 hour later, instead of 2:00am the clock is turned back 1 hour
|
||||
and 01:00 happens again (this time 01:00 EST)
|
||||
|
||||
In fact, every instant between 01:00 and 02:00 occurs twice. This means
|
||||
that if you try and create a time in the 'US/Eastern' timezone
|
||||
the standard datetime syntax, there is no way to specify if you meant
|
||||
before of after the end-of-daylight-saving-time transition. Using the
|
||||
pytz custom syntax, the best you can do is make an educated guess:
|
||||
|
||||
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 1, 30, 00))
|
||||
>>> loc_dt.strftime(fmt)
|
||||
'2002-10-27 01:30:00 EST-0500'
|
||||
|
||||
As you can see, the system has chosen one for you and there is a 50%
|
||||
chance of it being out by one hour. For some applications, this does
|
||||
not matter. However, if you are trying to schedule meetings with people
|
||||
in different timezones or analyze log files it is not acceptable.
|
||||
|
||||
The best and simplest solution is to stick with using UTC. The pytz
|
||||
package encourages using UTC for internal timezone representation by
|
||||
including a special UTC implementation based on the standard Python
|
||||
reference implementation in the Python documentation.
|
||||
|
||||
The UTC timezone unpickles to be the same instance, and pickles to a
|
||||
smaller size than other pytz tzinfo instances. The UTC implementation
|
||||
can be obtained as pytz.utc, pytz.UTC, or pytz.timezone('UTC').
|
||||
|
||||
>>> import pickle, pytz
|
||||
>>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
|
||||
>>> naive = dt.replace(tzinfo=None)
|
||||
>>> p = pickle.dumps(dt, 1)
|
||||
>>> naive_p = pickle.dumps(naive, 1)
|
||||
>>> len(p) - len(naive_p)
|
||||
17
|
||||
>>> new = pickle.loads(p)
|
||||
>>> new == dt
|
||||
True
|
||||
>>> new is dt
|
||||
False
|
||||
>>> new.tzinfo is dt.tzinfo
|
||||
True
|
||||
>>> pytz.utc is pytz.UTC is pytz.timezone('UTC')
|
||||
True
|
||||
|
||||
Note that some other timezones are commonly thought of as the same (GMT,
|
||||
Greenwich, Universal, etc.). The definition of UTC is distinct from these
|
||||
other timezones, and they are not equivalent. For this reason, they will
|
||||
not compare the same in Python.
|
||||
|
||||
>>> utc == pytz.timezone('GMT')
|
||||
False
|
||||
|
||||
See the section `What is UTC`_, below.
|
||||
|
||||
If you insist on working with local times, this library provides a
|
||||
facility for constructing them unambiguously:
|
||||
|
||||
>>> loc_dt = datetime(2002, 10, 27, 1, 30, 00)
|
||||
>>> est_dt = eastern.localize(loc_dt, is_dst=True)
|
||||
>>> edt_dt = eastern.localize(loc_dt, is_dst=False)
|
||||
>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt))
|
||||
2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500
|
||||
|
||||
If you pass None as the is_dst flag to localize(), pytz will refuse to
|
||||
guess and raise exceptions if you try to build ambiguous or non-existent
|
||||
times.
|
||||
|
||||
For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern
|
||||
timezone when the clocks where put back at the end of Daylight Saving
|
||||
Time:
|
||||
|
||||
>>> dt = datetime(2002, 10, 27, 1, 30, 00)
|
||||
>>> try:
|
||||
... eastern.localize(dt, is_dst=None)
|
||||
... except pytz.exceptions.AmbiguousTimeError:
|
||||
... print('pytz.exceptions.AmbiguousTimeError: %s' % dt)
|
||||
pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00
|
||||
|
||||
Similarly, 2:30am on 7th April 2002 never happened at all in the
|
||||
US/Eastern timezone, as the clocks where put forward at 2:00am skipping
|
||||
the entire hour:
|
||||
|
||||
>>> dt = datetime(2002, 4, 7, 2, 30, 00)
|
||||
>>> try:
|
||||
... eastern.localize(dt, is_dst=None)
|
||||
... except pytz.exceptions.NonExistentTimeError:
|
||||
... print('pytz.exceptions.NonExistentTimeError: %s' % dt)
|
||||
pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00
|
||||
|
||||
Both of these exceptions share a common base class to make error handling
|
||||
easier:
|
||||
|
||||
>>> isinstance(pytz.AmbiguousTimeError(), pytz.InvalidTimeError)
|
||||
True
|
||||
>>> isinstance(pytz.NonExistentTimeError(), pytz.InvalidTimeError)
|
||||
True
|
||||
|
||||
|
||||
A special case is where countries change their timezone definitions
|
||||
with no daylight savings time switch. For example, in 1915 Warsaw
|
||||
switched from Warsaw time to Central European time with no daylight savings
|
||||
transition. So at the stroke of midnight on August 5th 1915 the clocks
|
||||
were wound back 24 minutes creating an ambiguous time period that cannot
|
||||
be specified without referring to the timezone abbreviation or the
|
||||
actual UTC offset. In this case midnight happened twice, neither time
|
||||
during a daylight saving time period. pytz handles this transition by
|
||||
treating the ambiguous period before the switch as daylight savings
|
||||
time, and the ambiguous period after as standard time.
|
||||
|
||||
|
||||
>>> warsaw = pytz.timezone('Europe/Warsaw')
|
||||
>>> amb_dt1 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=True)
|
||||
>>> amb_dt1.strftime(fmt)
|
||||
'1915-08-04 23:59:59 WMT+0124'
|
||||
>>> amb_dt2 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=False)
|
||||
>>> amb_dt2.strftime(fmt)
|
||||
'1915-08-04 23:59:59 CET+0100'
|
||||
>>> switch_dt = warsaw.localize(datetime(1915, 8, 5, 00, 00, 00), is_dst=False)
|
||||
>>> switch_dt.strftime(fmt)
|
||||
'1915-08-05 00:00:00 CET+0100'
|
||||
>>> str(switch_dt - amb_dt1)
|
||||
'0:24:01'
|
||||
>>> str(switch_dt - amb_dt2)
|
||||
'0:00:01'
|
||||
|
||||
The best way of creating a time during an ambiguous time period is
|
||||
by converting from another timezone such as UTC:
|
||||
|
||||
>>> utc_dt = datetime(1915, 8, 4, 22, 36, tzinfo=pytz.utc)
|
||||
>>> utc_dt.astimezone(warsaw).strftime(fmt)
|
||||
'1915-08-04 23:36:00 CET+0100'
|
||||
|
||||
The standard Python way of handling all these ambiguities is not to
|
||||
handle them, such as demonstrated in this example using the US/Eastern
|
||||
timezone definition from the Python documentation (Note that this
|
||||
implementation only works for dates between 1987 and 2006 - it is
|
||||
included for tests only!):
|
||||
|
||||
>>> from pytz.reference import Eastern # pytz.reference only for tests
|
||||
>>> dt = datetime(2002, 10, 27, 0, 30, tzinfo=Eastern)
|
||||
>>> str(dt)
|
||||
'2002-10-27 00:30:00-04:00'
|
||||
>>> str(dt + timedelta(hours=1))
|
||||
'2002-10-27 01:30:00-05:00'
|
||||
>>> str(dt + timedelta(hours=2))
|
||||
'2002-10-27 02:30:00-05:00'
|
||||
>>> str(dt + timedelta(hours=3))
|
||||
'2002-10-27 03:30:00-05:00'
|
||||
|
||||
Notice the first two results? At first glance you might think they are
|
||||
correct, but taking the UTC offset into account you find that they are
|
||||
actually two hours appart instead of the 1 hour we asked for.
|
||||
|
||||
>>> from pytz.reference import UTC # pytz.reference only for tests
|
||||
>>> str(dt.astimezone(UTC))
|
||||
'2002-10-27 04:30:00+00:00'
|
||||
>>> str((dt + timedelta(hours=1)).astimezone(UTC))
|
||||
'2002-10-27 06:30:00+00:00'
|
||||
|
||||
|
||||
Country Information
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A mechanism is provided to access the timezones commonly in use
|
||||
for a particular country, looked up using the ISO 3166 country code.
|
||||
It returns a list of strings that can be used to retrieve the relevant
|
||||
tzinfo instance using ``pytz.timezone()``:
|
||||
|
||||
>>> print(' '.join(pytz.country_timezones['nz']))
|
||||
Pacific/Auckland Pacific/Chatham
|
||||
|
||||
The Olson database comes with a ISO 3166 country code to English country
|
||||
name mapping that pytz exposes as a dictionary:
|
||||
|
||||
>>> print(pytz.country_names['nz'])
|
||||
New Zealand
|
||||
|
||||
|
||||
What is UTC
|
||||
~~~~~~~~~~~
|
||||
|
||||
'UTC' is `Coordinated Universal Time`_. It is a successor to, but distinct
|
||||
from, Greenwich Mean Time (GMT) and the various definitions of Universal
|
||||
Time. UTC is now the worldwide standard for regulating clocks and time
|
||||
measurement.
|
||||
|
||||
All other timezones are defined relative to UTC, and include offsets like
|
||||
UTC+0800 - hours to add or subtract from UTC to derive the local time. No
|
||||
daylight saving time occurs in UTC, making it a useful timezone to perform
|
||||
date arithmetic without worrying about the confusion and ambiguities caused
|
||||
by daylight saving time transitions, your country changing its timezone, or
|
||||
mobile computers that roam through multiple timezones.
|
||||
|
||||
.. _Coordinated Universal Time: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
|
||||
|
||||
|
||||
Helpers
|
||||
~~~~~~~
|
||||
|
||||
There are two lists of timezones provided.
|
||||
|
||||
``all_timezones`` is the exhaustive list of the timezone names that can
|
||||
be used.
|
||||
|
||||
>>> from pytz import all_timezones
|
||||
>>> len(all_timezones) >= 500
|
||||
True
|
||||
>>> 'Etc/Greenwich' in all_timezones
|
||||
True
|
||||
|
||||
``common_timezones`` is a list of useful, current timezones. It doesn't
|
||||
contain deprecated zones or historical zones, except for a few I've
|
||||
deemed in common usage, such as US/Eastern (open a bug report if you
|
||||
think other timezones are deserving of being included here). It is also
|
||||
a sequence of strings.
|
||||
|
||||
>>> from pytz import common_timezones
|
||||
>>> len(common_timezones) < len(all_timezones)
|
||||
True
|
||||
>>> 'Etc/Greenwich' in common_timezones
|
||||
False
|
||||
>>> 'Australia/Melbourne' in common_timezones
|
||||
True
|
||||
>>> 'US/Eastern' in common_timezones
|
||||
True
|
||||
>>> 'Canada/Eastern' in common_timezones
|
||||
True
|
||||
>>> 'Australia/Yancowinna' in all_timezones
|
||||
True
|
||||
>>> 'Australia/Yancowinna' in common_timezones
|
||||
False
|
||||
|
||||
Both ``common_timezones`` and ``all_timezones`` are alphabetically
|
||||
sorted:
|
||||
|
||||
>>> common_timezones_dupe = common_timezones[:]
|
||||
>>> common_timezones_dupe.sort()
|
||||
>>> common_timezones == common_timezones_dupe
|
||||
True
|
||||
>>> all_timezones_dupe = all_timezones[:]
|
||||
>>> all_timezones_dupe.sort()
|
||||
>>> all_timezones == all_timezones_dupe
|
||||
True
|
||||
|
||||
``all_timezones`` and ``common_timezones`` are also available as sets.
|
||||
|
||||
>>> from pytz import all_timezones_set, common_timezones_set
|
||||
>>> 'US/Eastern' in all_timezones_set
|
||||
True
|
||||
>>> 'US/Eastern' in common_timezones_set
|
||||
True
|
||||
>>> 'Australia/Victoria' in common_timezones_set
|
||||
False
|
||||
|
||||
You can also retrieve lists of timezones used by particular countries
|
||||
using the ``country_timezones()`` function. It requires an ISO-3166
|
||||
two letter country code.
|
||||
|
||||
>>> from pytz import country_timezones
|
||||
>>> print(' '.join(country_timezones('ch')))
|
||||
Europe/Zurich
|
||||
>>> print(' '.join(country_timezones('CH')))
|
||||
Europe/Zurich
|
||||
|
||||
|
||||
Internationalization - i18n/l10n
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pytz is an interface to the IANA database, which uses ASCII names. The `Unicode Consortium's Unicode Locales (CLDR) <http://cldr.unicode.org>`_
|
||||
project provides translations. Thomas Khyn's
|
||||
`l18n <https://pypi.org/project/l18n/>`_ package can be used to access
|
||||
these translations from Python.
|
||||
|
||||
|
||||
License
|
||||
~~~~~~~
|
||||
|
||||
MIT license.
|
||||
|
||||
This code is also available as part of Zope 3 under the Zope Public
|
||||
License, Version 2.1 (ZPL).
|
||||
|
||||
I'm happy to relicense this code if necessary for inclusion in other
|
||||
open source projects.
|
||||
|
||||
|
||||
Latest Versions
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This package will be updated after releases of the Olson timezone
|
||||
database. The latest version can be downloaded from the `Python Package
|
||||
Index <https://pypi.org/project/pytz/>`_. The code that is used
|
||||
to generate this distribution is hosted on launchpad.net and available
|
||||
using git::
|
||||
|
||||
git clone https://git.launchpad.net/pytz
|
||||
|
||||
A mirror on github is also available at https://github.com/stub42/pytz
|
||||
|
||||
Announcements of new releases are made on
|
||||
`Launchpad <https://launchpad.net/pytz>`_, and the
|
||||
`Atom feed <http://feeds.launchpad.net/pytz/announcements.atom>`_
|
||||
hosted there.
|
||||
|
||||
|
||||
Bugs, Feature Requests & Patches
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Bugs can be reported using `Launchpad Bugs <https://bugs.launchpad.net/pytz>`_.
|
||||
|
||||
|
||||
Security Issues
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Reports about security issues can be made via `Tidelift <https://tidelift.com/security>`_.
|
||||
|
||||
|
||||
Issues & Limitations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Offsets from UTC are rounded to the nearest whole minute, so timezones
|
||||
such as Europe/Amsterdam pre 1937 will be up to 30 seconds out. This
|
||||
is a limitation of the Python datetime library.
|
||||
|
||||
- If you think a timezone definition is incorrect, I probably can't fix
|
||||
it. pytz is a direct translation of the Olson timezone database, and
|
||||
changes to the timezone definitions need to be made to this source.
|
||||
If you find errors they should be reported to the time zone mailing
|
||||
list, linked from http://www.iana.org/time-zones.
|
||||
|
||||
|
||||
Further Reading
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
More info than you want to know about timezones:
|
||||
http://www.twinsun.com/tz/tz-link.htm
|
||||
|
||||
|
||||
Contact
|
||||
~~~~~~~
|
||||
|
||||
Stuart Bishop <stuart@stuartbishop.net>
|
||||
|
||||
|
||||
|
||||
|
620
venv/Lib/site-packages/pytz-2020.5.dist-info/RECORD
Normal file
620
venv/Lib/site-packages/pytz-2020.5.dist-info/RECORD
Normal file
@ -0,0 +1,620 @@
|
||||
pytz-2020.5.dist-info/DESCRIPTION.rst,sha256=ovmwqIt48fGpf9TczEWesZm0IiGilc_OZOjBt_tim30,19920
|
||||
pytz-2020.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pytz-2020.5.dist-info/LICENSE.txt,sha256=vosaN-vibFkqkPbA6zMQOn84POL010mMCvmlJpkKB7g,1088
|
||||
pytz-2020.5.dist-info/METADATA,sha256=neCuI92q_0vlWvGOBjfjPO2H45Ci3-XOK0ST3BGYtus,21412
|
||||
pytz-2020.5.dist-info/RECORD,,
|
||||
pytz-2020.5.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
|
||||
pytz-2020.5.dist-info/metadata.json,sha256=ceZopxmcs5aQcAdi7Fkgkvm7rEDR217DdYuyyAsHFGc,1587
|
||||
pytz-2020.5.dist-info/top_level.txt,sha256=6xRYlt934v1yHb1JIrXgHyGxn3cqACvd-yE8ski_kcc,5
|
||||
pytz-2020.5.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
||||
pytz/__init__.py,sha256=OfK0oUZ8VCG5JB4nh32KtDfcqN_STCScQ0RuQb21vQs,35147
|
||||
pytz/__pycache__/__init__.cpython-36.pyc,,
|
||||
pytz/__pycache__/exceptions.cpython-36.pyc,,
|
||||
pytz/__pycache__/lazy.cpython-36.pyc,,
|
||||
pytz/__pycache__/reference.cpython-36.pyc,,
|
||||
pytz/__pycache__/tzfile.cpython-36.pyc,,
|
||||
pytz/__pycache__/tzinfo.cpython-36.pyc,,
|
||||
pytz/exceptions.py,sha256=434ZcuLlpLQY9mWoGq7zJMV1TyiYvVgpKBU1qZkbDjM,1571
|
||||
pytz/lazy.py,sha256=toeR5uDWKBj6ezsUZ4elNP6CEMtK7CO2jS9A30nsFbo,5404
|
||||
pytz/reference.py,sha256=zUtCki7JFEmrzrjNsfMD7YL0lWDxynKc1Ubo4iXSs74,3778
|
||||
pytz/tzfile.py,sha256=K2y7pZs4vydpZVftrfAA_-hgw17y1Szc7z_QCse6udU,4723
|
||||
pytz/tzinfo.py,sha256=-5UjW-yqHbtO5NtSaWope7EbSdf2oTES26Kdlxjqdk0,19272
|
||||
pytz/zoneinfo/Africa/Abidjan,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Accra,sha256=c0Z3DcevVpxyT9HOgW1xSf_f8-MDQgBZ-qFVfMlZ4RU,1060
|
||||
pytz/zoneinfo/Africa/Addis_Ababa,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Algiers,sha256=vaFpjNVCwObnbfu82rOQzdJvN6nVgmpXpQ1aqzfzsqY,735
|
||||
pytz/zoneinfo/Africa/Asmara,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Asmera,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Bamako,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Bangui,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Banjul,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Bissau,sha256=IjuxDP6EZiDHFvl_bHS6NN7sdRxLKXllooBC829poak,194
|
||||
pytz/zoneinfo/Africa/Blantyre,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Brazzaville,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Bujumbura,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Cairo,sha256=L6zLQLnQtLkEELOGfm6USaHY33qAEPgGV822-iU1vxc,1955
|
||||
pytz/zoneinfo/Africa/Casablanca,sha256=qzlDyFvkLZWy8Bydogdx_cxZCkWzRwEEsuVWstJI_-s,2429
|
||||
pytz/zoneinfo/Africa/Ceuta,sha256=jp7xqONgZ3NPnElHzJEVusHKM9rxDK1nxJm4-i7Ln8o,2036
|
||||
pytz/zoneinfo/Africa/Conakry,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Dakar,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Dar_es_Salaam,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Djibouti,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Douala,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/El_Aaiun,sha256=Ja0t5t3QHHrvY0EGgxadypAabj4GjMLuQTTbOAur5M0,2295
|
||||
pytz/zoneinfo/Africa/Freetown,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Gaborone,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Harare,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Johannesburg,sha256=bBvMdSZo53WFowiuhUO9C8zY6BOGViboCb-U8_49l34,246
|
||||
pytz/zoneinfo/Africa/Juba,sha256=TC4SaGEzvtDtdyU6lfxdqVQqDsNklvVokhqHZt_YteU,653
|
||||
pytz/zoneinfo/Africa/Kampala,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Khartoum,sha256=MYWDoJ3AcCItZdApoeOgtWWDDxquwTon5v5TOGP70-o,679
|
||||
pytz/zoneinfo/Africa/Kigali,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Kinshasa,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Lagos,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Libreville,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Lome,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Luanda,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Lubumbashi,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Lusaka,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Malabo,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Maputo,sha256=k_GelVHViGiuWCB1LSyTpIYSTDZEY9yclInQRY-LxoI,149
|
||||
pytz/zoneinfo/Africa/Maseru,sha256=bBvMdSZo53WFowiuhUO9C8zY6BOGViboCb-U8_49l34,246
|
||||
pytz/zoneinfo/Africa/Mbabane,sha256=bBvMdSZo53WFowiuhUO9C8zY6BOGViboCb-U8_49l34,246
|
||||
pytz/zoneinfo/Africa/Mogadishu,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Monrovia,sha256=-VsJW5cU4KdvfgYaQVv4lcuzmaKIVFMd42nO6RXOBdU,208
|
||||
pytz/zoneinfo/Africa/Nairobi,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Africa/Ndjamena,sha256=8T3A0Zm9Gj0Bvm6rd88t3GAXKiKdGUfHlIqYlkYI0KM,199
|
||||
pytz/zoneinfo/Africa/Niamey,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Nouakchott,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Ouagadougou,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Porto-Novo,sha256=z_6wKCzL1_ug5JP_hneh5abdUZeIUELkN_ladz-ESEY,235
|
||||
pytz/zoneinfo/Africa/Sao_Tome,sha256=MdjxpQ268uzJ7Zx1ZroFUtRUwqsJ6F_yY3AYV9FXw1I,254
|
||||
pytz/zoneinfo/Africa/Timbuktu,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Africa/Tripoli,sha256=W1dptGD70T7ppGoo0fczFQeDiIp0nultLNPV66MwB2c,625
|
||||
pytz/zoneinfo/Africa/Tunis,sha256=OFVMEM4eYT2Ez0beuhEUCTSIpcFldWxsV2uEoTZIUNI,689
|
||||
pytz/zoneinfo/Africa/Windhoek,sha256=xuhvudrMH4alnVmouSTQI8YL8F_HbgsF2EQ7AZKzuHs,955
|
||||
pytz/zoneinfo/America/Adak,sha256=IB1DhwJQAKbhPJ9jHLf8zW5Dad7HIkBS-dhv64E1OlM,2356
|
||||
pytz/zoneinfo/America/Anchorage,sha256=oZA1NSPS2BWdymYpnCHFO8BlYVS-ll5KLg2Ez9CbETs,2371
|
||||
pytz/zoneinfo/America/Anguilla,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Antigua,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Araguaina,sha256=kppiiytmSQeesflyNGYM3r8NVUl1C-ggu08s9_Tt-co,884
|
||||
pytz/zoneinfo/America/Argentina/Buenos_Aires,sha256=ntn_GFHadbrFJ4ZuhU6h2uzbFwmDyS9mXV5S28pkGF8,1076
|
||||
pytz/zoneinfo/America/Argentina/Catamarca,sha256=diH1f96kbbY-7gJYQnSCNHs3n9dwHJqUhSdGNx1L7I0,1076
|
||||
pytz/zoneinfo/America/Argentina/ComodRivadavia,sha256=diH1f96kbbY-7gJYQnSCNHs3n9dwHJqUhSdGNx1L7I0,1076
|
||||
pytz/zoneinfo/America/Argentina/Cordoba,sha256=1XqIP8Qo2bPR7909hrAI-qAttybmwEW4ms7FjZA5Yfw,1076
|
||||
pytz/zoneinfo/America/Argentina/Jujuy,sha256=5HR0TlZFifwJ5nLTmg7yWXgCTx9mRhahfs4_Wq70wOY,1048
|
||||
pytz/zoneinfo/America/Argentina/La_Rioja,sha256=Zf_E3akFE1YUt9MZ4xxbRnOrp2bH1D-Bjsc0SLFfRyU,1090
|
||||
pytz/zoneinfo/America/Argentina/Mendoza,sha256=5DJiYYeQpcLBR_IoIJtk43IswJeGYawx5GykszuJ-Nw,1076
|
||||
pytz/zoneinfo/America/Argentina/Rio_Gallegos,sha256=T97WADwva6JbxICviNQUt_7iw9c-nloI4QJCscENSck,1076
|
||||
pytz/zoneinfo/America/Argentina/Salta,sha256=ATw0uR6szWKPs6jzdn6revS7UxCXD26ORK6jlmsjL18,1048
|
||||
pytz/zoneinfo/America/Argentina/San_Juan,sha256=qlW693a0Tnofy-RdcVBuWY3DvTTGxWwcYdKU3Y98pX8,1090
|
||||
pytz/zoneinfo/America/Argentina/San_Luis,sha256=WYdcro5-Fe-N6LkQsKwx_1tVozmnBp58DO1-BJs2suo,1102
|
||||
pytz/zoneinfo/America/Argentina/Tucuman,sha256=wsjg1a5AM1dP2gjr112k3vt54trcOOM_StF74xzvBJc,1104
|
||||
pytz/zoneinfo/America/Argentina/Ushuaia,sha256=9548Vvq_kpw_NX5s65vYuIbqvwGV-PBxqwmcrflLI0U,1076
|
||||
pytz/zoneinfo/America/Aruba,sha256=ZGEIylAZ5iy_rIBsXREtH_ZfWRIkLI9dQjP_EIyn3sY,186
|
||||
pytz/zoneinfo/America/Asuncion,sha256=FTLtFk6MjJoh5VIDgJ2Sf4B_iNeCDxrV0MWwQL-sOVM,2044
|
||||
pytz/zoneinfo/America/Atikokan,sha256=4a94GtPHUdQ-2sdz9WinsKn9V_QiM4XmFj48FTPMeSA,336
|
||||
pytz/zoneinfo/America/Atka,sha256=IB1DhwJQAKbhPJ9jHLf8zW5Dad7HIkBS-dhv64E1OlM,2356
|
||||
pytz/zoneinfo/America/Bahia,sha256=cmLkSAAzINlzYGXBqADEU3uPgA9S5nt-p1AV3Zy86VY,1024
|
||||
pytz/zoneinfo/America/Bahia_Banderas,sha256=BNjbcHSlPsJ4UpJx-gs1hpIyx2ScBieh1nyDuGb0PcE,1546
|
||||
pytz/zoneinfo/America/Barbados,sha256=Y0XwBAv5eSAZHNixVIhRvPxNWqP2a1FCH2_z2Vrd-sc,314
|
||||
pytz/zoneinfo/America/Belem,sha256=_258hQZLCEXBX8xRLyQSw-AE-jiDmjVwJX32mN5UUEk,576
|
||||
pytz/zoneinfo/America/Belize,sha256=pkfLY2KfPchbeJa1pWcXmWAwp4ZlRvxWLVezXnrbkws,1614
|
||||
pytz/zoneinfo/America/Blanc-Sablon,sha256=tVN5ZPmIO3vc3_ayowg6qbvjheg4OJtDFT9y8IuW334,298
|
||||
pytz/zoneinfo/America/Boa_Vista,sha256=V4VVOkrFUV1qUfVp9E974IOJFmA5QxQrctatTBEb-hs,632
|
||||
pytz/zoneinfo/America/Bogota,sha256=ZaQKTZi35AMdlROs0vjEDA_phR8ztJOnjA8aLJZ5tHw,246
|
||||
pytz/zoneinfo/America/Boise,sha256=Yv4AXa2nSH_oVo3FZqZCR7V7z7c6WnQgKIUyNUpzGXA,2394
|
||||
pytz/zoneinfo/America/Buenos_Aires,sha256=ntn_GFHadbrFJ4ZuhU6h2uzbFwmDyS9mXV5S28pkGF8,1076
|
||||
pytz/zoneinfo/America/Cambridge_Bay,sha256=Nanl8yH4SshljhEjDe-PZCYEXbUuuZGmkbAAt2dB-bk,2084
|
||||
pytz/zoneinfo/America/Campo_Grande,sha256=5BBENR3_8gJp4F_Uj2RRknvRc4JJWNRPnZU9E7tb8QI,1444
|
||||
pytz/zoneinfo/America/Cancun,sha256=YR2U5T6mDGd5xm8EVA_TM1NwSRMYPNYWvV7wuthnX0I,782
|
||||
pytz/zoneinfo/America/Caracas,sha256=2NpwXPEtQkI82WCZuQWHXf66VCADcawMpfhKTsuA0x4,264
|
||||
pytz/zoneinfo/America/Catamarca,sha256=diH1f96kbbY-7gJYQnSCNHs3n9dwHJqUhSdGNx1L7I0,1076
|
||||
pytz/zoneinfo/America/Cayenne,sha256=atVbW5ChJiKQ_q-3kFs-DLTTZa9ptkiHkmJlq4AXoY4,198
|
||||
pytz/zoneinfo/America/Cayman,sha256=kayA_pdpMcSQ0FjIzotdcf-m1JYfbKE-qcFT8LC8zqA,182
|
||||
pytz/zoneinfo/America/Chicago,sha256=4aZFw-svkMyXmSpNufqzK-xveos-oVJDpEyI8Yu9HQE,3576
|
||||
pytz/zoneinfo/America/Chihuahua,sha256=cewXJyEw4KCoz33yl8o2tUJZmugBWH4R0Aovdmuqf-o,1484
|
||||
pytz/zoneinfo/America/Coral_Harbour,sha256=4a94GtPHUdQ-2sdz9WinsKn9V_QiM4XmFj48FTPMeSA,336
|
||||
pytz/zoneinfo/America/Cordoba,sha256=1XqIP8Qo2bPR7909hrAI-qAttybmwEW4ms7FjZA5Yfw,1076
|
||||
pytz/zoneinfo/America/Costa_Rica,sha256=74rYa6lrgIkyls9PkHo8SCYl9oOqiuG5S7MWdnJelP4,316
|
||||
pytz/zoneinfo/America/Creston,sha256=dNOa71QgQ2d5uh7cl-xZme-8u3nMR9GJ7PSktWIDORQ,208
|
||||
pytz/zoneinfo/America/Cuiaba,sha256=M0FsR8T9s4jFSuzD8Qi6pqtb6Rf2NTzyVHKGZrn56n4,1416
|
||||
pytz/zoneinfo/America/Curacao,sha256=ZGEIylAZ5iy_rIBsXREtH_ZfWRIkLI9dQjP_EIyn3sY,186
|
||||
pytz/zoneinfo/America/Danmarkshavn,sha256=YRZAfUCoVtaL1L-MYMYMH1wyOaVQnfUo_gFnvMXSuzw,698
|
||||
pytz/zoneinfo/America/Dawson,sha256=rAHhyuMuyjf_eyA2SBG76MRBf_fj_xi5FAuiWVQgJhw,1614
|
||||
pytz/zoneinfo/America/Dawson_Creek,sha256=aJXCyP4j3ggE4wGCN-LrS9hpD_5zWHzQTeSAKTWEPUM,1050
|
||||
pytz/zoneinfo/America/Denver,sha256=6_yPo1_mvnt9DgpPzr0QdHsjdsfUG6ALnagQLML1DSM,2444
|
||||
pytz/zoneinfo/America/Detroit,sha256=hecz8yqY2Cj5B61G3gLZdAVZvRgK9l0P90c_gN-uD5g,2230
|
||||
pytz/zoneinfo/America/Dominica,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Edmonton,sha256=-TkIfc3QlvaCf0p8COZ43Y1HRBAl-nARUi-JdXeK1vE,2332
|
||||
pytz/zoneinfo/America/Eirunepe,sha256=pS90HZzRwH4Tf8ugmKHfiphX7zCPqZkh_0CNb-fEMAM,656
|
||||
pytz/zoneinfo/America/El_Salvador,sha256=gvGN8Lkj-sGm2_rs8OUjAMf1oMtKp2Xes6UfWT0WqgU,224
|
||||
pytz/zoneinfo/America/Ensenada,sha256=OHHtvy3J70z6wvKBHgPqMEnGs6SXp8fkf0WX9ZiOODk,2342
|
||||
pytz/zoneinfo/America/Fort_Nelson,sha256=erfODr3DrSpz65kAdO7Ts2dGbZxvddEP6gx4BX3y2J0,2240
|
||||
pytz/zoneinfo/America/Fort_Wayne,sha256=GrNub1_3Um5Qh67wOx58_TEAz4fwAeAlk2AlMTVA_sI,1666
|
||||
pytz/zoneinfo/America/Fortaleza,sha256=mITuMrRLRTWyoiF04Oy_UZ8gxZofTpXDblM8t7ch7Sg,716
|
||||
pytz/zoneinfo/America/Glace_Bay,sha256=G8DGLGCapH_aYCF_OhaL5Qonf7FOAgAPwelO5htCWBc,2192
|
||||
pytz/zoneinfo/America/Godthab,sha256=FtlXWP_hBNuwBHkI2b1yne_tSUJpwLtWLyTHZoFZkmM,1878
|
||||
pytz/zoneinfo/America/Goose_Bay,sha256=JgaLueghSvX2g725FOfIgpgvsqxZGykWOhAZWGpQZRY,3210
|
||||
pytz/zoneinfo/America/Grand_Turk,sha256=4YOFEPK60Bel2_fCsY6vSZxUcMJKjiKtyOf_Q0khEwU,1834
|
||||
pytz/zoneinfo/America/Grenada,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Guadeloupe,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Guatemala,sha256=dugUgCd6QY52yHkHuUP4jRWzo5x439IQigaYCvEF46Q,280
|
||||
pytz/zoneinfo/America/Guayaquil,sha256=PbcF4bvGAm-aFwdtGPotJy3kb4NwoyWwxgwL98BeUWA,246
|
||||
pytz/zoneinfo/America/Guyana,sha256=mDyb0FtGOpwGPq864vAHX22LY0Pxex94f1wVMyo36d0,236
|
||||
pytz/zoneinfo/America/Halifax,sha256=TZpmc5PwWoLfTfQoQ_b3U17BE2iVKSeNkR0Ho8mbTn8,3424
|
||||
pytz/zoneinfo/America/Havana,sha256=HUQeAuKBsEkI5SLZjqynXICOUVOajkKzKH5r-Ov5Odc,2416
|
||||
pytz/zoneinfo/America/Hermosillo,sha256=9Ij30JYmMscC1XHi4o9v-uSXoUuE8V9zhGz2iV5hVFI,416
|
||||
pytz/zoneinfo/America/Indiana/Indianapolis,sha256=GrNub1_3Um5Qh67wOx58_TEAz4fwAeAlk2AlMTVA_sI,1666
|
||||
pytz/zoneinfo/America/Indiana/Knox,sha256=BiALShjiOLg1o8mMRWJ1jyTlJkgvwzte7B9WSOvTUNg,2428
|
||||
pytz/zoneinfo/America/Indiana/Marengo,sha256=CPYY3XgJFNEzONxei7x04wOGI_b86RAn4jBPewi1HZw,1722
|
||||
pytz/zoneinfo/America/Indiana/Petersburg,sha256=axot1SloP27ZWjezmo7kldu9qA2frEtPVqWngcXtft0,1904
|
||||
pytz/zoneinfo/America/Indiana/Tell_City,sha256=GrWNjb1i4sbIYlJ8fU0viJ2Q5JmrlvLgcLQILnk3El8,1684
|
||||
pytz/zoneinfo/America/Indiana/Vevay,sha256=GGosHbQUoIDOKPZxdal42X40veEITMmrnlKOnLUhb-c,1414
|
||||
pytz/zoneinfo/America/Indiana/Vincennes,sha256=gh7LAbHbMD92eo9C_c5IiwQ1fJvxhdJN402Q_4YJdLg,1694
|
||||
pytz/zoneinfo/America/Indiana/Winamac,sha256=yS-_aKSC4crd0WdNutkHRHxUjmBCU56QVQcqy7kYpbQ,1778
|
||||
pytz/zoneinfo/America/Indianapolis,sha256=GrNub1_3Um5Qh67wOx58_TEAz4fwAeAlk2AlMTVA_sI,1666
|
||||
pytz/zoneinfo/America/Inuvik,sha256=MU_oDiidQaijt1KV0B5h9LqHoCrJ8ieldD9tsiJiX5o,1894
|
||||
pytz/zoneinfo/America/Iqaluit,sha256=6PitEMSFWcSb-Io8fvm4oQ_7v39G_qANc6reTjXoZJ0,2032
|
||||
pytz/zoneinfo/America/Jamaica,sha256=wlagieUPRf5-beie-h7QsONbNzjGsm8vMs8uf28pw28,482
|
||||
pytz/zoneinfo/America/Jujuy,sha256=5HR0TlZFifwJ5nLTmg7yWXgCTx9mRhahfs4_Wq70wOY,1048
|
||||
pytz/zoneinfo/America/Juneau,sha256=k7hxb0aGRnfnE-DBi3LkcjAzRPyAf0_Hw0vVFfjGeb0,2353
|
||||
pytz/zoneinfo/America/Kentucky/Louisville,sha256=-yqgeeHZdq6oP3_WzVvYOmqV9HQv8y7ZWmc9bzHvJAY,2772
|
||||
pytz/zoneinfo/America/Kentucky/Monticello,sha256=NJMKjG7jjlRzZhndMPw51bYW0D3jviW2Qbl70YcU0Gg,2352
|
||||
pytz/zoneinfo/America/Knox_IN,sha256=BiALShjiOLg1o8mMRWJ1jyTlJkgvwzte7B9WSOvTUNg,2428
|
||||
pytz/zoneinfo/America/Kralendijk,sha256=ZGEIylAZ5iy_rIBsXREtH_ZfWRIkLI9dQjP_EIyn3sY,186
|
||||
pytz/zoneinfo/America/La_Paz,sha256=PAGF2VU_QOw2xT1Cqdp2P8Aj9hXMVWlCByV7cvfIQ_k,232
|
||||
pytz/zoneinfo/America/Lima,sha256=JHDCg95uw6BEu4a4Gfyikm1s8rm8AsYPG8dJxQQNZFs,406
|
||||
pytz/zoneinfo/America/Los_Angeles,sha256=VOy1PikdjiVdJ7lukVGzwl8uDxV_KYqznkTm5BLEiDM,2836
|
||||
pytz/zoneinfo/America/Louisville,sha256=-yqgeeHZdq6oP3_WzVvYOmqV9HQv8y7ZWmc9bzHvJAY,2772
|
||||
pytz/zoneinfo/America/Lower_Princes,sha256=ZGEIylAZ5iy_rIBsXREtH_ZfWRIkLI9dQjP_EIyn3sY,186
|
||||
pytz/zoneinfo/America/Maceio,sha256=pzjNghmeHhvF4aI3cDq2G_5t71BSNGIbRAF5NmJyDmw,744
|
||||
pytz/zoneinfo/America/Managua,sha256=xBzF01AHn2E2fD8Qdy-DHFe36UqoeNpKPfChduBKWdk,430
|
||||
pytz/zoneinfo/America/Manaus,sha256=lp6RlkcXJQ7mSsKqnEgC8svJVrFDJk_16xxvfpNSpK4,604
|
||||
pytz/zoneinfo/America/Marigot,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Martinique,sha256=fMs80kOU2YFvC0f9y2eje97JeAtTYBamXrnlTunNLzQ,232
|
||||
pytz/zoneinfo/America/Matamoros,sha256=RlEMOT_zvCLQ8s7TNvRE2PnC4H9JrxO7MGxmfu5xPPI,1390
|
||||
pytz/zoneinfo/America/Mazatlan,sha256=aIyre-8trAXSHtqxbuu6gDDkWCUjI_SdAKPIjz74M2E,1526
|
||||
pytz/zoneinfo/America/Mendoza,sha256=5DJiYYeQpcLBR_IoIJtk43IswJeGYawx5GykszuJ-Nw,1076
|
||||
pytz/zoneinfo/America/Menominee,sha256=Arv9WLbfhNcpRsUjHDU757BEdwlp08Gt30AixG3gZ04,2274
|
||||
pytz/zoneinfo/America/Merida,sha256=BJQ5mzAT-akb_EA7WqGdNheCorDqLBnDS_4X3YJz0rc,1422
|
||||
pytz/zoneinfo/America/Metlakatla,sha256=twmieGTVY2V-U8nFxqvx7asYv8GVjeWdLtrOI7UApVI,1423
|
||||
pytz/zoneinfo/America/Mexico_City,sha256=DSpTe5TT0KBsxGx79Rs7ah-zJpiGOJKwPjztovRN0b4,1584
|
||||
pytz/zoneinfo/America/Miquelon,sha256=LNbkN87EnZUa41Xizko5VIN55EyQvf5Kk5b5AfNQG8Q,1666
|
||||
pytz/zoneinfo/America/Moncton,sha256=Wmv-bk9aKKcWWzOpc1UFu67HOfwaIk2Wmh3LgqGctys,3154
|
||||
pytz/zoneinfo/America/Monterrey,sha256=HA4yn9jQHk9i0PqiB7fSoFdzXtB1DT1cheGRPXrQNdQ,1390
|
||||
pytz/zoneinfo/America/Montevideo,sha256=4jcgTegK5X8F0yNYzk-3oySZ4U9XQ09UbTJ_mlu8N70,1510
|
||||
pytz/zoneinfo/America/Montreal,sha256=ggOSzbHkmfgu9wTQzP0MUKsrKMbgveuAeThh1eFl1a0,3494
|
||||
pytz/zoneinfo/America/Montserrat,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Nassau,sha256=MEpB_L1x3UnwwqjOwNqDvCfgQYPOnhB2jewLwiOxV4g,2388
|
||||
pytz/zoneinfo/America/New_York,sha256=7AoiEGjr3wV4P7C4Qs35COZqwr2mjNDq7ocpsSPFOM8,3536
|
||||
pytz/zoneinfo/America/Nipigon,sha256=EGPXcOin8mfzFTkYJm4ICpY7fyE24I2pXg4ejafSMyU,2122
|
||||
pytz/zoneinfo/America/Nome,sha256=2izM3-P-PqJ9za6MdhzFfMvPFNq7Gim69tAvEwPeY2s,2367
|
||||
pytz/zoneinfo/America/Noronha,sha256=3R4lLV8jg5SljhC5OVVCk51Y77Efjo6zCe-oppg_FFo,716
|
||||
pytz/zoneinfo/America/North_Dakota/Beulah,sha256=PHlzEk3wsNXYsfMZZSio7ZfdnyxPFpOhK3dS-1AJKGg,2380
|
||||
pytz/zoneinfo/America/North_Dakota/Center,sha256=PaM52_JOVMEpVdw5qiOlhkp3qA0xp0d6Z9neOatmLKo,2380
|
||||
pytz/zoneinfo/America/North_Dakota/New_Salem,sha256=o0xmH1FUh3lVFLtP5Lb9c0PfSyaPTsRvQSQYwnn_yls,2380
|
||||
pytz/zoneinfo/America/Nuuk,sha256=FtlXWP_hBNuwBHkI2b1yne_tSUJpwLtWLyTHZoFZkmM,1878
|
||||
pytz/zoneinfo/America/Ojinaga,sha256=cO3V-x_1Q-mpbJgKNd6-WTfxDEHBV1aqS4wzVl5A0Q4,1484
|
||||
pytz/zoneinfo/America/Panama,sha256=kayA_pdpMcSQ0FjIzotdcf-m1JYfbKE-qcFT8LC8zqA,182
|
||||
pytz/zoneinfo/America/Pangnirtung,sha256=P9Kw_I-NxcUYJIr1j40jTn9q7F8TPAE_FqXsfLYF86A,2094
|
||||
pytz/zoneinfo/America/Paramaribo,sha256=Hm5tDwUmnoTrTUPEO4WArfSF74ZjywVEocy4kL51FzA,262
|
||||
pytz/zoneinfo/America/Phoenix,sha256=nEOwYOnGxENw9zW8m50PGxbtVfTrX3QYAo4x4LgOLfI,328
|
||||
pytz/zoneinfo/America/Port-au-Prince,sha256=09ZAJd4IOiMpfdpUuF1U44R_hRt6BvpAkFXOnYO9yOM,1434
|
||||
pytz/zoneinfo/America/Port_of_Spain,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Porto_Acre,sha256=17onkm8P_VgMkErjK9rr0qwNni7qp9tgcUZ93g3ltOs,628
|
||||
pytz/zoneinfo/America/Porto_Velho,sha256=ZRfzgGEu26hnl3JPtiZLOSFGj_WBSbOKdiLC1xIyc5c,576
|
||||
pytz/zoneinfo/America/Puerto_Rico,sha256=hJHlV_-AGoMGUWuMpZRv9fLmghrzFHfrR9fRkcxaZJc,246
|
||||
pytz/zoneinfo/America/Punta_Arenas,sha256=kpqStczF3X0yK0lwOcxmwbQM8ZV9MrNktm7orJF-EJc,1902
|
||||
pytz/zoneinfo/America/Rainy_River,sha256=r6kx6lD2IzCdygkj-DKyL2tPSn7k0Zil7PSHCBFKOa0,2122
|
||||
pytz/zoneinfo/America/Rankin_Inlet,sha256=KpQX97-EuF4MNyxQrtOKP616CK_vjniM-lo14WGVz0c,1892
|
||||
pytz/zoneinfo/America/Recife,sha256=ijFN2ZzZe5oBYdl8Ag3SwmGjj2JeVYYX2Vo767g2s6I,716
|
||||
pytz/zoneinfo/America/Regina,sha256=yjqT08pHbICYe83H8JmtaDBvCFqRv7Tfze3Y8xuXukw,980
|
||||
pytz/zoneinfo/America/Resolute,sha256=VP_u5XsepfSwx7Ou9zjGw2p5Qi10AIA54sP1J2DkppM,1892
|
||||
pytz/zoneinfo/America/Rio_Branco,sha256=17onkm8P_VgMkErjK9rr0qwNni7qp9tgcUZ93g3ltOs,628
|
||||
pytz/zoneinfo/America/Rosario,sha256=1XqIP8Qo2bPR7909hrAI-qAttybmwEW4ms7FjZA5Yfw,1076
|
||||
pytz/zoneinfo/America/Santa_Isabel,sha256=OHHtvy3J70z6wvKBHgPqMEnGs6SXp8fkf0WX9ZiOODk,2342
|
||||
pytz/zoneinfo/America/Santarem,sha256=Gl_lI3pPZ57UIYXWcmaTpFqWDA5re6bHh1nWs_Z0-Nc,602
|
||||
pytz/zoneinfo/America/Santiago,sha256=GB14PW0xABV283dXc8qL-nnDW-ViFUR3bne7sg0Aido,2529
|
||||
pytz/zoneinfo/America/Santo_Domingo,sha256=DKtaEj8fQ92ybITTWU4Bm160S9pzJmUVbjaWRnenxU4,458
|
||||
pytz/zoneinfo/America/Sao_Paulo,sha256=cO3VGekMGdSf1y4f_UgkpDMRes26-l1oGUoDglIiUQg,1444
|
||||
pytz/zoneinfo/America/Scoresbysund,sha256=dfHb86egoiNykb3bR3OHXpGFPm_Apck8BLiVTCqVAVc,1916
|
||||
pytz/zoneinfo/America/Shiprock,sha256=6_yPo1_mvnt9DgpPzr0QdHsjdsfUG6ALnagQLML1DSM,2444
|
||||
pytz/zoneinfo/America/Sitka,sha256=aiS7Fk37hZpzZ9VkeJQeF-BqTLRC1QOTCgMAJwT8UxA,2329
|
||||
pytz/zoneinfo/America/St_Barthelemy,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/St_Johns,sha256=r1-17uKv27eZ3JsVkw_DLZQbo6wvjuuVu7C2pDsmOgI,3655
|
||||
pytz/zoneinfo/America/St_Kitts,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/St_Lucia,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/St_Thomas,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/St_Vincent,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Swift_Current,sha256=RRKOF7vZC8VvYxD8PP4J1_hUPayKBP7Lu80avRkfPDY,560
|
||||
pytz/zoneinfo/America/Tegucigalpa,sha256=EzOz7ntTlreMq69JZ2CcAb8Ps98V9bUMN480tpPIyw4,252
|
||||
pytz/zoneinfo/America/Thule,sha256=8xuPRaZU8RgO5ECqFYHYmnHioc81sBOailkVu8Y02i8,1502
|
||||
pytz/zoneinfo/America/Thunder_Bay,sha256=cJ9lcf2mDZttEx_ttYYoZAJfuGhSsDgNV2PI-ggWdPE,2202
|
||||
pytz/zoneinfo/America/Tijuana,sha256=OHHtvy3J70z6wvKBHgPqMEnGs6SXp8fkf0WX9ZiOODk,2342
|
||||
pytz/zoneinfo/America/Toronto,sha256=ggOSzbHkmfgu9wTQzP0MUKsrKMbgveuAeThh1eFl1a0,3494
|
||||
pytz/zoneinfo/America/Tortola,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Vancouver,sha256=sknKH0jSPWam-DHfM35qXs8Nam7d5TFlkUI9Sgxryyg,2892
|
||||
pytz/zoneinfo/America/Virgin,sha256=17gT2eOVMFKJF_sypwDPudkFwGEijrRfkBU-aK3FL60,148
|
||||
pytz/zoneinfo/America/Whitehorse,sha256=Kfv607qGHJxXGBP1nPJyNg2_duWrmxhZGFQr82ukgq8,1614
|
||||
pytz/zoneinfo/America/Winnipeg,sha256=7P-_YQrneFcon7QKSTOnkiGjEppFDn3Z48MJ1qq8VBw,2868
|
||||
pytz/zoneinfo/America/Yakutat,sha256=tFwnKbvwhyyn4LNTAn5ye_JWDdxjCerNDt7oOwUwO2M,2305
|
||||
pytz/zoneinfo/America/Yellowknife,sha256=pfFvC8NEy373KbO6r6ec-Gw_O0D2h64mXU1X1AsUDgE,1966
|
||||
pytz/zoneinfo/Antarctica/Casey,sha256=a_ShNA5q27F-GNPiFPttIhhdHc1MP485jX6pwRjZ_t0,384
|
||||
pytz/zoneinfo/Antarctica/Davis,sha256=6PokyOaaISRTN13sisuGgdt5vG5A2YqNooJpfLTb5SQ,297
|
||||
pytz/zoneinfo/Antarctica/DumontDUrville,sha256=g8HQLY-aN3p6az-04KdHOdZYFnN__-8ltHRuY9eQX-I,194
|
||||
pytz/zoneinfo/Antarctica/Macquarie,sha256=ie7RlaU8RHTorVVj-MX8StKMqx_oXf4UH2PUqpzcwe0,2260
|
||||
pytz/zoneinfo/Antarctica/Mawson,sha256=9TW1g_z0tk5EfeB7K69VJo8agO7-K9ZxWbiqNKnUZNE,199
|
||||
pytz/zoneinfo/Antarctica/McMurdo,sha256=gADjoyPo_QISQU6UJrAgcHp3HDaMoOFRdH-d23uBSyc,2437
|
||||
pytz/zoneinfo/Antarctica/Palmer,sha256=DW_DXByXg5MnMZ-w1bNdu8b0lKOYD_EgrPRd5EcyEm4,1418
|
||||
pytz/zoneinfo/Antarctica/Rothera,sha256=QQI1m1IN4_2e6Bb0z-rOYaOwxp4XjMJDOKM9SFDUPKg,164
|
||||
pytz/zoneinfo/Antarctica/South_Pole,sha256=gADjoyPo_QISQU6UJrAgcHp3HDaMoOFRdH-d23uBSyc,2437
|
||||
pytz/zoneinfo/Antarctica/Syowa,sha256=VnmdVypdJUhsBw1XuXZEcEQIFmoiqoYcdpl8ht37QgY,165
|
||||
pytz/zoneinfo/Antarctica/Troll,sha256=3zrh-P_jMCss9GGwHJJHkypZZydq4mkgo_TDqctn3c4,1162
|
||||
pytz/zoneinfo/Antarctica/Vostok,sha256=6tx86WD3MVGJBCbOJUCoA6YlGwCn2BT4B85Zss0vz4Y,165
|
||||
pytz/zoneinfo/Arctic/Longyearbyen,sha256=UdCERhj1JYpx3ojmilaRoyVoR4qMA1-PEv6hGwnpsJA,2228
|
||||
pytz/zoneinfo/Asia/Aden,sha256=rq9KPj8l0FBnnKn93WkMeA1IngNtTzk5_oV4sEZhc4w,165
|
||||
pytz/zoneinfo/Asia/Almaty,sha256=rBIl_pqZNmKZabjEa4mcsLahl9PbAdZJpQMQLVmcfBU,997
|
||||
pytz/zoneinfo/Asia/Amman,sha256=bvwhc1hPCGvQMqWzaoCHrCA_y78n3H-Z2t4wHSocuAw,1853
|
||||
pytz/zoneinfo/Asia/Anadyr,sha256=hDDTly45ejoVVP9Al07TmKpTACNGJaIPlcXLRbsG_4g,1188
|
||||
pytz/zoneinfo/Asia/Aqtau,sha256=A5exZN256JagFJTcasgdCrQ8giOqZ2EFMRVYBWTaqZA,983
|
||||
pytz/zoneinfo/Asia/Aqtobe,sha256=LQ7P5LEEe7jbWbjqvzmM79c0o6AdZeCExQS-fOWp8yw,1011
|
||||
pytz/zoneinfo/Asia/Ashgabat,sha256=L4DYV2mZWycsYeHIypXzO6ZNY3tD8wjgxfPR2ZPW26c,619
|
||||
pytz/zoneinfo/Asia/Ashkhabad,sha256=L4DYV2mZWycsYeHIypXzO6ZNY3tD8wjgxfPR2ZPW26c,619
|
||||
pytz/zoneinfo/Asia/Atyrau,sha256=3uEo89ORyDJqQ_TtaQdIf9UPaB8WqIRQVi0geeY9gVE,991
|
||||
pytz/zoneinfo/Asia/Baghdad,sha256=lQMSUnOuijbcoTaCqMNnYhnvKtS2IVP_kXFAzePVNDU,983
|
||||
pytz/zoneinfo/Asia/Bahrain,sha256=V0rFJdLHIrToJ5Wl28VzVowwCVZoY8ZZSeNp-7kOvjY,199
|
||||
pytz/zoneinfo/Asia/Baku,sha256=vhHnliaOdRyNudl0sFJFdLynEg0Hc0I-IiZNfbDeCbM,1227
|
||||
pytz/zoneinfo/Asia/Bangkok,sha256=eYq0vh89N1j069URoQvtBu0ndEal6FPrtbF8WCKKpDw,199
|
||||
pytz/zoneinfo/Asia/Barnaul,sha256=2c1Cq8XYlBgybRQMP8w0NCf7kaLDrPZtGn4M5iJZbJo,1221
|
||||
pytz/zoneinfo/Asia/Beirut,sha256=_Z_2ZAg_iL9vU51JDB8CB04uXBDrf1kLIis-JnXaS2o,2154
|
||||
pytz/zoneinfo/Asia/Bishkek,sha256=do_4ki1JvSKupUrvlz9jRkHspDhdvk1D2IkByFskjJM,983
|
||||
pytz/zoneinfo/Asia/Brunei,sha256=BMMjwEmZ9rMoNpWfg8IrlLhRbMKbdW48padRF-FGolc,203
|
||||
pytz/zoneinfo/Asia/Calcutta,sha256=6Qw0EDbLcgMgDik8s7UTJn4QSjmllPNeGVJU5rwKF88,285
|
||||
pytz/zoneinfo/Asia/Chita,sha256=4ICOcAVAEWnP-cdf_YJu1_kCYnYPG2_vYfSbuNI-VwI,1221
|
||||
pytz/zoneinfo/Asia/Choibalsan,sha256=sJQAAjiT9VyG73dYhpYkq4tcmfITcPpiAa8YXsDlKag,949
|
||||
pytz/zoneinfo/Asia/Chongqing,sha256=ZP_C5DqUQ1oEPAQNHTr36S0DGtx453N68YYbqk7u8-Y,561
|
||||
pytz/zoneinfo/Asia/Chungking,sha256=ZP_C5DqUQ1oEPAQNHTr36S0DGtx453N68YYbqk7u8-Y,561
|
||||
pytz/zoneinfo/Asia/Colombo,sha256=HGea9jswIIgz7k20LTzbKtQyUun67IP5HvsZrmAJZJY,372
|
||||
pytz/zoneinfo/Asia/Dacca,sha256=3K5llGhcpCdZMMcJuomICVv7lZlDRpU4PUb5DtFx8l4,337
|
||||
pytz/zoneinfo/Asia/Damascus,sha256=6mcB6bxH1KsLqzb_LmJUT3tUDnq9_ScLFKoMFkcZy3A,2294
|
||||
pytz/zoneinfo/Asia/Dhaka,sha256=3K5llGhcpCdZMMcJuomICVv7lZlDRpU4PUb5DtFx8l4,337
|
||||
pytz/zoneinfo/Asia/Dili,sha256=ptjbacc9JK0pv2JpD-gHMglrwYNj9LMMIua0U0ZTMUc,227
|
||||
pytz/zoneinfo/Asia/Dubai,sha256=-ga0m3ua9Y6kSWREz2_VdtcVAkq83WrW3vxjBI7WNGs,165
|
||||
pytz/zoneinfo/Asia/Dushanbe,sha256=FUk9Tt_GimfRulcWamEvuOvA7FQ52YfZqQ2w88qMx6M,591
|
||||
pytz/zoneinfo/Asia/Famagusta,sha256=CFrcygd8ude5x6OEtfM_Dw0KYHoxpPPzq46KoHVxjjc,2028
|
||||
pytz/zoneinfo/Asia/Gaza,sha256=LPrVQ4DY43CC-2CnMLx6VEt4IP26P6Jw0HRlhbEbXHo,2422
|
||||
pytz/zoneinfo/Asia/Harbin,sha256=ZP_C5DqUQ1oEPAQNHTr36S0DGtx453N68YYbqk7u8-Y,561
|
||||
pytz/zoneinfo/Asia/Hebron,sha256=dXAP5ZeuPWJZLh6Y41hEz3owiuqElZxn1SKtdWaeKtw,2450
|
||||
pytz/zoneinfo/Asia/Ho_Chi_Minh,sha256=L5TXNg6-odIIn-JAyLTR8fKFiUFBNFwy0HzwZchbnm4,351
|
||||
pytz/zoneinfo/Asia/Hong_Kong,sha256=UcnFEc9S8hMWl9giVXni4TAhLPWX0H12XvwSt4AJHew,1203
|
||||
pytz/zoneinfo/Asia/Hovd,sha256=JUnOos7PNTi2VRKxD6XnaVR3NpuhsX_Pi18rIzVe1xw,891
|
||||
pytz/zoneinfo/Asia/Irkutsk,sha256=iUJZCVBjpfB4rNKJOr6g0zUZtccYYk_Gk0wTklx8Yj0,1243
|
||||
pytz/zoneinfo/Asia/Istanbul,sha256=2S0A_f7VxvyErJMMCPqK33AChA29IVkMr1o-SpMtMxk,1947
|
||||
pytz/zoneinfo/Asia/Jakarta,sha256=_WRgz6Zb6wxIXtMwpKjG4w4PJtDRzkhdrw-3a4NCBFA,355
|
||||
pytz/zoneinfo/Asia/Jayapura,sha256=ihzUd-L8HUVqG-Na10MyPE-YYwjVFj-xerqjTN4EJZs,221
|
||||
pytz/zoneinfo/Asia/Jerusalem,sha256=JUuWQmW5Tha0pJjw61Q5aN7CX0z4D7ops9OOSnda6Dc,2388
|
||||
pytz/zoneinfo/Asia/Kabul,sha256=ial7SvweHTQXDl79MnXm6QHtiw2i7Zt1e5urLXU8Sq8,208
|
||||
pytz/zoneinfo/Asia/Kamchatka,sha256=pBA0RbynKTKsMCmf2hJMZ_hgVUPemms-VceMMJ7QC64,1166
|
||||
pytz/zoneinfo/Asia/Karachi,sha256=iB-mWMTXUyfBwAkZdz8_UmEw0xsgxIub-KNI7akzhkk,379
|
||||
pytz/zoneinfo/Asia/Kashgar,sha256=AEXDJ5PxQOhePZZw1QZl98moDNa-bW3I3WVNQZHBPYA,165
|
||||
pytz/zoneinfo/Asia/Kathmandu,sha256=TUeW7rDSifOTSsNxvo9igIYZfGITEZUf-0EjglyRDWs,212
|
||||
pytz/zoneinfo/Asia/Katmandu,sha256=TUeW7rDSifOTSsNxvo9igIYZfGITEZUf-0EjglyRDWs,212
|
||||
pytz/zoneinfo/Asia/Khandyga,sha256=XYzE2tsE5Say9pg0cHDQkEE9aTuy2piFSLAGx_d-dmM,1271
|
||||
pytz/zoneinfo/Asia/Kolkata,sha256=6Qw0EDbLcgMgDik8s7UTJn4QSjmllPNeGVJU5rwKF88,285
|
||||
pytz/zoneinfo/Asia/Krasnoyarsk,sha256=nzRw4PI2AiK_Ge854b8U7TSDw0LGQy3ca5YuOOU2XwI,1207
|
||||
pytz/zoneinfo/Asia/Kuala_Lumpur,sha256=RfiIYo6sMEkSA8m5iUmyOyJzKZrgRs8ehGuDZwoq88k,383
|
||||
pytz/zoneinfo/Asia/Kuching,sha256=KsAtQ0aocINozixwW7CkorY-1PTLlsj7UUnQGQMEYTQ,483
|
||||
pytz/zoneinfo/Asia/Kuwait,sha256=rq9KPj8l0FBnnKn93WkMeA1IngNtTzk5_oV4sEZhc4w,165
|
||||
pytz/zoneinfo/Asia/Macao,sha256=MvAkRyRsrA2r052ItlyF5bh2FheRjI0jPwg0uIiH2Yk,1227
|
||||
pytz/zoneinfo/Asia/Macau,sha256=MvAkRyRsrA2r052ItlyF5bh2FheRjI0jPwg0uIiH2Yk,1227
|
||||
pytz/zoneinfo/Asia/Magadan,sha256=cqwjKQt8TlznM1w2DezAZuz1EjeOfLxPeSY19i9zkfQ,1222
|
||||
pytz/zoneinfo/Asia/Makassar,sha256=OhJtCqSTEU-u5n0opBVO5Bu-wQzcYPy9S_6aAhJXgOw,254
|
||||
pytz/zoneinfo/Asia/Manila,sha256=ujfq0kl1EhxcYSOrG-FS750aNaYUt1TT4bFuK4EcL_c,328
|
||||
pytz/zoneinfo/Asia/Muscat,sha256=-ga0m3ua9Y6kSWREz2_VdtcVAkq83WrW3vxjBI7WNGs,165
|
||||
pytz/zoneinfo/Asia/Nicosia,sha256=0Unm0IFT7HyGeQ7F3vTa_-klfysCgrulqFO6BD1plZU,2002
|
||||
pytz/zoneinfo/Asia/Novokuznetsk,sha256=vQGcqKdmYmWDdl73QPZTcyadnope1RPJ4oBgZelQu90,1165
|
||||
pytz/zoneinfo/Asia/Novosibirsk,sha256=ApL3s20HX2eIAno03HCa2RXdlLotVb9JvnZl7W1sM00,1221
|
||||
pytz/zoneinfo/Asia/Omsk,sha256=wxbEesfe7dJOkNPffqTwT6wuTSSTM6E9f0uFMAyzMCM,1207
|
||||
pytz/zoneinfo/Asia/Oral,sha256=iMjqD4LvDgyxN15v7CqyEdBDyBFaOlChwX1wHz2JiVQ,1005
|
||||
pytz/zoneinfo/Asia/Phnom_Penh,sha256=eYq0vh89N1j069URoQvtBu0ndEal6FPrtbF8WCKKpDw,199
|
||||
pytz/zoneinfo/Asia/Pontianak,sha256=inOXwuKtjKv1z_eliPZSIqjSt6whtuxhPeG1YpjU_BQ,353
|
||||
pytz/zoneinfo/Asia/Pyongyang,sha256=_-g3GnDAtfDX4XAktXH9jFouLUDmOovnjoOfvRpUDsE,237
|
||||
pytz/zoneinfo/Asia/Qatar,sha256=V0rFJdLHIrToJ5Wl28VzVowwCVZoY8ZZSeNp-7kOvjY,199
|
||||
pytz/zoneinfo/Asia/Qostanay,sha256=UGYEvmZfAAS9D6EMGd0n6-r_Az_zgTDSWLPeHzFLfu0,1011
|
||||
pytz/zoneinfo/Asia/Qyzylorda,sha256=aiSRxwoUbQ-TBHf2wcyaOhQb86j3jQpXwcQaSPnAtwU,1025
|
||||
pytz/zoneinfo/Asia/Rangoon,sha256=ZHuX-XVHr8dGJjrPQ5cW7b8jQUv3ihyd-VzN545mlMA,268
|
||||
pytz/zoneinfo/Asia/Riyadh,sha256=rq9KPj8l0FBnnKn93WkMeA1IngNtTzk5_oV4sEZhc4w,165
|
||||
pytz/zoneinfo/Asia/Saigon,sha256=L5TXNg6-odIIn-JAyLTR8fKFiUFBNFwy0HzwZchbnm4,351
|
||||
pytz/zoneinfo/Asia/Sakhalin,sha256=95AdPwOgSe0g9wdx67kKLDbjvY3FtpeVBoAWbJVco0w,1202
|
||||
pytz/zoneinfo/Asia/Samarkand,sha256=BBe6Gg_KlSQuS5hAyvvhZWmClcLJaFjnCNGC391HHQM,577
|
||||
pytz/zoneinfo/Asia/Seoul,sha256=LI9LsV3XcJC0l-KoQf8zI-y7rk-du57erS-N2Ptdi7Q,617
|
||||
pytz/zoneinfo/Asia/Shanghai,sha256=ZP_C5DqUQ1oEPAQNHTr36S0DGtx453N68YYbqk7u8-Y,561
|
||||
pytz/zoneinfo/Asia/Singapore,sha256=hIgr_LHMTWh3GgeG-MmLHBp-9anUxQcfMlKFtX8WvmU,383
|
||||
pytz/zoneinfo/Asia/Srednekolymsk,sha256=0DllW8q5VgXEMV5c_nLJElZsNpauvNhNACQpcgdqEl0,1208
|
||||
pytz/zoneinfo/Asia/Taipei,sha256=DMmQwOpPql25ue3Nf8vAKKT4em06D1Z9rHbLIitxixk,761
|
||||
pytz/zoneinfo/Asia/Tashkent,sha256=LS-yTxh0v1vmJoQ9I6fY-IERk7ukPmovVx2Ut_-b-Ig,591
|
||||
pytz/zoneinfo/Asia/Tbilisi,sha256=w6UNxgyn4BVVTF5WkAtxo_u7nnIY26makKQ5nRgifds,1035
|
||||
pytz/zoneinfo/Asia/Tehran,sha256=ATT50Q0hK6uSba5_WnOE3Px0OWxIwxaqK5Oi10P2A-M,2582
|
||||
pytz/zoneinfo/Asia/Tel_Aviv,sha256=JUuWQmW5Tha0pJjw61Q5aN7CX0z4D7ops9OOSnda6Dc,2388
|
||||
pytz/zoneinfo/Asia/Thimbu,sha256=uia8or5dtDkxVUZrcLwkjbTz9C7ZhLq0T4jlE4YvuvQ,203
|
||||
pytz/zoneinfo/Asia/Thimphu,sha256=uia8or5dtDkxVUZrcLwkjbTz9C7ZhLq0T4jlE4YvuvQ,203
|
||||
pytz/zoneinfo/Asia/Tokyo,sha256=oCueZgRNxcNcX3ZGdif9y6Su4cyVhga4XHdwlcrYLOs,309
|
||||
pytz/zoneinfo/Asia/Tomsk,sha256=77YgdJLxETRKjQjnaHHf54xBAqNywTDwQQmZ5v6Aq28,1221
|
||||
pytz/zoneinfo/Asia/Ujung_Pandang,sha256=OhJtCqSTEU-u5n0opBVO5Bu-wQzcYPy9S_6aAhJXgOw,254
|
||||
pytz/zoneinfo/Asia/Ulaanbaatar,sha256=uyQSzIBl0f2TXHrmUm3VPs1C9ro013hYmAlx6yUjh3Y,891
|
||||
pytz/zoneinfo/Asia/Ulan_Bator,sha256=uyQSzIBl0f2TXHrmUm3VPs1C9ro013hYmAlx6yUjh3Y,891
|
||||
pytz/zoneinfo/Asia/Urumqi,sha256=AEXDJ5PxQOhePZZw1QZl98moDNa-bW3I3WVNQZHBPYA,165
|
||||
pytz/zoneinfo/Asia/Ust-Nera,sha256=JAZhRAPdbOL9AL-WHOL8aZjxdZxLmGDNBGMCw9TKtR8,1252
|
||||
pytz/zoneinfo/Asia/Vientiane,sha256=eYq0vh89N1j069URoQvtBu0ndEal6FPrtbF8WCKKpDw,199
|
||||
pytz/zoneinfo/Asia/Vladivostok,sha256=Wokhgtj2nwUj992h7SyfB_fRNHAKfPNzhsf_oZpim8c,1208
|
||||
pytz/zoneinfo/Asia/Yakutsk,sha256=RVCIl52EvMrp2RG2hg2cjDSr9QhsscaAT-NV81xw7zc,1207
|
||||
pytz/zoneinfo/Asia/Yangon,sha256=ZHuX-XVHr8dGJjrPQ5cW7b8jQUv3ihyd-VzN545mlMA,268
|
||||
pytz/zoneinfo/Asia/Yekaterinburg,sha256=NzVc2DiPeyw0FdMHwSPQJF9k3tvWdtrETZiN58pyxLk,1243
|
||||
pytz/zoneinfo/Asia/Yerevan,sha256=k0WHtWQW_cBCjcEv8nP01cVPeTVDlf18lQ0_u6cin1o,1151
|
||||
pytz/zoneinfo/Atlantic/Azores,sha256=ut7TdE-xiQNjRybg56Tt5b7Zo5zqbuF5IFci2aDMs1Q,3484
|
||||
pytz/zoneinfo/Atlantic/Bermuda,sha256=LNGKfMsnYvwImjTyzXrLhMOHHDu7qI67RbYNKvvI15I,2396
|
||||
pytz/zoneinfo/Atlantic/Canary,sha256=ymK9ufqphvNjDK3hzikN4GfkcR3QeCBiPKyVc6FjlbA,1897
|
||||
pytz/zoneinfo/Atlantic/Cape_Verde,sha256=ESQvE3deMI-lx9mG0yJLEsFX5KRl-7c6gD5O2h0Zm9Q,270
|
||||
pytz/zoneinfo/Atlantic/Faeroe,sha256=NibdZPZtapnYR_myIZnMdTaSKGsOBGgujj0_T2NvAzs,1815
|
||||
pytz/zoneinfo/Atlantic/Faroe,sha256=NibdZPZtapnYR_myIZnMdTaSKGsOBGgujj0_T2NvAzs,1815
|
||||
pytz/zoneinfo/Atlantic/Jan_Mayen,sha256=UdCERhj1JYpx3ojmilaRoyVoR4qMA1-PEv6hGwnpsJA,2228
|
||||
pytz/zoneinfo/Atlantic/Madeira,sha256=e1K2l8ykd8xpznQNs3SSuIZ1ZfVx2Y69EXrhvYV3P14,3475
|
||||
pytz/zoneinfo/Atlantic/Reykjavik,sha256=mSkaRBGZLeUrm88EeHcaWnEd35Wn-Ag2G10HtI3G2fg,1162
|
||||
pytz/zoneinfo/Atlantic/South_Georgia,sha256=QZ72fRKp6Kgvy7DfyHGht1MVnzGgSPujLQd4XMjNrrc,164
|
||||
pytz/zoneinfo/Atlantic/St_Helena,sha256=0u-sTl8j2IyV1ywdtCgHFw9S9D3ZiiBa9akqkbny2Zc,148
|
||||
pytz/zoneinfo/Atlantic/Stanley,sha256=exKMLw-P952wS1FTxVjnUU1mkD2OvKUDwtDt8IGgf8w,1214
|
||||
pytz/zoneinfo/Australia/ACT,sha256=QsOFdYWxbbL4_9R7oZ-qYPRzNA3o1P6TIOp76GFgWQY,2190
|
||||
pytz/zoneinfo/Australia/Adelaide,sha256=ld2EbxU75oVgmPe703z-I6aqLg0Kmv62ZcCGzkT5R20,2208
|
||||
pytz/zoneinfo/Australia/Brisbane,sha256=eW6Qzze2t0-speJmmvt1JMzbkSadIKdE84XHc7JUtGc,419
|
||||
pytz/zoneinfo/Australia/Broken_Hill,sha256=3k_3ljTvS5GSfo7Xh6w71UgR3aAwYPBsnCJ-mlEYCqQ,2229
|
||||
pytz/zoneinfo/Australia/Canberra,sha256=QsOFdYWxbbL4_9R7oZ-qYPRzNA3o1P6TIOp76GFgWQY,2190
|
||||
pytz/zoneinfo/Australia/Currie,sha256=GLQSzgIfsWxOvmKOrhpfofWqINQf6h36NYy3mcq6gcg,2358
|
||||
pytz/zoneinfo/Australia/Darwin,sha256=fn0IZhIW98FAnzLig-_GBtW5LA54jajdeeUzg4tCGvo,325
|
||||
pytz/zoneinfo/Australia/Eucla,sha256=LxEuFWyMse_cALVtRWCkf6sIIEk13jQ4JXW8k2agSd8,470
|
||||
pytz/zoneinfo/Australia/Hobart,sha256=GLQSzgIfsWxOvmKOrhpfofWqINQf6h36NYy3mcq6gcg,2358
|
||||
pytz/zoneinfo/Australia/LHI,sha256=Luf0Lx_iJHuh3kZd4LxRjf36tLF5-wW2UFMVNKNT7gg,1860
|
||||
pytz/zoneinfo/Australia/Lindeman,sha256=xM6Udx22oLNoLR1Y7GQhHOYov8nw3xQNqgc_NVQ2JK4,475
|
||||
pytz/zoneinfo/Australia/Lord_Howe,sha256=Luf0Lx_iJHuh3kZd4LxRjf36tLF5-wW2UFMVNKNT7gg,1860
|
||||
pytz/zoneinfo/Australia/Melbourne,sha256=lvx_MQcunMc6u2smIrl8X427bLsXvjkgpCSdjYCTNBM,2190
|
||||
pytz/zoneinfo/Australia/NSW,sha256=QsOFdYWxbbL4_9R7oZ-qYPRzNA3o1P6TIOp76GFgWQY,2190
|
||||
pytz/zoneinfo/Australia/North,sha256=fn0IZhIW98FAnzLig-_GBtW5LA54jajdeeUzg4tCGvo,325
|
||||
pytz/zoneinfo/Australia/Perth,sha256=Al1DOUh4U_ofMUQSeVlzSyD3x7SUjP9dchSaBUGmeWg,446
|
||||
pytz/zoneinfo/Australia/Queensland,sha256=eW6Qzze2t0-speJmmvt1JMzbkSadIKdE84XHc7JUtGc,419
|
||||
pytz/zoneinfo/Australia/South,sha256=ld2EbxU75oVgmPe703z-I6aqLg0Kmv62ZcCGzkT5R20,2208
|
||||
pytz/zoneinfo/Australia/Sydney,sha256=QsOFdYWxbbL4_9R7oZ-qYPRzNA3o1P6TIOp76GFgWQY,2190
|
||||
pytz/zoneinfo/Australia/Tasmania,sha256=GLQSzgIfsWxOvmKOrhpfofWqINQf6h36NYy3mcq6gcg,2358
|
||||
pytz/zoneinfo/Australia/Victoria,sha256=lvx_MQcunMc6u2smIrl8X427bLsXvjkgpCSdjYCTNBM,2190
|
||||
pytz/zoneinfo/Australia/West,sha256=Al1DOUh4U_ofMUQSeVlzSyD3x7SUjP9dchSaBUGmeWg,446
|
||||
pytz/zoneinfo/Australia/Yancowinna,sha256=3k_3ljTvS5GSfo7Xh6w71UgR3aAwYPBsnCJ-mlEYCqQ,2229
|
||||
pytz/zoneinfo/Brazil/Acre,sha256=17onkm8P_VgMkErjK9rr0qwNni7qp9tgcUZ93g3ltOs,628
|
||||
pytz/zoneinfo/Brazil/DeNoronha,sha256=3R4lLV8jg5SljhC5OVVCk51Y77Efjo6zCe-oppg_FFo,716
|
||||
pytz/zoneinfo/Brazil/East,sha256=cO3VGekMGdSf1y4f_UgkpDMRes26-l1oGUoDglIiUQg,1444
|
||||
pytz/zoneinfo/Brazil/West,sha256=lp6RlkcXJQ7mSsKqnEgC8svJVrFDJk_16xxvfpNSpK4,604
|
||||
pytz/zoneinfo/CET,sha256=o4omkrM_IsITxooUo8krM921XfBdvRs9JhwGXGd-Ypg,2094
|
||||
pytz/zoneinfo/CST6CDT,sha256=WGbtZ1FwjRX6Jeo_TCXKsfeDs4V9uhXGJfcnLJhk3s0,2310
|
||||
pytz/zoneinfo/Canada/Atlantic,sha256=TZpmc5PwWoLfTfQoQ_b3U17BE2iVKSeNkR0Ho8mbTn8,3424
|
||||
pytz/zoneinfo/Canada/Central,sha256=7P-_YQrneFcon7QKSTOnkiGjEppFDn3Z48MJ1qq8VBw,2868
|
||||
pytz/zoneinfo/Canada/Eastern,sha256=ggOSzbHkmfgu9wTQzP0MUKsrKMbgveuAeThh1eFl1a0,3494
|
||||
pytz/zoneinfo/Canada/Mountain,sha256=-TkIfc3QlvaCf0p8COZ43Y1HRBAl-nARUi-JdXeK1vE,2332
|
||||
pytz/zoneinfo/Canada/Newfoundland,sha256=r1-17uKv27eZ3JsVkw_DLZQbo6wvjuuVu7C2pDsmOgI,3655
|
||||
pytz/zoneinfo/Canada/Pacific,sha256=sknKH0jSPWam-DHfM35qXs8Nam7d5TFlkUI9Sgxryyg,2892
|
||||
pytz/zoneinfo/Canada/Saskatchewan,sha256=yjqT08pHbICYe83H8JmtaDBvCFqRv7Tfze3Y8xuXukw,980
|
||||
pytz/zoneinfo/Canada/Yukon,sha256=Kfv607qGHJxXGBP1nPJyNg2_duWrmxhZGFQr82ukgq8,1614
|
||||
pytz/zoneinfo/Chile/Continental,sha256=GB14PW0xABV283dXc8qL-nnDW-ViFUR3bne7sg0Aido,2529
|
||||
pytz/zoneinfo/Chile/EasterIsland,sha256=paHp1QRXIa02kgd0-4V6vWXdqcwheow-hJQD9VqacfQ,2233
|
||||
pytz/zoneinfo/Cuba,sha256=HUQeAuKBsEkI5SLZjqynXICOUVOajkKzKH5r-Ov5Odc,2416
|
||||
pytz/zoneinfo/EET,sha256=gGVsW5-qnI7ty8vqVK1ADWhunrvAT8kUC79GUf-_7G8,1908
|
||||
pytz/zoneinfo/EST,sha256=uKE_VPKfxGyYEsyqV_DdE2MW55vs_qUioOdIn5Goobc,114
|
||||
pytz/zoneinfo/EST5EDT,sha256=fwzEMT1jgnY2dDjd0EqDl26_7LC-oF48Bd4ng5311H0,2310
|
||||
pytz/zoneinfo/Egypt,sha256=L6zLQLnQtLkEELOGfm6USaHY33qAEPgGV822-iU1vxc,1955
|
||||
pytz/zoneinfo/Eire,sha256=-JSA3vsi44F1DE8supVjSppH2Vpp12WjJI0_COtAmqU,3492
|
||||
pytz/zoneinfo/Etc/GMT,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Etc/GMT+0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Etc/GMT+1,sha256=1Qzl2X9rQ_RXEf11yH09wQZCr_ph6UdFP7E0yu9s-IQ,116
|
||||
pytz/zoneinfo/Etc/GMT+10,sha256=JEQyQyQlkC0o6ZTdeVjZhCIOh6cK5TF7H00Pkls-sUI,117
|
||||
pytz/zoneinfo/Etc/GMT+11,sha256=tWvcvYMFCaE60nJVvDrrov7stJvs1KQYOyrhl3dzcUs,117
|
||||
pytz/zoneinfo/Etc/GMT+12,sha256=b70HEhErq8IJmq8x7cOZy4eR__3fq5uHHpjvPBEHqMA,117
|
||||
pytz/zoneinfo/Etc/GMT+2,sha256=T6Ep5zhslBKbYaECFUB6gUKh3iTZPyMoW1kjhonxrUo,116
|
||||
pytz/zoneinfo/Etc/GMT+3,sha256=QGoYrE04bUJ-OzL37dt2MZT5FxWNLpJDPVXgJbstYZA,116
|
||||
pytz/zoneinfo/Etc/GMT+4,sha256=RWrkNki-wV7X-coe0VvufBe6LrWVpkPJgia5QQYEnBo,116
|
||||
pytz/zoneinfo/Etc/GMT+5,sha256=oRmeC41dgYXT-zzyZIRKXN9IvdL2Da5nTuwmG2_prIA,116
|
||||
pytz/zoneinfo/Etc/GMT+6,sha256=d6dAnwiejyFI2n7AzFlFW0aFAT6zYNEjBIEG0uu0sbQ,116
|
||||
pytz/zoneinfo/Etc/GMT+7,sha256=TqjYbzd0YHpx1wisFg08J19wTpg6ztJLLongZY_lozs,116
|
||||
pytz/zoneinfo/Etc/GMT+8,sha256=th_8bIMmYgRPCesBrbmBhRr0jQO7whd70LiY9HfwJyk,116
|
||||
pytz/zoneinfo/Etc/GMT+9,sha256=Qq5E6iUS7JMJIymT7YoqlI8MtqtVy0mr9t6zWFtWc9Y,116
|
||||
pytz/zoneinfo/Etc/GMT-0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Etc/GMT-1,sha256=73F1eU8uAQGP3mcoB2q99CjfManGFHk3fefljp9pYC4,117
|
||||
pytz/zoneinfo/Etc/GMT-10,sha256=fKWWNwLBOp1OkKjtc1w9LIXJR1mTTD-JdvYflRy1IrU,118
|
||||
pytz/zoneinfo/Etc/GMT-11,sha256=D2S79n6psa9t9_2vj5wIrFpHH2OJLcCKP6vtwzFZINY,118
|
||||
pytz/zoneinfo/Etc/GMT-12,sha256=me4V6lmWI8gSr8H7N41WAD0Eww1anh_EF34Qr9UoSnI,118
|
||||
pytz/zoneinfo/Etc/GMT-13,sha256=xbmbG1BQA6Dlpa_iUwEGyJxW4a3t6lmawdPKAE8vbR8,118
|
||||
pytz/zoneinfo/Etc/GMT-14,sha256=PpXoREBh02qFpvxVMj2pV9IAzSQvBE7XPvnN9qSZ-Kc,118
|
||||
pytz/zoneinfo/Etc/GMT-2,sha256=ve6hWLdeuiLhqagaWLqMD6HNybS1chRwjudfTZ2bYBE,117
|
||||
pytz/zoneinfo/Etc/GMT-3,sha256=N77jILanuLDVkLsdujXZSu-dsHiwN5MIpwh7fMUifso,117
|
||||
pytz/zoneinfo/Etc/GMT-4,sha256=LSko5fVHqPl5zfwjGqkbMa_OFnvtpT6o_4xYxNz9n5o,117
|
||||
pytz/zoneinfo/Etc/GMT-5,sha256=uLaSR5Mb18HRTsAA5SveY9PAJ97dO8QzIWqNXe3wZb4,117
|
||||
pytz/zoneinfo/Etc/GMT-6,sha256=JSN-RUAphJ50fpIv7cYC6unrtrz9S1Wma-piDHlGe7c,117
|
||||
pytz/zoneinfo/Etc/GMT-7,sha256=vVAOF8xU9T9ESnw68c0SFXpcvkoopaiwTR0zbefHHSU,117
|
||||
pytz/zoneinfo/Etc/GMT-8,sha256=S7xFQbFMpiDZy4v5L4D9fCrjRIzzoLC5p8Se23xi7us,117
|
||||
pytz/zoneinfo/Etc/GMT-9,sha256=I5vHNmUK-Yyg_S1skFN44VGVzBgktjFgVQiDIKO4aMI,117
|
||||
pytz/zoneinfo/Etc/GMT0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Etc/Greenwich,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Etc/UCT,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/Etc/UTC,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/Etc/Universal,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/Etc/Zulu,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/Europe/Amsterdam,sha256=pw8HngVt3bU5QrRzu70qOmf69TIyklkglvVUte9ntKo,2910
|
||||
pytz/zoneinfo/Europe/Andorra,sha256=gTB5jCQmvIw3JJi1_vAcOYuhtzPBR6RXUx9gVV6p6ug,1742
|
||||
pytz/zoneinfo/Europe/Astrakhan,sha256=ywtzL92KVfoybOmAhE9eHqmMcvJZm5b0js5GDdWIJEQ,1165
|
||||
pytz/zoneinfo/Europe/Athens,sha256=XDY-FBUddRyQHN8GxQLZ4awjuOlWlzlUdjv7OdXFNzA,2262
|
||||
pytz/zoneinfo/Europe/Belfast,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/Europe/Belgrade,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/Berlin,sha256=XuR19xoPwaMvrrhJ-MOcbnqmbW1B7HQrl7OnQ2s7BwE,2298
|
||||
pytz/zoneinfo/Europe/Bratislava,sha256=G9fdhUXmzx651BnyZ6V7AOYIV9EV5aMJMm44eJaLLZw,2301
|
||||
pytz/zoneinfo/Europe/Brussels,sha256=gS9Vrrbozend9HhuFetCVrIegs9fXSjaG60X2UVwysA,2933
|
||||
pytz/zoneinfo/Europe/Bucharest,sha256=nfg6-bU2D6DMEWb9EMIBR5kxnNsbDSx0UKfHH_ZzqFc,2184
|
||||
pytz/zoneinfo/Europe/Budapest,sha256=lNwqxWciBvw9ei81VQwIKHbC_ZDJjpgHU6HFg4wCUkY,2368
|
||||
pytz/zoneinfo/Europe/Busingen,sha256=K5QY7Ujj2VUchKR4bhhb0hgdAJhmwED71ykXDQOGKe8,1909
|
||||
pytz/zoneinfo/Europe/Chisinau,sha256=p1J_rqFE13pL8cpBRrEFe-teCI8f0fKK4uTUy_4diF4,2390
|
||||
pytz/zoneinfo/Europe/Copenhagen,sha256=q7iAbkd7y9QvbAi6XGZEUOTwNDCRYWRu9VQCxUrZ01U,2137
|
||||
pytz/zoneinfo/Europe/Dublin,sha256=-JSA3vsi44F1DE8supVjSppH2Vpp12WjJI0_COtAmqU,3492
|
||||
pytz/zoneinfo/Europe/Gibraltar,sha256=egOcazf2u1njGZ0tDj-f1NzZT_K5rpUKSqtShxO7U6c,3052
|
||||
pytz/zoneinfo/Europe/Guernsey,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/Europe/Helsinki,sha256=GEkB7LsVhmegt7YuuWheCDvDGC7b7Nw9bTdDGS9qkJc,1900
|
||||
pytz/zoneinfo/Europe/Isle_of_Man,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/Europe/Istanbul,sha256=2S0A_f7VxvyErJMMCPqK33AChA29IVkMr1o-SpMtMxk,1947
|
||||
pytz/zoneinfo/Europe/Jersey,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/Europe/Kaliningrad,sha256=s7GXSe1YvMcs7AiUhHNTA6I4nAOQn_Kmz_ZqJYO-LMM,1493
|
||||
pytz/zoneinfo/Europe/Kiev,sha256=iVkTPFkl2tADYapa1HASlaV3tT2VsJpTPTTJC_9HtAk,2088
|
||||
pytz/zoneinfo/Europe/Kirov,sha256=Sr4HEUwk3tPTXioeCLhvlgKbCAFU7Gy2UB3f--uWLDc,1153
|
||||
pytz/zoneinfo/Europe/Lisbon,sha256=L6n3snx6pNHHJIL6JOLFOAlYkQ2J5uB_y5MG_Ic_PDU,3469
|
||||
pytz/zoneinfo/Europe/Ljubljana,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/London,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/Europe/Luxembourg,sha256=974Dvf_X1QISKG1zIiTJJIfGavobO21HUVS-HfysOcY,2946
|
||||
pytz/zoneinfo/Europe/Madrid,sha256=MTTMnrbDDtexRikd72-FbQEpCZjc63_UtBIiDomD95c,2614
|
||||
pytz/zoneinfo/Europe/Malta,sha256=xRwBfrV8hOihGtqcek5_B6l5hjc206g3yfbEWXIaUis,2620
|
||||
pytz/zoneinfo/Europe/Mariehamn,sha256=GEkB7LsVhmegt7YuuWheCDvDGC7b7Nw9bTdDGS9qkJc,1900
|
||||
pytz/zoneinfo/Europe/Minsk,sha256=mn86zdrNWpJYDfE51Iy9n1-Zi2piTyb9EPaS2A-uGJQ,1321
|
||||
pytz/zoneinfo/Europe/Monaco,sha256=50uVZXYXXqfnr-K4tsSNl26CZbRju65C-STp818wX84,2944
|
||||
pytz/zoneinfo/Europe/Moscow,sha256=KmkofRcj6T8Ph28PJChm8JVp13uRvef6TZ0GuPzUiDw,1535
|
||||
pytz/zoneinfo/Europe/Nicosia,sha256=0Unm0IFT7HyGeQ7F3vTa_-klfysCgrulqFO6BD1plZU,2002
|
||||
pytz/zoneinfo/Europe/Oslo,sha256=UdCERhj1JYpx3ojmilaRoyVoR4qMA1-PEv6hGwnpsJA,2228
|
||||
pytz/zoneinfo/Europe/Paris,sha256=q3ehSIot1GZ6TyMHIjbg0oRf4ghAXuwbSDSYVim6evg,2962
|
||||
pytz/zoneinfo/Europe/Podgorica,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/Prague,sha256=G9fdhUXmzx651BnyZ6V7AOYIV9EV5aMJMm44eJaLLZw,2301
|
||||
pytz/zoneinfo/Europe/Riga,sha256=hJ2_0m1taW9IuA-hMyP5n-WX7YOrR0heKszJhgljRWk,2198
|
||||
pytz/zoneinfo/Europe/Rome,sha256=-X5F_d3Dz0kBRWiUTXUN-fgeCHbUEHLaaHIwEPZEdUQ,2641
|
||||
pytz/zoneinfo/Europe/Samara,sha256=z2innqSZ8_lkEy8cIyF9JM_FfnO2sWZaqeFqOh8pD7M,1215
|
||||
pytz/zoneinfo/Europe/San_Marino,sha256=-X5F_d3Dz0kBRWiUTXUN-fgeCHbUEHLaaHIwEPZEdUQ,2641
|
||||
pytz/zoneinfo/Europe/Sarajevo,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/Saratov,sha256=BMej49HlQG24CWCh5VOENrB3jPuJPScPszRtb7MrJ3I,1183
|
||||
pytz/zoneinfo/Europe/Simferopol,sha256=_M6LXB5Rqh932nKIJotGjT8YNszAOb7RjHN5ng-uW1Y,1453
|
||||
pytz/zoneinfo/Europe/Skopje,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/Sofia,sha256=hCQKXfMNrnA5xHNw_uzTjKzVw4-Bvsq5oGO4yUCv5tY,2077
|
||||
pytz/zoneinfo/Europe/Stockholm,sha256=Xgp4GSh8-pzdeJeP8TQ20jWDDUj17R69h6RYTbLYd2g,1909
|
||||
pytz/zoneinfo/Europe/Tallinn,sha256=4a6JC0aIpMzqIV7O35zoG0LLJwkQq5AoXZ2ivkic6-w,2148
|
||||
pytz/zoneinfo/Europe/Tirane,sha256=ztlZyCS9WCXeVW8nBun3Tyi5HUY0EtFbiBbEc1gucuw,2084
|
||||
pytz/zoneinfo/Europe/Tiraspol,sha256=p1J_rqFE13pL8cpBRrEFe-teCI8f0fKK4uTUy_4diF4,2390
|
||||
pytz/zoneinfo/Europe/Ulyanovsk,sha256=nFsgcVTmTiiFzHtyJDRnO-3H4GRAfAeceb6b2jFHLUQ,1267
|
||||
pytz/zoneinfo/Europe/Uzhgorod,sha256=TIG1rC4QR7nz-vO1VtmN9mDMVjKPDKi7mEB9KpfJOBA,2050
|
||||
pytz/zoneinfo/Europe/Vaduz,sha256=K5QY7Ujj2VUchKR4bhhb0hgdAJhmwED71ykXDQOGKe8,1909
|
||||
pytz/zoneinfo/Europe/Vatican,sha256=-X5F_d3Dz0kBRWiUTXUN-fgeCHbUEHLaaHIwEPZEdUQ,2641
|
||||
pytz/zoneinfo/Europe/Vienna,sha256=ZmI3kADE6bnrJEccqh73XXBY36L1G4DkpiTQImtNrUk,2200
|
||||
pytz/zoneinfo/Europe/Vilnius,sha256=UFzRX3orCTB8d9IzlxJPy5eUA2oBPuCu1UJl-2D7C3U,2162
|
||||
pytz/zoneinfo/Europe/Volgograd,sha256=XZNEUXwnmGdOTld_9Lug2CFfXbFCJFZC45nOMb59FRk,1165
|
||||
pytz/zoneinfo/Europe/Warsaw,sha256=TiLDPbeVF0ckgLVEkaSeDaKZ8wctdJDOl_HE_Wd5rKs,2654
|
||||
pytz/zoneinfo/Europe/Zagreb,sha256=OpWtsGFWBE_S-mYoQcAmjCta9HwbGQANnSmVY9OHCTo,1920
|
||||
pytz/zoneinfo/Europe/Zaporozhye,sha256=V0dhGl3gET8OftMezf8CVy-W00Z7FtuEev5TjI2Rnyw,2106
|
||||
pytz/zoneinfo/Europe/Zurich,sha256=K5QY7Ujj2VUchKR4bhhb0hgdAJhmwED71ykXDQOGKe8,1909
|
||||
pytz/zoneinfo/Factory,sha256=aFFlKx93HXoJoF4SSuTlD8cZtJA-ne5oKzAa6eX2V4k,116
|
||||
pytz/zoneinfo/GB,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/GB-Eire,sha256=xp08wV44TZMmAdBqppttDChQAb8tRN03GcEht99RYtY,3648
|
||||
pytz/zoneinfo/GMT,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/GMT+0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/GMT-0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/GMT0,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/Greenwich,sha256=bZ83iIPAefhsA4elVHqSxEmGnYBuB94QCEqwTwJJAY0,114
|
||||
pytz/zoneinfo/HST,sha256=1YkCncvgL9Z5CmUo4Vk8VbQmgA7ZAQ0PtE37j1yOli8,115
|
||||
pytz/zoneinfo/Hongkong,sha256=UcnFEc9S8hMWl9giVXni4TAhLPWX0H12XvwSt4AJHew,1203
|
||||
pytz/zoneinfo/Iceland,sha256=mSkaRBGZLeUrm88EeHcaWnEd35Wn-Ag2G10HtI3G2fg,1162
|
||||
pytz/zoneinfo/Indian/Antananarivo,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Indian/Chagos,sha256=23B26pwwK0gxW7TP76GltyY-RU_o6RGGSrF93pF7S1E,199
|
||||
pytz/zoneinfo/Indian/Christmas,sha256=J4I0WDX_LYAJxsx2vU0EdxFJQKRE-rRL1UvNQv09pCs,165
|
||||
pytz/zoneinfo/Indian/Cocos,sha256=PX-k8JpghajjvhljtBjWozaiu9NhUSpVeoACy2cAxN8,174
|
||||
pytz/zoneinfo/Indian/Comoro,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Indian/Kerguelen,sha256=oIvd6bmQFMLUefoBn4c1fQTOAawGcrPcmge2jU7BsYo,165
|
||||
pytz/zoneinfo/Indian/Mahe,sha256=ZNXjaoL_o6572xXgsgSmbd5D_SkaCaayolpSN1je82w,165
|
||||
pytz/zoneinfo/Indian/Maldives,sha256=dUQBbrmoB3odWsMt3K1YUnB447A6nkW3aR1aHzdLF7M,199
|
||||
pytz/zoneinfo/Indian/Mauritius,sha256=k6vWUVcfU3gS1K12e_aMw6BeSdMvdLyCJRCAL7CD0go,241
|
||||
pytz/zoneinfo/Indian/Mayotte,sha256=yJsuJTqJJqbOz37_NOS_zbf-JNr_IthHGMMN7sDqSWg,265
|
||||
pytz/zoneinfo/Indian/Reunion,sha256=lHnSVh7CYCuDBEM4dYsWDk006BSAznkCPxjiTtL_WiI,165
|
||||
pytz/zoneinfo/Iran,sha256=ATT50Q0hK6uSba5_WnOE3Px0OWxIwxaqK5Oi10P2A-M,2582
|
||||
pytz/zoneinfo/Israel,sha256=JUuWQmW5Tha0pJjw61Q5aN7CX0z4D7ops9OOSnda6Dc,2388
|
||||
pytz/zoneinfo/Jamaica,sha256=wlagieUPRf5-beie-h7QsONbNzjGsm8vMs8uf28pw28,482
|
||||
pytz/zoneinfo/Japan,sha256=oCueZgRNxcNcX3ZGdif9y6Su4cyVhga4XHdwlcrYLOs,309
|
||||
pytz/zoneinfo/Kwajalein,sha256=L4nH3qxv5EBKVRxYt67b9IfZfBzg5KJk19iu7x3oBMk,316
|
||||
pytz/zoneinfo/Libya,sha256=W1dptGD70T7ppGoo0fczFQeDiIp0nultLNPV66MwB2c,625
|
||||
pytz/zoneinfo/MET,sha256=i3CKSuP4N_PAj7o-Cbk8zPEdFs0CWWBCAfg2JXDx5V8,2094
|
||||
pytz/zoneinfo/MST,sha256=6IQwvtT12Bz1pTiqFuoVxNY-4ViS7ZrYHo5nPWwzKPw,114
|
||||
pytz/zoneinfo/MST7MDT,sha256=910Ek32FKoSyZWY_H19VHaVvqb-JsvnWTOOHvhrKsE0,2310
|
||||
pytz/zoneinfo/Mexico/BajaNorte,sha256=OHHtvy3J70z6wvKBHgPqMEnGs6SXp8fkf0WX9ZiOODk,2342
|
||||
pytz/zoneinfo/Mexico/BajaSur,sha256=aIyre-8trAXSHtqxbuu6gDDkWCUjI_SdAKPIjz74M2E,1526
|
||||
pytz/zoneinfo/Mexico/General,sha256=DSpTe5TT0KBsxGx79Rs7ah-zJpiGOJKwPjztovRN0b4,1584
|
||||
pytz/zoneinfo/NZ,sha256=gADjoyPo_QISQU6UJrAgcHp3HDaMoOFRdH-d23uBSyc,2437
|
||||
pytz/zoneinfo/NZ-CHAT,sha256=lkVqaSF1WWpv_B2K-k2uJp2setRVK6XbjsQ38gDGVEg,2068
|
||||
pytz/zoneinfo/Navajo,sha256=6_yPo1_mvnt9DgpPzr0QdHsjdsfUG6ALnagQLML1DSM,2444
|
||||
pytz/zoneinfo/PRC,sha256=ZP_C5DqUQ1oEPAQNHTr36S0DGtx453N68YYbqk7u8-Y,561
|
||||
pytz/zoneinfo/PST8PDT,sha256=Q7TCLkE69a6g7mPoPAkqhg-0dStyiAC0jVlM72KG_R8,2310
|
||||
pytz/zoneinfo/Pacific/Apia,sha256=p1vFsjfezDCHmPOnmgG47q7wTPM5feosoWN3ucgGnrw,1097
|
||||
pytz/zoneinfo/Pacific/Auckland,sha256=gADjoyPo_QISQU6UJrAgcHp3HDaMoOFRdH-d23uBSyc,2437
|
||||
pytz/zoneinfo/Pacific/Bougainville,sha256=ZKDa_S_2gSlmOWizV1DqxH3wbE58rfK1vKZHZqrrtjI,268
|
||||
pytz/zoneinfo/Pacific/Chatham,sha256=lkVqaSF1WWpv_B2K-k2uJp2setRVK6XbjsQ38gDGVEg,2068
|
||||
pytz/zoneinfo/Pacific/Chuuk,sha256=6IYDKViuRDC_RVx1AJOxazVET6cZtdv_LFE6xbtGItI,269
|
||||
pytz/zoneinfo/Pacific/Easter,sha256=paHp1QRXIa02kgd0-4V6vWXdqcwheow-hJQD9VqacfQ,2233
|
||||
pytz/zoneinfo/Pacific/Efate,sha256=pG4NMVeM3hBJTZnZmqeLqz3Q5oCggTW4HO-R9Fe926A,538
|
||||
pytz/zoneinfo/Pacific/Enderbury,sha256=zqW7qAC_6FTcgrGEMhpIsl1oV9I46gY2nH3pwadll68,234
|
||||
pytz/zoneinfo/Pacific/Fakaofo,sha256=gow-SgE5r5c8J_Ag5nvJ5SUPDg6yH8pth_a-QLDcPv8,200
|
||||
pytz/zoneinfo/Pacific/Fiji,sha256=W6rxVK44zQaoLWLexVRoav16jMcuWYbNskIa5Ld9H-Q,1077
|
||||
pytz/zoneinfo/Pacific/Funafuti,sha256=P-XYwlWQpWvS3Q_TYFe37BrgxKJy5tg7PHEQNCDGv5U,166
|
||||
pytz/zoneinfo/Pacific/Galapagos,sha256=MdtlC-ffp8reICzDxsQ8tWMsTkq5ZcN-j3OyyhjokV8,238
|
||||
pytz/zoneinfo/Pacific/Gambier,sha256=z6eYF8sszLjkfpqmWnbBBAUB-ibaR5nodKaAYbvXOe0,164
|
||||
pytz/zoneinfo/Pacific/Guadalcanal,sha256=6GX-XpxcCyA64qUMdxJMFMq4sPk0ZjhexqGbryzfgjE,166
|
||||
pytz/zoneinfo/Pacific/Guam,sha256=Ex9znmf6rNfGze6gNpZJCMr1TT4rkl2SnrhecrdJufI,494
|
||||
pytz/zoneinfo/Pacific/Honolulu,sha256=fwPRv1Jk56sCOi75uZfd_Iy2k2aSQHx3B2K5xUlSPzM,329
|
||||
pytz/zoneinfo/Pacific/Johnston,sha256=fwPRv1Jk56sCOi75uZfd_Iy2k2aSQHx3B2K5xUlSPzM,329
|
||||
pytz/zoneinfo/Pacific/Kiritimati,sha256=VHR3iuwiv3tx65WtitVHCoQEg3VJd812VZ5djuSyUxc,238
|
||||
pytz/zoneinfo/Pacific/Kosrae,sha256=Vm5AKI6NvuYSz58s8922WNIiWoqPcix2JOJOix1mlSU,351
|
||||
pytz/zoneinfo/Pacific/Kwajalein,sha256=L4nH3qxv5EBKVRxYt67b9IfZfBzg5KJk19iu7x3oBMk,316
|
||||
pytz/zoneinfo/Pacific/Majuro,sha256=Dwqh7gXoz7Duwu1n7XF8yEjhM4ULEs42LSQyy7F-qzQ,310
|
||||
pytz/zoneinfo/Pacific/Marquesas,sha256=uzsjVolutGRXp_FRnvXoU0ApDEb4ZaYoz_r60D7jufg,173
|
||||
pytz/zoneinfo/Pacific/Midway,sha256=fCYrYphYY6rUfxOw712y5cyRe104AC3pouqD3bCINFg,175
|
||||
pytz/zoneinfo/Pacific/Nauru,sha256=oGxocYsqssZ_EeQHf3cUP5cg0qtqzx1BzoEjVWjE_7g,252
|
||||
pytz/zoneinfo/Pacific/Niue,sha256=lSsVlJJ458vNuIgjZESQyatsJV3LpWGyHqbYXMXPjZ4,241
|
||||
pytz/zoneinfo/Pacific/Norfolk,sha256=CdEXM9SKYC9Wn7aMxD2sV5i8zE88NQo25Z_L874JthI,880
|
||||
pytz/zoneinfo/Pacific/Noumea,sha256=FSanpAOCE7WHQeiop4QErKV9ZC3Tzu2GxkH8-tIXsHY,304
|
||||
pytz/zoneinfo/Pacific/Pago_Pago,sha256=fCYrYphYY6rUfxOw712y5cyRe104AC3pouqD3bCINFg,175
|
||||
pytz/zoneinfo/Pacific/Palau,sha256=CRW__McXPlOaxo2S9kHMHaBdjv7u59ZWEwYuJConzmQ,180
|
||||
pytz/zoneinfo/Pacific/Pitcairn,sha256=O65Ed1FOCF_0rEjpYPAquDwtAF3hxyJNiujgpgZV0kc,202
|
||||
pytz/zoneinfo/Pacific/Pohnpei,sha256=YqXrKwjhUnxWyV6PFg1L6_zu84MfPW82dypf0S7pHtQ,303
|
||||
pytz/zoneinfo/Pacific/Ponape,sha256=YqXrKwjhUnxWyV6PFg1L6_zu84MfPW82dypf0S7pHtQ,303
|
||||
pytz/zoneinfo/Pacific/Port_Moresby,sha256=ei_XjmiRDLh-RU94uvz9CCIIRFH1r0X7WL-sB-6DF60,186
|
||||
pytz/zoneinfo/Pacific/Rarotonga,sha256=UfUhlaG0u7yOlzoKnHE9pRiHqQ2N_M9n5WHaCCwtbV4,577
|
||||
pytz/zoneinfo/Pacific/Saipan,sha256=Ex9znmf6rNfGze6gNpZJCMr1TT4rkl2SnrhecrdJufI,494
|
||||
pytz/zoneinfo/Pacific/Samoa,sha256=fCYrYphYY6rUfxOw712y5cyRe104AC3pouqD3bCINFg,175
|
||||
pytz/zoneinfo/Pacific/Tahiti,sha256=9iozXRFYDhBOLijmDk2mRS4Mb-LXWW1u7n790jBNKxM,165
|
||||
pytz/zoneinfo/Pacific/Tarawa,sha256=vT6UxW7KeGptdh80Fj9ASATGmLx8Wai630lML4mwg80,166
|
||||
pytz/zoneinfo/Pacific/Tongatapu,sha256=ht8ZhdveQXJqsxYtSEcqmRTzXA3OtqYoi4WVBvOPGhw,372
|
||||
pytz/zoneinfo/Pacific/Truk,sha256=6IYDKViuRDC_RVx1AJOxazVET6cZtdv_LFE6xbtGItI,269
|
||||
pytz/zoneinfo/Pacific/Wake,sha256=dTJxldgcad-kGrODwo4cAHGRSsS-K3fjeZ62WEUhmFk,166
|
||||
pytz/zoneinfo/Pacific/Wallis,sha256=CAlw1H5gkER5lkvtmHY-ppoGL3hNmYxfMaXQpI0fTOE,166
|
||||
pytz/zoneinfo/Pacific/Yap,sha256=6IYDKViuRDC_RVx1AJOxazVET6cZtdv_LFE6xbtGItI,269
|
||||
pytz/zoneinfo/Poland,sha256=TiLDPbeVF0ckgLVEkaSeDaKZ8wctdJDOl_HE_Wd5rKs,2654
|
||||
pytz/zoneinfo/Portugal,sha256=L6n3snx6pNHHJIL6JOLFOAlYkQ2J5uB_y5MG_Ic_PDU,3469
|
||||
pytz/zoneinfo/ROC,sha256=DMmQwOpPql25ue3Nf8vAKKT4em06D1Z9rHbLIitxixk,761
|
||||
pytz/zoneinfo/ROK,sha256=LI9LsV3XcJC0l-KoQf8zI-y7rk-du57erS-N2Ptdi7Q,617
|
||||
pytz/zoneinfo/Singapore,sha256=hIgr_LHMTWh3GgeG-MmLHBp-9anUxQcfMlKFtX8WvmU,383
|
||||
pytz/zoneinfo/Turkey,sha256=2S0A_f7VxvyErJMMCPqK33AChA29IVkMr1o-SpMtMxk,1947
|
||||
pytz/zoneinfo/UCT,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/US/Alaska,sha256=oZA1NSPS2BWdymYpnCHFO8BlYVS-ll5KLg2Ez9CbETs,2371
|
||||
pytz/zoneinfo/US/Aleutian,sha256=IB1DhwJQAKbhPJ9jHLf8zW5Dad7HIkBS-dhv64E1OlM,2356
|
||||
pytz/zoneinfo/US/Arizona,sha256=nEOwYOnGxENw9zW8m50PGxbtVfTrX3QYAo4x4LgOLfI,328
|
||||
pytz/zoneinfo/US/Central,sha256=4aZFw-svkMyXmSpNufqzK-xveos-oVJDpEyI8Yu9HQE,3576
|
||||
pytz/zoneinfo/US/East-Indiana,sha256=GrNub1_3Um5Qh67wOx58_TEAz4fwAeAlk2AlMTVA_sI,1666
|
||||
pytz/zoneinfo/US/Eastern,sha256=7AoiEGjr3wV4P7C4Qs35COZqwr2mjNDq7ocpsSPFOM8,3536
|
||||
pytz/zoneinfo/US/Hawaii,sha256=fwPRv1Jk56sCOi75uZfd_Iy2k2aSQHx3B2K5xUlSPzM,329
|
||||
pytz/zoneinfo/US/Indiana-Starke,sha256=BiALShjiOLg1o8mMRWJ1jyTlJkgvwzte7B9WSOvTUNg,2428
|
||||
pytz/zoneinfo/US/Michigan,sha256=hecz8yqY2Cj5B61G3gLZdAVZvRgK9l0P90c_gN-uD5g,2230
|
||||
pytz/zoneinfo/US/Mountain,sha256=6_yPo1_mvnt9DgpPzr0QdHsjdsfUG6ALnagQLML1DSM,2444
|
||||
pytz/zoneinfo/US/Pacific,sha256=VOy1PikdjiVdJ7lukVGzwl8uDxV_KYqznkTm5BLEiDM,2836
|
||||
pytz/zoneinfo/US/Samoa,sha256=fCYrYphYY6rUfxOw712y5cyRe104AC3pouqD3bCINFg,175
|
||||
pytz/zoneinfo/UTC,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/Universal,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/W-SU,sha256=KmkofRcj6T8Ph28PJChm8JVp13uRvef6TZ0GuPzUiDw,1535
|
||||
pytz/zoneinfo/WET,sha256=Sc0l03EfVs_aIi17I4KyZJFkwiAHat5BgpjuuFDhgQ0,1905
|
||||
pytz/zoneinfo/Zulu,sha256=i4WEZ5GrLIpUY8g6W-PAQ-JXDXRIQ01BOYlp7Ufj5vI,114
|
||||
pytz/zoneinfo/iso3166.tab,sha256=BMh_yY7MXp8DMEy71jarFX3IJSNpwuEyIjIo2HKUXD4,4463
|
||||
pytz/zoneinfo/leapseconds,sha256=RsEIB_LQGp6HLUj0RLadn4rtq-VEBdYoSaVdNXFISA4,3388
|
||||
pytz/zoneinfo/tzdata.zi,sha256=zIWuTXiIRd8lZwwZvO37rBZRzo_1O0_7BMgB8ZOeqhM,113146
|
||||
pytz/zoneinfo/zone.tab,sha256=2wzsaOCfYuhhMxZsFO9y1ckAPgD4uzBfbff64XV1qWU,19321
|
||||
pytz/zoneinfo/zone1970.tab,sha256=EBkiCpmeio73EQXCl7T9lGFBT19NJwWPw8tqkCc0QRI,17835
|
6
venv/Lib/site-packages/pytz-2020.5.dist-info/WHEEL
Normal file
6
venv/Lib/site-packages/pytz-2020.5.dist-info/WHEEL
Normal 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
|
||||
|
@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 6 - Mature", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules"], "download_url": "https://pypi.org/project/pytz/", "extensions": {"python.details": {"contacts": [{"email": "stuart@stuartbishop.net", "name": "Stuart Bishop", "role": "author"}, {"email": "stuart@stuartbishop.net", "name": "Stuart Bishop", "role": "maintainer"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "http://pythonhosted.org/pytz"}}}, "generator": "bdist_wheel (0.30.0)", "keywords": ["timezone", "tzinfo", "datetime", "olson", "time"], "license": "MIT", "metadata_version": "2.0", "name": "pytz", "platform": "Independent", "summary": "World timezone definitions, modern and historical", "version": "2020.5"}
|
@ -0,0 +1 @@
|
||||
pytz
|
1558
venv/Lib/site-packages/pytz/__init__.py
Normal file
1558
venv/Lib/site-packages/pytz/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
59
venv/Lib/site-packages/pytz/exceptions.py
Normal file
59
venv/Lib/site-packages/pytz/exceptions.py
Normal file
@ -0,0 +1,59 @@
|
||||
'''
|
||||
Custom exceptions raised by pytz.
|
||||
'''
|
||||
|
||||
__all__ = [
|
||||
'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError',
|
||||
'NonExistentTimeError',
|
||||
]
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
'''Base class for all exceptions raised by the pytz library'''
|
||||
|
||||
|
||||
class UnknownTimeZoneError(KeyError, Error):
|
||||
'''Exception raised when pytz is passed an unknown timezone.
|
||||
|
||||
>>> isinstance(UnknownTimeZoneError(), LookupError)
|
||||
True
|
||||
|
||||
This class is actually a subclass of KeyError to provide backwards
|
||||
compatibility with code relying on the undocumented behavior of earlier
|
||||
pytz releases.
|
||||
|
||||
>>> isinstance(UnknownTimeZoneError(), KeyError)
|
||||
True
|
||||
|
||||
And also a subclass of pytz.exceptions.Error, as are other pytz
|
||||
exceptions.
|
||||
|
||||
>>> isinstance(UnknownTimeZoneError(), Error)
|
||||
True
|
||||
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class InvalidTimeError(Error):
|
||||
'''Base class for invalid time exceptions.'''
|
||||
|
||||
|
||||
class AmbiguousTimeError(InvalidTimeError):
|
||||
'''Exception raised when attempting to create an ambiguous wallclock time.
|
||||
|
||||
At the end of a DST transition period, a particular wallclock time will
|
||||
occur twice (once before the clocks are set back, once after). Both
|
||||
possibilities may be correct, unless further information is supplied.
|
||||
|
||||
See DstTzInfo.normalize() for more info
|
||||
'''
|
||||
|
||||
|
||||
class NonExistentTimeError(InvalidTimeError):
|
||||
'''Exception raised when attempting to create a wallclock time that
|
||||
cannot exist.
|
||||
|
||||
At the start of a DST transition period, the wallclock time jumps forward.
|
||||
The instants jumped over never occur.
|
||||
'''
|
172
venv/Lib/site-packages/pytz/lazy.py
Normal file
172
venv/Lib/site-packages/pytz/lazy.py
Normal file
@ -0,0 +1,172 @@
|
||||
from threading import RLock
|
||||
try:
|
||||
from collections.abc import Mapping as DictMixin
|
||||
except ImportError: # Python < 3.3
|
||||
try:
|
||||
from UserDict import DictMixin # Python 2
|
||||
except ImportError: # Python 3.0-3.3
|
||||
from collections import Mapping as DictMixin
|
||||
|
||||
|
||||
# With lazy loading, we might end up with multiple threads triggering
|
||||
# it at the same time. We need a lock.
|
||||
_fill_lock = RLock()
|
||||
|
||||
|
||||
class LazyDict(DictMixin):
|
||||
"""Dictionary populated on first use."""
|
||||
data = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
if self.data is None:
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if self.data is None:
|
||||
self._fill()
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return self.data[key.upper()]
|
||||
|
||||
def __contains__(self, key):
|
||||
if self.data is None:
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if self.data is None:
|
||||
self._fill()
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return key in self.data
|
||||
|
||||
def __iter__(self):
|
||||
if self.data is None:
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if self.data is None:
|
||||
self._fill()
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return iter(self.data)
|
||||
|
||||
def __len__(self):
|
||||
if self.data is None:
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if self.data is None:
|
||||
self._fill()
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return len(self.data)
|
||||
|
||||
def keys(self):
|
||||
if self.data is None:
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if self.data is None:
|
||||
self._fill()
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return self.data.keys()
|
||||
|
||||
|
||||
class LazyList(list):
|
||||
"""List populated on first use."""
|
||||
|
||||
_props = [
|
||||
'__str__', '__repr__', '__unicode__',
|
||||
'__hash__', '__sizeof__', '__cmp__',
|
||||
'__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',
|
||||
'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove',
|
||||
'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__',
|
||||
'__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__',
|
||||
'__getitem__', '__setitem__', '__delitem__', '__iter__',
|
||||
'__reversed__', '__getslice__', '__setslice__', '__delslice__']
|
||||
|
||||
def __new__(cls, fill_iter=None):
|
||||
|
||||
if fill_iter is None:
|
||||
return list()
|
||||
|
||||
# We need a new class as we will be dynamically messing with its
|
||||
# methods.
|
||||
class LazyList(list):
|
||||
pass
|
||||
|
||||
fill_iter = [fill_iter]
|
||||
|
||||
def lazy(name):
|
||||
def _lazy(self, *args, **kw):
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if len(fill_iter) > 0:
|
||||
list.extend(self, fill_iter.pop())
|
||||
for method_name in cls._props:
|
||||
delattr(LazyList, method_name)
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return getattr(list, name)(self, *args, **kw)
|
||||
return _lazy
|
||||
|
||||
for name in cls._props:
|
||||
setattr(LazyList, name, lazy(name))
|
||||
|
||||
new_list = LazyList()
|
||||
return new_list
|
||||
|
||||
# Not all versions of Python declare the same magic methods.
|
||||
# Filter out properties that don't exist in this version of Python
|
||||
# from the list.
|
||||
LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)]
|
||||
|
||||
|
||||
class LazySet(set):
|
||||
"""Set populated on first use."""
|
||||
|
||||
_props = (
|
||||
'__str__', '__repr__', '__unicode__',
|
||||
'__hash__', '__sizeof__', '__cmp__',
|
||||
'__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',
|
||||
'__contains__', '__len__', '__nonzero__',
|
||||
'__getitem__', '__setitem__', '__delitem__', '__iter__',
|
||||
'__sub__', '__and__', '__xor__', '__or__',
|
||||
'__rsub__', '__rand__', '__rxor__', '__ror__',
|
||||
'__isub__', '__iand__', '__ixor__', '__ior__',
|
||||
'add', 'clear', 'copy', 'difference', 'difference_update',
|
||||
'discard', 'intersection', 'intersection_update', 'isdisjoint',
|
||||
'issubset', 'issuperset', 'pop', 'remove',
|
||||
'symmetric_difference', 'symmetric_difference_update',
|
||||
'union', 'update')
|
||||
|
||||
def __new__(cls, fill_iter=None):
|
||||
|
||||
if fill_iter is None:
|
||||
return set()
|
||||
|
||||
class LazySet(set):
|
||||
pass
|
||||
|
||||
fill_iter = [fill_iter]
|
||||
|
||||
def lazy(name):
|
||||
def _lazy(self, *args, **kw):
|
||||
_fill_lock.acquire()
|
||||
try:
|
||||
if len(fill_iter) > 0:
|
||||
for i in fill_iter.pop():
|
||||
set.add(self, i)
|
||||
for method_name in cls._props:
|
||||
delattr(LazySet, method_name)
|
||||
finally:
|
||||
_fill_lock.release()
|
||||
return getattr(set, name)(self, *args, **kw)
|
||||
return _lazy
|
||||
|
||||
for name in cls._props:
|
||||
setattr(LazySet, name, lazy(name))
|
||||
|
||||
new_set = LazySet()
|
||||
return new_set
|
||||
|
||||
# Not all versions of Python declare the same magic methods.
|
||||
# Filter out properties that don't exist in this version of Python
|
||||
# from the list.
|
||||
LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)]
|
140
venv/Lib/site-packages/pytz/reference.py
Normal file
140
venv/Lib/site-packages/pytz/reference.py
Normal file
@ -0,0 +1,140 @@
|
||||
'''
|
||||
Reference tzinfo implementations from the Python docs.
|
||||
Used for testing against as they are only correct for the years
|
||||
1987 to 2006. Do not use these for real code.
|
||||
'''
|
||||
|
||||
from datetime import tzinfo, timedelta, datetime
|
||||
from pytz import HOUR, ZERO, UTC
|
||||
|
||||
__all__ = [
|
||||
'FixedOffset',
|
||||
'LocalTimezone',
|
||||
'USTimeZone',
|
||||
'Eastern',
|
||||
'Central',
|
||||
'Mountain',
|
||||
'Pacific',
|
||||
'UTC'
|
||||
]
|
||||
|
||||
|
||||
# A class building tzinfo objects for fixed-offset time zones.
|
||||
# Note that FixedOffset(0, "UTC") is a different way to build a
|
||||
# UTC tzinfo object.
|
||||
class FixedOffset(tzinfo):
|
||||
"""Fixed offset in minutes east from UTC."""
|
||||
|
||||
def __init__(self, offset, name):
|
||||
self.__offset = timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
import time as _time
|
||||
|
||||
STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||
if _time.daylight:
|
||||
DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||
else:
|
||||
DSTOFFSET = STDOFFSET
|
||||
|
||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||
|
||||
|
||||
# A class capturing the platform's idea of local time.
|
||||
class LocalTimezone(tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTOFFSET
|
||||
else:
|
||||
return STDOFFSET
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTDIFF
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return _time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, -1)
|
||||
stamp = _time.mktime(tt)
|
||||
tt = _time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
Local = LocalTimezone()
|
||||
|
||||
|
||||
def first_sunday_on_or_after(dt):
|
||||
days_to_go = 6 - dt.weekday()
|
||||
if days_to_go:
|
||||
dt += timedelta(days_to_go)
|
||||
return dt
|
||||
|
||||
|
||||
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
|
||||
DSTSTART = datetime(1, 4, 1, 2)
|
||||
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
|
||||
# which is the first Sunday on or after Oct 25.
|
||||
DSTEND = datetime(1, 10, 25, 1)
|
||||
|
||||
|
||||
# A complete implementation of current DST rules for major US time zones.
|
||||
class USTimeZone(tzinfo):
|
||||
|
||||
def __init__(self, hours, reprname, stdname, dstname):
|
||||
self.stdoffset = timedelta(hours=hours)
|
||||
self.reprname = reprname
|
||||
self.stdname = stdname
|
||||
self.dstname = dstname
|
||||
|
||||
def __repr__(self):
|
||||
return self.reprname
|
||||
|
||||
def tzname(self, dt):
|
||||
if self.dst(dt):
|
||||
return self.dstname
|
||||
else:
|
||||
return self.stdname
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.stdoffset + self.dst(dt)
|
||||
|
||||
def dst(self, dt):
|
||||
if dt is None or dt.tzinfo is None:
|
||||
# An exception may be sensible here, in one or both cases.
|
||||
# It depends on how you want to treat them. The default
|
||||
# fromutc() implementation (called by the default astimezone()
|
||||
# implementation) passes a datetime with dt.tzinfo is self.
|
||||
return ZERO
|
||||
assert dt.tzinfo is self
|
||||
|
||||
# Find first Sunday in April & the last in October.
|
||||
start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
|
||||
end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
|
||||
|
||||
# Can't compare naive to aware objects, so strip the timezone from
|
||||
# dt first.
|
||||
if start <= dt.replace(tzinfo=None) < end:
|
||||
return HOUR
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
|
||||
Central = USTimeZone(-6, "Central", "CST", "CDT")
|
||||
Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
|
||||
Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
|
133
venv/Lib/site-packages/pytz/tzfile.py
Normal file
133
venv/Lib/site-packages/pytz/tzfile.py
Normal file
@ -0,0 +1,133 @@
|
||||
'''
|
||||
$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
from struct import unpack, calcsize
|
||||
|
||||
from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo
|
||||
from pytz.tzinfo import memorized_datetime, memorized_timedelta
|
||||
|
||||
|
||||
def _byte_string(s):
|
||||
"""Cast a string or byte string to an ASCII byte string."""
|
||||
return s.encode('ASCII')
|
||||
|
||||
_NULL = _byte_string('\0')
|
||||
|
||||
|
||||
def _std_string(s):
|
||||
"""Cast a string or byte string to an ASCII string."""
|
||||
return str(s.decode('ASCII'))
|
||||
|
||||
|
||||
def build_tzinfo(zone, fp):
|
||||
head_fmt = '>4s c 15x 6l'
|
||||
head_size = calcsize(head_fmt)
|
||||
(magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt,
|
||||
typecnt, charcnt) = unpack(head_fmt, fp.read(head_size))
|
||||
|
||||
# Make sure it is a tzfile(5) file
|
||||
assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic)
|
||||
|
||||
# Read out the transition times, localtime indices and ttinfo structures.
|
||||
data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict(
|
||||
timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt)
|
||||
data_size = calcsize(data_fmt)
|
||||
data = unpack(data_fmt, fp.read(data_size))
|
||||
|
||||
# make sure we unpacked the right number of values
|
||||
assert len(data) == 2 * timecnt + 3 * typecnt + 1
|
||||
transitions = [memorized_datetime(trans)
|
||||
for trans in data[:timecnt]]
|
||||
lindexes = list(data[timecnt:2 * timecnt])
|
||||
ttinfo_raw = data[2 * timecnt:-1]
|
||||
tznames_raw = data[-1]
|
||||
del data
|
||||
|
||||
# Process ttinfo into separate structs
|
||||
ttinfo = []
|
||||
tznames = {}
|
||||
i = 0
|
||||
while i < len(ttinfo_raw):
|
||||
# have we looked up this timezone name yet?
|
||||
tzname_offset = ttinfo_raw[i + 2]
|
||||
if tzname_offset not in tznames:
|
||||
nul = tznames_raw.find(_NULL, tzname_offset)
|
||||
if nul < 0:
|
||||
nul = len(tznames_raw)
|
||||
tznames[tzname_offset] = _std_string(
|
||||
tznames_raw[tzname_offset:nul])
|
||||
ttinfo.append((ttinfo_raw[i],
|
||||
bool(ttinfo_raw[i + 1]),
|
||||
tznames[tzname_offset]))
|
||||
i += 3
|
||||
|
||||
# Now build the timezone object
|
||||
if len(ttinfo) == 1 or len(transitions) == 0:
|
||||
ttinfo[0][0], ttinfo[0][2]
|
||||
cls = type(zone, (StaticTzInfo,), dict(
|
||||
zone=zone,
|
||||
_utcoffset=memorized_timedelta(ttinfo[0][0]),
|
||||
_tzname=ttinfo[0][2]))
|
||||
else:
|
||||
# Early dates use the first standard time ttinfo
|
||||
i = 0
|
||||
while ttinfo[i][1]:
|
||||
i += 1
|
||||
if ttinfo[i] == ttinfo[lindexes[0]]:
|
||||
transitions[0] = datetime.min
|
||||
else:
|
||||
transitions.insert(0, datetime.min)
|
||||
lindexes.insert(0, i)
|
||||
|
||||
# calculate transition info
|
||||
transition_info = []
|
||||
for i in range(len(transitions)):
|
||||
inf = ttinfo[lindexes[i]]
|
||||
utcoffset = inf[0]
|
||||
if not inf[1]:
|
||||
dst = 0
|
||||
else:
|
||||
for j in range(i - 1, -1, -1):
|
||||
prev_inf = ttinfo[lindexes[j]]
|
||||
if not prev_inf[1]:
|
||||
break
|
||||
dst = inf[0] - prev_inf[0] # dst offset
|
||||
|
||||
# Bad dst? Look further. DST > 24 hours happens when
|
||||
# a timzone has moved across the international dateline.
|
||||
if dst <= 0 or dst > 3600 * 3:
|
||||
for j in range(i + 1, len(transitions)):
|
||||
stdinf = ttinfo[lindexes[j]]
|
||||
if not stdinf[1]:
|
||||
dst = inf[0] - stdinf[0]
|
||||
if dst > 0:
|
||||
break # Found a useful std time.
|
||||
|
||||
tzname = inf[2]
|
||||
|
||||
# Round utcoffset and dst to the nearest minute or the
|
||||
# datetime library will complain. Conversions to these timezones
|
||||
# might be up to plus or minus 30 seconds out, but it is
|
||||
# the best we can do.
|
||||
utcoffset = int((utcoffset + 30) // 60) * 60
|
||||
dst = int((dst + 30) // 60) * 60
|
||||
transition_info.append(memorized_ttinfo(utcoffset, dst, tzname))
|
||||
|
||||
cls = type(zone, (DstTzInfo,), dict(
|
||||
zone=zone,
|
||||
_utc_transition_times=transitions,
|
||||
_transition_info=transition_info))
|
||||
|
||||
return cls()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os.path
|
||||
from pprint import pprint
|
||||
base = os.path.join(os.path.dirname(__file__), 'zoneinfo')
|
||||
tz = build_tzinfo('Australia/Melbourne',
|
||||
open(os.path.join(base, 'Australia', 'Melbourne'), 'rb'))
|
||||
tz = build_tzinfo('US/Eastern',
|
||||
open(os.path.join(base, 'US', 'Eastern'), 'rb'))
|
||||
pprint(tz._utc_transition_times)
|
577
venv/Lib/site-packages/pytz/tzinfo.py
Normal file
577
venv/Lib/site-packages/pytz/tzinfo.py
Normal file
@ -0,0 +1,577 @@
|
||||
'''Base classes and helpers for building zone specific tzinfo classes'''
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from bisect import bisect_right
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
import pytz
|
||||
from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError
|
||||
|
||||
__all__ = []
|
||||
|
||||
_timedelta_cache = {}
|
||||
|
||||
|
||||
def memorized_timedelta(seconds):
|
||||
'''Create only one instance of each distinct timedelta'''
|
||||
try:
|
||||
return _timedelta_cache[seconds]
|
||||
except KeyError:
|
||||
delta = timedelta(seconds=seconds)
|
||||
_timedelta_cache[seconds] = delta
|
||||
return delta
|
||||
|
||||
_epoch = datetime.utcfromtimestamp(0)
|
||||
_datetime_cache = {0: _epoch}
|
||||
|
||||
|
||||
def memorized_datetime(seconds):
|
||||
'''Create only one instance of each distinct datetime'''
|
||||
try:
|
||||
return _datetime_cache[seconds]
|
||||
except KeyError:
|
||||
# NB. We can't just do datetime.utcfromtimestamp(seconds) as this
|
||||
# fails with negative values under Windows (Bug #90096)
|
||||
dt = _epoch + timedelta(seconds=seconds)
|
||||
_datetime_cache[seconds] = dt
|
||||
return dt
|
||||
|
||||
_ttinfo_cache = {}
|
||||
|
||||
|
||||
def memorized_ttinfo(*args):
|
||||
'''Create only one instance of each distinct tuple'''
|
||||
try:
|
||||
return _ttinfo_cache[args]
|
||||
except KeyError:
|
||||
ttinfo = (
|
||||
memorized_timedelta(args[0]),
|
||||
memorized_timedelta(args[1]),
|
||||
args[2]
|
||||
)
|
||||
_ttinfo_cache[args] = ttinfo
|
||||
return ttinfo
|
||||
|
||||
_notime = memorized_timedelta(0)
|
||||
|
||||
|
||||
def _to_seconds(td):
|
||||
'''Convert a timedelta to seconds'''
|
||||
return td.seconds + td.days * 24 * 60 * 60
|
||||
|
||||
|
||||
class BaseTzInfo(tzinfo):
|
||||
# Overridden in subclass
|
||||
_utcoffset = None
|
||||
_tzname = None
|
||||
zone = None
|
||||
|
||||
def __str__(self):
|
||||
return self.zone
|
||||
|
||||
|
||||
class StaticTzInfo(BaseTzInfo):
|
||||
'''A timezone that has a constant offset from UTC
|
||||
|
||||
These timezones are rare, as most locations have changed their
|
||||
offset at some point in their history
|
||||
'''
|
||||
def fromutc(self, dt):
|
||||
'''See datetime.tzinfo.fromutc'''
|
||||
if dt.tzinfo is not None and dt.tzinfo is not self:
|
||||
raise ValueError('fromutc: dt.tzinfo is not self')
|
||||
return (dt + self._utcoffset).replace(tzinfo=self)
|
||||
|
||||
def utcoffset(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.utcoffset
|
||||
|
||||
is_dst is ignored for StaticTzInfo, and exists only to
|
||||
retain compatibility with DstTzInfo.
|
||||
'''
|
||||
return self._utcoffset
|
||||
|
||||
def dst(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.dst
|
||||
|
||||
is_dst is ignored for StaticTzInfo, and exists only to
|
||||
retain compatibility with DstTzInfo.
|
||||
'''
|
||||
return _notime
|
||||
|
||||
def tzname(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.tzname
|
||||
|
||||
is_dst is ignored for StaticTzInfo, and exists only to
|
||||
retain compatibility with DstTzInfo.
|
||||
'''
|
||||
return self._tzname
|
||||
|
||||
def localize(self, dt, is_dst=False):
|
||||
'''Convert naive time to local time'''
|
||||
if dt.tzinfo is not None:
|
||||
raise ValueError('Not naive datetime (tzinfo is already set)')
|
||||
return dt.replace(tzinfo=self)
|
||||
|
||||
def normalize(self, dt, is_dst=False):
|
||||
'''Correct the timezone information on the given datetime.
|
||||
|
||||
This is normally a no-op, as StaticTzInfo timezones never have
|
||||
ambiguous cases to correct:
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> gmt = timezone('GMT')
|
||||
>>> isinstance(gmt, StaticTzInfo)
|
||||
True
|
||||
>>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)
|
||||
>>> gmt.normalize(dt) is dt
|
||||
True
|
||||
|
||||
The supported method of converting between timezones is to use
|
||||
datetime.astimezone(). Currently normalize() also works:
|
||||
|
||||
>>> la = timezone('America/Los_Angeles')
|
||||
>>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
||||
>>> gmt.normalize(dt).strftime(fmt)
|
||||
'2011-05-07 08:02:03 GMT (+0000)'
|
||||
'''
|
||||
if dt.tzinfo is self:
|
||||
return dt
|
||||
if dt.tzinfo is None:
|
||||
raise ValueError('Naive time - no tzinfo set')
|
||||
return dt.astimezone(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<StaticTzInfo %r>' % (self.zone,)
|
||||
|
||||
def __reduce__(self):
|
||||
# Special pickle to zone remains a singleton and to cope with
|
||||
# database changes.
|
||||
return pytz._p, (self.zone,)
|
||||
|
||||
|
||||
class DstTzInfo(BaseTzInfo):
|
||||
'''A timezone that has a variable offset from UTC
|
||||
|
||||
The offset might change if daylight saving time comes into effect,
|
||||
or at a point in history when the region decides to change their
|
||||
timezone definition.
|
||||
'''
|
||||
# Overridden in subclass
|
||||
|
||||
# Sorted list of DST transition times, UTC
|
||||
_utc_transition_times = None
|
||||
|
||||
# [(utcoffset, dstoffset, tzname)] corresponding to
|
||||
# _utc_transition_times entries
|
||||
_transition_info = None
|
||||
|
||||
zone = None
|
||||
|
||||
# Set in __init__
|
||||
|
||||
_tzinfos = None
|
||||
_dst = None # DST offset
|
||||
|
||||
def __init__(self, _inf=None, _tzinfos=None):
|
||||
if _inf:
|
||||
self._tzinfos = _tzinfos
|
||||
self._utcoffset, self._dst, self._tzname = _inf
|
||||
else:
|
||||
_tzinfos = {}
|
||||
self._tzinfos = _tzinfos
|
||||
self._utcoffset, self._dst, self._tzname = (
|
||||
self._transition_info[0])
|
||||
_tzinfos[self._transition_info[0]] = self
|
||||
for inf in self._transition_info[1:]:
|
||||
if inf not in _tzinfos:
|
||||
_tzinfos[inf] = self.__class__(inf, _tzinfos)
|
||||
|
||||
def fromutc(self, dt):
|
||||
'''See datetime.tzinfo.fromutc'''
|
||||
if (dt.tzinfo is not None and
|
||||
getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):
|
||||
raise ValueError('fromutc: dt.tzinfo is not self')
|
||||
dt = dt.replace(tzinfo=None)
|
||||
idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)
|
||||
inf = self._transition_info[idx]
|
||||
return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])
|
||||
|
||||
def normalize(self, dt):
|
||||
'''Correct the timezone information on the given datetime
|
||||
|
||||
If date arithmetic crosses DST boundaries, the tzinfo
|
||||
is not magically adjusted. This method normalizes the
|
||||
tzinfo to the correct one.
|
||||
|
||||
To test, first we need to do some setup
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> utc = timezone('UTC')
|
||||
>>> eastern = timezone('US/Eastern')
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
||||
|
||||
We next create a datetime right on an end-of-DST transition point,
|
||||
the instant when the wallclocks are wound back one hour.
|
||||
|
||||
>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
|
||||
>>> loc_dt = utc_dt.astimezone(eastern)
|
||||
>>> loc_dt.strftime(fmt)
|
||||
'2002-10-27 01:00:00 EST (-0500)'
|
||||
|
||||
Now, if we subtract a few minutes from it, note that the timezone
|
||||
information has not changed.
|
||||
|
||||
>>> before = loc_dt - timedelta(minutes=10)
|
||||
>>> before.strftime(fmt)
|
||||
'2002-10-27 00:50:00 EST (-0500)'
|
||||
|
||||
But we can fix that by calling the normalize method
|
||||
|
||||
>>> before = eastern.normalize(before)
|
||||
>>> before.strftime(fmt)
|
||||
'2002-10-27 01:50:00 EDT (-0400)'
|
||||
|
||||
The supported method of converting between timezones is to use
|
||||
datetime.astimezone(). Currently, normalize() also works:
|
||||
|
||||
>>> th = timezone('Asia/Bangkok')
|
||||
>>> am = timezone('Europe/Amsterdam')
|
||||
>>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
||||
>>> am.normalize(dt).strftime(fmt)
|
||||
'2011-05-06 20:02:03 CEST (+0200)'
|
||||
'''
|
||||
if dt.tzinfo is None:
|
||||
raise ValueError('Naive time - no tzinfo set')
|
||||
|
||||
# Convert dt in localtime to UTC
|
||||
offset = dt.tzinfo._utcoffset
|
||||
dt = dt.replace(tzinfo=None)
|
||||
dt = dt - offset
|
||||
# convert it back, and return it
|
||||
return self.fromutc(dt)
|
||||
|
||||
def localize(self, dt, is_dst=False):
|
||||
'''Convert naive time to local time.
|
||||
|
||||
This method should be used to construct localtimes, rather
|
||||
than passing a tzinfo argument to a datetime constructor.
|
||||
|
||||
is_dst is used to determine the correct timezone in the ambigous
|
||||
period at the end of daylight saving time.
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
||||
>>> amdam = timezone('Europe/Amsterdam')
|
||||
>>> dt = datetime(2004, 10, 31, 2, 0, 0)
|
||||
>>> loc_dt1 = amdam.localize(dt, is_dst=True)
|
||||
>>> loc_dt2 = amdam.localize(dt, is_dst=False)
|
||||
>>> loc_dt1.strftime(fmt)
|
||||
'2004-10-31 02:00:00 CEST (+0200)'
|
||||
>>> loc_dt2.strftime(fmt)
|
||||
'2004-10-31 02:00:00 CET (+0100)'
|
||||
>>> str(loc_dt2 - loc_dt1)
|
||||
'1:00:00'
|
||||
|
||||
Use is_dst=None to raise an AmbiguousTimeError for ambiguous
|
||||
times at the end of daylight saving time
|
||||
|
||||
>>> try:
|
||||
... loc_dt1 = amdam.localize(dt, is_dst=None)
|
||||
... except AmbiguousTimeError:
|
||||
... print('Ambiguous')
|
||||
Ambiguous
|
||||
|
||||
is_dst defaults to False
|
||||
|
||||
>>> amdam.localize(dt) == amdam.localize(dt, False)
|
||||
True
|
||||
|
||||
is_dst is also used to determine the correct timezone in the
|
||||
wallclock times jumped over at the start of daylight saving time.
|
||||
|
||||
>>> pacific = timezone('US/Pacific')
|
||||
>>> dt = datetime(2008, 3, 9, 2, 0, 0)
|
||||
>>> ploc_dt1 = pacific.localize(dt, is_dst=True)
|
||||
>>> ploc_dt2 = pacific.localize(dt, is_dst=False)
|
||||
>>> ploc_dt1.strftime(fmt)
|
||||
'2008-03-09 02:00:00 PDT (-0700)'
|
||||
>>> ploc_dt2.strftime(fmt)
|
||||
'2008-03-09 02:00:00 PST (-0800)'
|
||||
>>> str(ploc_dt2 - ploc_dt1)
|
||||
'1:00:00'
|
||||
|
||||
Use is_dst=None to raise a NonExistentTimeError for these skipped
|
||||
times.
|
||||
|
||||
>>> try:
|
||||
... loc_dt1 = pacific.localize(dt, is_dst=None)
|
||||
... except NonExistentTimeError:
|
||||
... print('Non-existent')
|
||||
Non-existent
|
||||
'''
|
||||
if dt.tzinfo is not None:
|
||||
raise ValueError('Not naive datetime (tzinfo is already set)')
|
||||
|
||||
# Find the two best possibilities.
|
||||
possible_loc_dt = set()
|
||||
for delta in [timedelta(days=-1), timedelta(days=1)]:
|
||||
loc_dt = dt + delta
|
||||
idx = max(0, bisect_right(
|
||||
self._utc_transition_times, loc_dt) - 1)
|
||||
inf = self._transition_info[idx]
|
||||
tzinfo = self._tzinfos[inf]
|
||||
loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))
|
||||
if loc_dt.replace(tzinfo=None) == dt:
|
||||
possible_loc_dt.add(loc_dt)
|
||||
|
||||
if len(possible_loc_dt) == 1:
|
||||
return possible_loc_dt.pop()
|
||||
|
||||
# If there are no possibly correct timezones, we are attempting
|
||||
# to convert a time that never happened - the time period jumped
|
||||
# during the start-of-DST transition period.
|
||||
if len(possible_loc_dt) == 0:
|
||||
# If we refuse to guess, raise an exception.
|
||||
if is_dst is None:
|
||||
raise NonExistentTimeError(dt)
|
||||
|
||||
# If we are forcing the pre-DST side of the DST transition, we
|
||||
# obtain the correct timezone by winding the clock forward a few
|
||||
# hours.
|
||||
elif is_dst:
|
||||
return self.localize(
|
||||
dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)
|
||||
|
||||
# If we are forcing the post-DST side of the DST transition, we
|
||||
# obtain the correct timezone by winding the clock back.
|
||||
else:
|
||||
return self.localize(
|
||||
dt - timedelta(hours=6),
|
||||
is_dst=False) + timedelta(hours=6)
|
||||
|
||||
# If we get this far, we have multiple possible timezones - this
|
||||
# is an ambiguous case occuring during the end-of-DST transition.
|
||||
|
||||
# If told to be strict, raise an exception since we have an
|
||||
# ambiguous case
|
||||
if is_dst is None:
|
||||
raise AmbiguousTimeError(dt)
|
||||
|
||||
# Filter out the possiblilities that don't match the requested
|
||||
# is_dst
|
||||
filtered_possible_loc_dt = [
|
||||
p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst
|
||||
]
|
||||
|
||||
# Hopefully we only have one possibility left. Return it.
|
||||
if len(filtered_possible_loc_dt) == 1:
|
||||
return filtered_possible_loc_dt[0]
|
||||
|
||||
if len(filtered_possible_loc_dt) == 0:
|
||||
filtered_possible_loc_dt = list(possible_loc_dt)
|
||||
|
||||
# If we get this far, we have in a wierd timezone transition
|
||||
# where the clocks have been wound back but is_dst is the same
|
||||
# in both (eg. Europe/Warsaw 1915 when they switched to CET).
|
||||
# At this point, we just have to guess unless we allow more
|
||||
# hints to be passed in (such as the UTC offset or abbreviation),
|
||||
# but that is just getting silly.
|
||||
#
|
||||
# Choose the earliest (by UTC) applicable timezone if is_dst=True
|
||||
# Choose the latest (by UTC) applicable timezone if is_dst=False
|
||||
# i.e., behave like end-of-DST transition
|
||||
dates = {} # utc -> local
|
||||
for local_dt in filtered_possible_loc_dt:
|
||||
utc_time = (
|
||||
local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)
|
||||
assert utc_time not in dates
|
||||
dates[utc_time] = local_dt
|
||||
return dates[[min, max][not is_dst](dates)]
|
||||
|
||||
def utcoffset(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.utcoffset
|
||||
|
||||
The is_dst parameter may be used to remove ambiguity during DST
|
||||
transitions.
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> tz = timezone('America/St_Johns')
|
||||
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
||||
|
||||
>>> str(tz.utcoffset(ambiguous, is_dst=False))
|
||||
'-1 day, 20:30:00'
|
||||
|
||||
>>> str(tz.utcoffset(ambiguous, is_dst=True))
|
||||
'-1 day, 21:30:00'
|
||||
|
||||
>>> try:
|
||||
... tz.utcoffset(ambiguous)
|
||||
... except AmbiguousTimeError:
|
||||
... print('Ambiguous')
|
||||
Ambiguous
|
||||
|
||||
'''
|
||||
if dt is None:
|
||||
return None
|
||||
elif dt.tzinfo is not self:
|
||||
dt = self.localize(dt, is_dst)
|
||||
return dt.tzinfo._utcoffset
|
||||
else:
|
||||
return self._utcoffset
|
||||
|
||||
def dst(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.dst
|
||||
|
||||
The is_dst parameter may be used to remove ambiguity during DST
|
||||
transitions.
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> tz = timezone('America/St_Johns')
|
||||
|
||||
>>> normal = datetime(2009, 9, 1)
|
||||
|
||||
>>> str(tz.dst(normal))
|
||||
'1:00:00'
|
||||
>>> str(tz.dst(normal, is_dst=False))
|
||||
'1:00:00'
|
||||
>>> str(tz.dst(normal, is_dst=True))
|
||||
'1:00:00'
|
||||
|
||||
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
||||
|
||||
>>> str(tz.dst(ambiguous, is_dst=False))
|
||||
'0:00:00'
|
||||
>>> str(tz.dst(ambiguous, is_dst=True))
|
||||
'1:00:00'
|
||||
>>> try:
|
||||
... tz.dst(ambiguous)
|
||||
... except AmbiguousTimeError:
|
||||
... print('Ambiguous')
|
||||
Ambiguous
|
||||
|
||||
'''
|
||||
if dt is None:
|
||||
return None
|
||||
elif dt.tzinfo is not self:
|
||||
dt = self.localize(dt, is_dst)
|
||||
return dt.tzinfo._dst
|
||||
else:
|
||||
return self._dst
|
||||
|
||||
def tzname(self, dt, is_dst=None):
|
||||
'''See datetime.tzinfo.tzname
|
||||
|
||||
The is_dst parameter may be used to remove ambiguity during DST
|
||||
transitions.
|
||||
|
||||
>>> from pytz import timezone
|
||||
>>> tz = timezone('America/St_Johns')
|
||||
|
||||
>>> normal = datetime(2009, 9, 1)
|
||||
|
||||
>>> tz.tzname(normal)
|
||||
'NDT'
|
||||
>>> tz.tzname(normal, is_dst=False)
|
||||
'NDT'
|
||||
>>> tz.tzname(normal, is_dst=True)
|
||||
'NDT'
|
||||
|
||||
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
||||
|
||||
>>> tz.tzname(ambiguous, is_dst=False)
|
||||
'NST'
|
||||
>>> tz.tzname(ambiguous, is_dst=True)
|
||||
'NDT'
|
||||
>>> try:
|
||||
... tz.tzname(ambiguous)
|
||||
... except AmbiguousTimeError:
|
||||
... print('Ambiguous')
|
||||
Ambiguous
|
||||
'''
|
||||
if dt is None:
|
||||
return self.zone
|
||||
elif dt.tzinfo is not self:
|
||||
dt = self.localize(dt, is_dst)
|
||||
return dt.tzinfo._tzname
|
||||
else:
|
||||
return self._tzname
|
||||
|
||||
def __repr__(self):
|
||||
if self._dst:
|
||||
dst = 'DST'
|
||||
else:
|
||||
dst = 'STD'
|
||||
if self._utcoffset > _notime:
|
||||
return '<DstTzInfo %r %s+%s %s>' % (
|
||||
self.zone, self._tzname, self._utcoffset, dst
|
||||
)
|
||||
else:
|
||||
return '<DstTzInfo %r %s%s %s>' % (
|
||||
self.zone, self._tzname, self._utcoffset, dst
|
||||
)
|
||||
|
||||
def __reduce__(self):
|
||||
# Special pickle to zone remains a singleton and to cope with
|
||||
# database changes.
|
||||
return pytz._p, (
|
||||
self.zone,
|
||||
_to_seconds(self._utcoffset),
|
||||
_to_seconds(self._dst),
|
||||
self._tzname
|
||||
)
|
||||
|
||||
|
||||
def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
|
||||
"""Factory function for unpickling pytz tzinfo instances.
|
||||
|
||||
This is shared for both StaticTzInfo and DstTzInfo instances, because
|
||||
database changes could cause a zones implementation to switch between
|
||||
these two base classes and we can't break pickles on a pytz version
|
||||
upgrade.
|
||||
"""
|
||||
# Raises a KeyError if zone no longer exists, which should never happen
|
||||
# and would be a bug.
|
||||
tz = pytz.timezone(zone)
|
||||
|
||||
# A StaticTzInfo - just return it
|
||||
if utcoffset is None:
|
||||
return tz
|
||||
|
||||
# This pickle was created from a DstTzInfo. We need to
|
||||
# determine which of the list of tzinfo instances for this zone
|
||||
# to use in order to restore the state of any datetime instances using
|
||||
# it correctly.
|
||||
utcoffset = memorized_timedelta(utcoffset)
|
||||
dstoffset = memorized_timedelta(dstoffset)
|
||||
try:
|
||||
return tz._tzinfos[(utcoffset, dstoffset, tzname)]
|
||||
except KeyError:
|
||||
# The particular state requested in this timezone no longer exists.
|
||||
# This indicates a corrupt pickle, or the timezone database has been
|
||||
# corrected violently enough to make this particular
|
||||
# (utcoffset,dstoffset) no longer exist in the zone, or the
|
||||
# abbreviation has been changed.
|
||||
pass
|
||||
|
||||
# See if we can find an entry differing only by tzname. Abbreviations
|
||||
# get changed from the initial guess by the database maintainers to
|
||||
# match reality when this information is discovered.
|
||||
for localized_tz in tz._tzinfos.values():
|
||||
if (localized_tz._utcoffset == utcoffset and
|
||||
localized_tz._dst == dstoffset):
|
||||
return localized_tz
|
||||
|
||||
# This (utcoffset, dstoffset) information has been removed from the
|
||||
# zone. Add it back. This might occur when the database maintainers have
|
||||
# corrected incorrect information. datetime instances using this
|
||||
# incorrect information will continue to do so, exactly as they were
|
||||
# before being pickled. This is purely an overly paranoid safety net - I
|
||||
# doubt this will ever been needed in real life.
|
||||
inf = (utcoffset, dstoffset, tzname)
|
||||
tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)
|
||||
return tz._tzinfos[inf]
|
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Abidjan
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Abidjan
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Accra
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Accra
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Addis_Ababa
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Addis_Ababa
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Algiers
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Algiers
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Asmara
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Asmara
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Asmera
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Asmera
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bamako
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bamako
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bangui
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bangui
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Banjul
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Banjul
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bissau
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bissau
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Blantyre
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Blantyre
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Brazzaville
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Brazzaville
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bujumbura
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Bujumbura
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Cairo
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Cairo
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Casablanca
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Casablanca
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Ceuta
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Ceuta
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Conakry
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Conakry
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Dakar
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Dakar
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Dar_es_Salaam
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Dar_es_Salaam
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Djibouti
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Djibouti
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Douala
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Douala
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/El_Aaiun
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/El_Aaiun
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Freetown
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Freetown
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Gaborone
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Gaborone
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Harare
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Harare
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Johannesburg
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Johannesburg
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Juba
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Juba
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kampala
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kampala
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Khartoum
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Khartoum
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kigali
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kigali
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kinshasa
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Kinshasa
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Lagos
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Lagos
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Libreville
Normal file
BIN
venv/Lib/site-packages/pytz/zoneinfo/Africa/Libreville
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user