From a39888332646b197fbc1a9fabb56c84b7541c3ce Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 9 Nov 2018 12:59:00 -0800 Subject: [PATCH 01/33] schedule: make datetime objs timezone-aware and UTC default * fix mock now() and tests with a timezone * allow flake8-max-line-length to be 90 * fixes for lack of datetime.timezone in python 2.7 (simple UTC) Signed-off-by: Steve Arnold --- schedule/__init__.py | 22 +++++++++++++++------- schedule/timezone.py | 18 ++++++++++++++++++ test_schedule.py | 15 ++++++++++++--- tox.ini | 3 +++ 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 schedule/timezone.py diff --git a/schedule/__init__.py b/schedule/__init__.py index 45a6f1d2..b67cd787 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -44,6 +44,14 @@ import random import time +try: + from datetime import timezone + utc = timezone.utc +except ImportError: + from schedule.timezone import UTC + utc = UTC() + + logger = logging.getLogger('schedule') @@ -149,7 +157,7 @@ def idle_seconds(self): :return: Number of seconds until :meth:`next_run `. """ - return (self.next_run - datetime.datetime.now()).total_seconds() + return (self.next_run - datetime.datetime.now(utc)).total_seconds() class Job(object): @@ -191,7 +199,7 @@ def __lt__(self, other): def __repr__(self): def format_time(t): - return t.strftime('%Y-%m-%d %H:%M:%S') if t else '[never]' + return t.strftime('%Y-%m-%d %H:%M:%S %Z') if t else '[never]' timestats = '(last run: %s, next run: %s)' % ( format_time(self.last_run), format_time(self.next_run)) @@ -396,7 +404,7 @@ def should_run(self): """ :return: ``True`` if the job should be run now. """ - return datetime.datetime.now() >= self.next_run + return datetime.datetime.now(utc) >= self.next_run def run(self): """ @@ -406,7 +414,7 @@ def run(self): """ logger.info('Running job %s', self) ret = self.job_func() - self.last_run = datetime.datetime.now() + self.last_run = datetime.datetime.now(utc) self._schedule_next_run() return ret @@ -423,7 +431,7 @@ def _schedule_next_run(self): interval = self.interval self.period = datetime.timedelta(**{self.unit: interval}) - self.next_run = datetime.datetime.now() + self.period + self.next_run = datetime.datetime.now(utc) + self.period if self.start_day is not None: assert self.unit == 'weeks' weekdays = ( @@ -454,7 +462,7 @@ def _schedule_next_run(self): # If we are running for the first time, make sure we run # at the specified time *today* (or *this hour*) as well if not self.last_run: - now = datetime.datetime.now() + now = datetime.datetime.now(utc) if (self.unit == 'days' and self.at_time > now.time() and self.interval == 1): self.next_run = self.next_run - datetime.timedelta(days=1) @@ -462,7 +470,7 @@ def _schedule_next_run(self): self.next_run = self.next_run - datetime.timedelta(hours=1) if self.start_day is not None and self.at_time is not None: # Let's see if we will still make that time we specified today - if (self.next_run - datetime.datetime.now()).days >= 7: + if (self.next_run - datetime.datetime.now(utc)).days >= 7: self.next_run -= self.period diff --git a/schedule/timezone.py b/schedule/timezone.py new file mode 100644 index 00000000..042ed529 --- /dev/null +++ b/schedule/timezone.py @@ -0,0 +1,18 @@ +import datetime + + +class UTC(datetime.tzinfo): + """tzinfo derived concrete class named "UTC" with offset of 0""" + # can be configured here + _offset = datetime.timedelta(seconds=0) + _dst = datetime.timedelta(0) + _name = "UTC" + + def utcoffset(self, dt): + return self.__class__._offset + + def dst(self, dt): + return self.__class__._dst + + def tzname(self, dt): + return self.__class__._name diff --git a/test_schedule.py b/test_schedule.py index 139745ff..e94cf1a7 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -11,6 +11,13 @@ import schedule from schedule import every +try: + from datetime import timezone + utc = timezone.utc +except ImportError: + from schedule.timezone import UTC + utc = UTC() + def make_mock_job(name=None): job = mock.Mock() @@ -36,9 +43,10 @@ def today(cls): return cls(self.year, self.month, self.day) @classmethod - def now(cls): + def now(cls, tz=None): return cls(self.year, self.month, self.day, - self.hour, self.minute) + self.hour, self.minute).replace(tzinfo=tz) + self.original_datetime = datetime.datetime datetime.datetime = MockDate @@ -258,7 +266,8 @@ def test_next_run_property(self): every().hour.do(hourly_job) assert len(schedule.jobs) == 2 # Make sure the hourly job is first - assert schedule.next_run() == original_datetime(2010, 1, 6, 14, 16) + assert schedule.next_run() == original_datetime(2010, 1, 6, 14, 16, + tzinfo=utc) assert schedule.idle_seconds() == 60 * 60 def test_cancel_job(self): diff --git a/tox.ini b/tox.ini index 932d656b..43b26127 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,9 @@ envlist = py27, py36, docs 3.5 = py35, docs 3.6 = py36, docs +[flake8] +max-line-length = 90 + [testenv] deps = -rrequirements-dev.txt commands = From 76483ceeb5843a8f6d8a0a7800b1c990ff481573 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Tue, 13 Nov 2018 09:48:07 -0800 Subject: [PATCH 02/33] Update utc class and add tests for the new code Signed-off-by: Steve Arnold --- schedule/timezone.py | 15 ++++++++------- test_schedule.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/schedule/timezone.py b/schedule/timezone.py index 042ed529..1530e389 100644 --- a/schedule/timezone.py +++ b/schedule/timezone.py @@ -3,16 +3,17 @@ class UTC(datetime.tzinfo): """tzinfo derived concrete class named "UTC" with offset of 0""" - # can be configured here - _offset = datetime.timedelta(seconds=0) - _dst = datetime.timedelta(0) - _name = "UTC" + # can be changed to another timezone name/offset + def __init__(self): + self.__offset = datetime.timedelta(seconds=0) + self.__dst = datetime.timedelta(0) + self.__name = "UTC" def utcoffset(self, dt): - return self.__class__._offset + return self.__offset def dst(self, dt): - return self.__class__._dst + return self.__dst def tzname(self, dt): - return self.__class__._name + return self.__name diff --git a/test_schedule.py b/test_schedule.py index e94cf1a7..82a863b2 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1,4 +1,5 @@ """Unit tests for schedule.py""" +import sys import datetime import functools import mock @@ -72,6 +73,22 @@ def test_singular_time_units_match_plural_units(self): assert every().day.unit == every().days.unit assert every().week.unit == every().weeks.unit + def test_utc_is_normal(self): + fo = utc + self.assertIsInstance(fo, datetime.tzinfo) + dt = datetime.datetime.now() + self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) + self.assertEqual(fo.tzname(dt), "UTC") + + def test_utc_dst_is_dt(self): + fo = utc + dt = datetime.datetime.now() + if sys.version_info > (3, 0, 0): + dst_arg = None + else: + dst_arg = datetime.timedelta(0) + self.assertEqual(fo.dst(dt), dst_arg) + def test_time_range(self): with mock_datetime(2014, 6, 28, 12, 0): mock_job = make_mock_job() From f602fc000e2e30d02f146a1f783021695ba84e82 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 16 Nov 2018 12:35:51 -0800 Subject: [PATCH 03/33] schedule/__init__.py: make datetime objs timezone-aware and UTC default * requires (assumes) standard config of local system and hw clocks (everything is UTC by default using system TZ offset) Signed-off-by: Steve Arnold --- schedule/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index b67cd787..6528e106 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -39,6 +39,7 @@ """ import collections import datetime +from datetime import tzinfo, timezone import functools import logging import random @@ -358,7 +359,7 @@ def at(self, time_str): elif self.unit == 'hours': hour = 0 assert 0 <= minute <= 59 - self.at_time = datetime.time(hour, minute) + self.at_time = datetime.timetz(hour, minute) return self def to(self, latest): From 935cf31cac5601a9adb04c308791b5389abb9dae Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Tue, 13 Nov 2018 18:53:52 -0800 Subject: [PATCH 04/33] schedule/__init__.py: add class-level self.logging, update log calls * for getting logging from the parent package, it needs to specify a logfile/path in logging.basicConfig, import logging, etc Signed-off-by: Steve Arnold --- schedule/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 6528e106..d2862eff 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -53,9 +53,6 @@ utc = UTC() -logger = logging.getLogger('schedule') - - class CancelJob(object): """ Can be returned from a job to unschedule itself. @@ -71,6 +68,7 @@ class Scheduler(object): """ def __init__(self): self.jobs = [] + self.logger = logging.getLogger('schedule.Scheduler') def run_pending(self): """ @@ -96,8 +94,8 @@ def run_all(self, delay_seconds=0): :param delay_seconds: A delay added between every executed job """ - logger.info('Running *all* %i jobs with %is delay inbetween', - len(self.jobs), delay_seconds) + self.logger.info('Running *all* %i jobs with %is delay inbetween', + len(self.jobs), delay_seconds) for job in self.jobs[:]: self._run_job(job) time.sleep(delay_seconds) @@ -190,6 +188,7 @@ def __init__(self, interval, scheduler=None): self.start_day = None # Specific day of the week to start on self.tags = set() # unique set of tags for the job self.scheduler = scheduler # scheduler to register with + self.logger = logging.getLogger('schedule.Job') def __lt__(self, other): """ @@ -413,7 +412,7 @@ def run(self): :return: The return value returned by the `job_func` """ - logger.info('Running job %s', self) + self.logger.info('Running job %s', self) ret = self.job_func() self.last_run = datetime.datetime.now(utc) self._schedule_next_run() From f48d1b8067d64db95470211054a140a8ac079b0d Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Tue, 13 Nov 2018 19:57:24 -0800 Subject: [PATCH 05/33] schedule: add tag param for run_all and extra logging * rebase of original patch over update-logging patch Signed-off-by: Steve Arnold --- schedule/__init__.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index d2862eff..963202e5 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -84,19 +84,30 @@ def run_pending(self): for job in sorted(runnable_jobs): self._run_job(job) - def run_all(self, delay_seconds=0): + def run_all(self, delay_seconds=0, tag=None): """ - Run all jobs regardless if they are scheduled to run or not. + Run all jobs regardless if they are scheduled to run or not, + optionally matching one or more tags. A delay of `delay` seconds is added between each job. This helps distribute system load generated by the jobs more evenly over time. :param delay_seconds: A delay added between every executed job + + :param tag: An identifier used to identify a subset of + jobs to run """ + + if tag is None: + runnable_jobs = self.jobs[:] + else: + runnable_jobs = (job for job in self.jobs if tag in job.tags) + self.logger.info('Running *all* %i jobs with %is delay inbetween', len(self.jobs), delay_seconds) - for job in self.jobs[:]: + + for job in sorted(runnable_jobs): self._run_job(job) time.sleep(delay_seconds) @@ -109,8 +120,10 @@ def clear(self, tag=None): jobs to delete """ if tag is None: + self.logger.info('Deleting *all* jobs') del self.jobs[:] else: + self.logger.info('Deleting all jobs tagged "%s"', tag) self.jobs[:] = (job for job in self.jobs if tag not in job.tags) def cancel_job(self, job): @@ -498,11 +511,11 @@ def run_pending(): default_scheduler.run_pending() -def run_all(delay_seconds=0): +def run_all(delay_seconds=0, tag=None): """Calls :meth:`run_all ` on the :data:`default scheduler instance `. """ - default_scheduler.run_all(delay_seconds=delay_seconds) + default_scheduler.run_all(delay_seconds, tag) def clear(tag=None): From 4fceba067e15e16c1982ec819c6dd696480284f6 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Wed, 14 Nov 2018 18:46:35 -0800 Subject: [PATCH 06/33] schedule/__init__.py: rebase of add-properties on 0.5.0 plus patches Signed-off-by: Steve Arnold --- schedule/__init__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/schedule/__init__.py b/schedule/__init__.py index 963202e5..9a92e2df 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -171,6 +171,21 @@ def idle_seconds(self): """ return (self.next_run - datetime.datetime.now(utc)).total_seconds() + def idle_seconds_since(self, tag=None): + """ + Get the time since the last run of a tagged job + + :param tag: The tag to filter job list + :return: Number of seconds since + :meth:`last_run `. + """ + if not self.jobs or tag is None: + return None + else: + runnable_jobs = (job for job in self.jobs if tag in job.tags) + last_job = min(runnable_jobs).last_run + return (datetime.datetime.now(timezone.utc) - last_job).total_seconds() + class Job(object): """ @@ -193,6 +208,8 @@ def __init__(self, interval, scheduler=None): self.interval = interval # pause interval * unit between runs self.latest = None # upper limit to the interval self.job_func = None # the job job_func to run + self.job_name = None # the name of job_func to run + self.job_info = None # the job timestats (see below) self.unit = None # time units, e.g. 'minutes', 'hours', ... self.at_time = None # optional time at which this job runs self.last_run = None # datetime of the last run @@ -226,6 +243,9 @@ def format_time(t): for k, v in self.job_func.keywords.items()] call_repr = job_func_name + '(' + ', '.join(args + kwargs) + ')' + self.job_name = call_repr + self.job_info = timestats + if self.at_time is not None: return 'Every %s %s at %s do %s %s' % ( self.interval, @@ -419,6 +439,13 @@ def should_run(self): """ return datetime.datetime.now(utc) >= self.next_run + @property + def info(self): + """ + :return: ``string`` with `job_func` name and timestats + """ + return self.job_name + self.job_info + def run(self): """ Run the job and immediately reschedule it. @@ -544,3 +571,10 @@ def idle_seconds(): :data:`default scheduler instance `. """ return default_scheduler.idle_seconds + + +def idle_seconds_since(tag=None): + """Calls :meth:`idle_seconds_since ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.idle_seconds_since(tag) From a66ad048ca751b5479c572f052685ae4b89459aa Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 15 Nov 2018 23:37:47 -0800 Subject: [PATCH 07/33] schedule/parent_logger.py: add class logging snippet * use this to enable separate schedule log from parent application "from schedule.parent_logger import setup_logging" Signed-off-by: Steve Arnold --- schedule/parent_logger.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 schedule/parent_logger.py diff --git a/schedule/parent_logger.py b/schedule/parent_logger.py new file mode 100644 index 00000000..fcbcac7a --- /dev/null +++ b/schedule/parent_logger.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# coding: utf-8 + +import time, logging + +def setup_logging(debug): + if debug: + log_level = logging.getLevelName('DEBUG') + else: + log_level = logging.getLevelName('INFO') + + logging.basicConfig(level=log_level, + format="%(asctime)s %(name)s[%(process)d] %(levelname)s - %(message)s", + datefmt='%Y-%m-%d %H:%M:%S UTC', + filename='/var/log/schedule.log') + + # BUG: this does not print the TZ name because logging module is stupid... + logging.Formatter.converter = time.gmtime + + #global logger + #logger = logging.getLogger("confd") + From 95d72c0ae0705738032c44e751e3236969de41c9 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 16 Nov 2018 13:08:58 -0800 Subject: [PATCH 08/33] Post-rebase clenaups for flake8/pep8 and allow lines up to 95 chars Signed-off-by: Steve Arnold --- schedule/__init__.py | 3 +-- schedule/parent_logger.py | 22 ++++++++++++++++------ tox.ini | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 9a92e2df..05e110cb 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -39,7 +39,6 @@ """ import collections import datetime -from datetime import tzinfo, timezone import functools import logging import random @@ -391,7 +390,7 @@ def at(self, time_str): elif self.unit == 'hours': hour = 0 assert 0 <= minute <= 59 - self.at_time = datetime.timetz(hour, minute) + self.at_time = datetime.time(hour, minute) return self def to(self, latest): diff --git a/schedule/parent_logger.py b/schedule/parent_logger.py index fcbcac7a..063ab267 100644 --- a/schedule/parent_logger.py +++ b/schedule/parent_logger.py @@ -1,9 +1,17 @@ #!/usr/bin/env python # coding: utf-8 -import time, logging +import time +import logging + def setup_logging(debug): + """ + Can be imported by ```` to create a log file for current + scheduler and job class logging. In this example we use a ``debug`` + flag set in ```` to change the Log Level. We also use + UTC time and force the name in ``datefmt``. + """ if debug: log_level = logging.getLevelName('DEBUG') else: @@ -12,11 +20,13 @@ def setup_logging(debug): logging.basicConfig(level=log_level, format="%(asctime)s %(name)s[%(process)d] %(levelname)s - %(message)s", datefmt='%Y-%m-%d %H:%M:%S UTC', - filename='/var/log/schedule.log') + filename='/var/log/scheduler.log') - # BUG: this does not print the TZ name because logging module is stupid... + # BUG: This does not print the TZ name because logging module uses + # time instead of tz-aware datetime objects (so we force the + # correct name in datefmt above). logging.Formatter.converter = time.gmtime - #global logger - #logger = logging.getLogger("confd") - + # To also log parent info, try something like this + # global logger + # logger = logging.getLogger("my_package") diff --git a/tox.ini b/tox.ini index 43b26127..46c4e7b6 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py27, py36, docs 3.6 = py36, docs [flake8] -max-line-length = 90 +max-line-length = 95 [testenv] deps = -rrequirements-dev.txt From 36db9051f35263dae417f7e28b778a89d483f05f Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 16 Nov 2018 13:30:47 -0800 Subject: [PATCH 09/33] schedule/__init__.py: fix crufty datetime argument Signed-off-by: Steve Arnold --- schedule/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 05e110cb..66a009c4 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -183,7 +183,7 @@ def idle_seconds_since(self, tag=None): else: runnable_jobs = (job for job in self.jobs if tag in job.tags) last_job = min(runnable_jobs).last_run - return (datetime.datetime.now(timezone.utc) - last_job).total_seconds() + return (datetime.datetime.now(utc) - last_job).total_seconds() class Job(object): From 14b51a3c1d94ecca7b88fe4908b6c391288bfb42 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Tue, 20 Nov 2018 22:07:45 -0800 Subject: [PATCH 10/33] __init__.py: simplify new properties, tested with get_job_info(tag) * last_run and idle_seconds_since are both None until jobs run once Signed-off-by: Steve Arnold --- schedule/__init__.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 66a009c4..f7aac719 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -170,20 +170,25 @@ def idle_seconds(self): """ return (self.next_run - datetime.datetime.now(utc)).total_seconds() - def idle_seconds_since(self, tag=None): + @property + def last_run(self): """ - Get the time since the last run of a tagged job + Datetime when the last job ran (check for NoneType before using). - :param tag: The tag to filter job list - :return: Number of seconds since - :meth:`last_run `. + :return: A :class:`~datetime.datetime` object """ - if not self.jobs or tag is None: + if not self.jobs: return None - else: - runnable_jobs = (job for job in self.jobs if tag in job.tags) - last_job = min(runnable_jobs).last_run - return (datetime.datetime.now(utc) - last_job).total_seconds() + return max(self.jobs).last_run + + @property + def idle_seconds_since(self): + """ + :return: Number of seconds since (check for NoneType before using). + :meth:`next_run `. + """ + if self.last_run is not None: + return (datetime.datetime.now(utc) - self.last_run).total_seconds() class Job(object): @@ -572,8 +577,15 @@ def idle_seconds(): return default_scheduler.idle_seconds -def idle_seconds_since(tag=None): +def last_run(): + """Calls :meth:`last_run ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.last_run + + +def idle_seconds_since(): """Calls :meth:`idle_seconds_since ` on the :data:`default scheduler instance `. """ - return default_scheduler.idle_seconds_since(tag) + return default_scheduler.idle_seconds_since From 09fd83d02179286b1275dbafd9e57988b8875c66 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Wed, 21 Nov 2018 12:57:59 -0800 Subject: [PATCH 11/33] test_schedule.py: use the custom utc argument for datetime() test Signed-off-by: Steve Arnold --- test_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index 82a863b2..b56f9328 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -76,7 +76,7 @@ def test_singular_time_units_match_plural_units(self): def test_utc_is_normal(self): fo = utc self.assertIsInstance(fo, datetime.tzinfo) - dt = datetime.datetime.now() + dt = datetime.datetime.now(utc) self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) self.assertEqual(fo.tzname(dt), "UTC") From eb1deec538a3eef8290a5aace964b95a4b897646 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Wed, 21 Nov 2018 13:23:25 -0800 Subject: [PATCH 12/33] .travis.yml: disable coveralls post-build step Signed-off-by: Steve Arnold --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 17718e2e..bf5a3a56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,3 @@ python: install: pip install tox-travis coveralls script: - tox - - if [ $TRAVIS_TEST_RESULT -eq 0 ]; then coveralls; fi From d0a2a9b2307bc06876fcf31cf34842f93dd6b1a8 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 16 Nov 2018 12:35:51 -0800 Subject: [PATCH 13/33] schedule/__init__.py: make datetime objs timezone-aware and UTC default * requires (assumes) standard config of local system and hw clocks (everything is UTC by default using system TZ offset) Signed-off-by: Steve Arnold --- schedule/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/schedule/__init__.py b/schedule/__init__.py index 436e365e..ae73d31e 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -42,6 +42,7 @@ except ImportError: from collections import Hashable import datetime +from datetime import tzinfo, timezone import functools import logging import random From 40391c6205390b7bae0116f1910f942e4ae4d6bc Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 16 Nov 2018 13:08:58 -0800 Subject: [PATCH 14/33] Post-rebase clenaups for flake8/pep8 and allow lines up to 95 chars Signed-off-by: Steve Arnold --- schedule/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index ae73d31e..436e365e 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -42,7 +42,6 @@ except ImportError: from collections import Hashable import datetime -from datetime import tzinfo, timezone import functools import logging import random From b2714eafa08d293e9a26169d9434edb342977e14 Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Thu, 14 Nov 2019 12:07:57 -0800 Subject: [PATCH 15/33] test_schedule.py: fix UTC string expected result for python 2 vs 3 Signed-off-by: Stephen Arnold --- test_schedule.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index edee63ff..7337f4eb 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -143,7 +143,11 @@ def test_utc_is_normal(self): self.assertIsInstance(fo, datetime.tzinfo) dt = datetime.datetime.now(utc) self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) - self.assertEqual(fo.tzname(dt), "UTC") + if sys.version_info > (3, 0, 0): + utc_arg = "UTC+00:00" + else: + utc_arg = "UTC" + self.assertEqual(fo.tzname(dt), utc_arg) def test_utc_dst_is_dt(self): fo = utc From fa01b4b7823a8efaf1805be497fa2eca9f267cee Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Thu, 14 Nov 2019 12:54:35 -0800 Subject: [PATCH 16/33] test_schedule.py: (really) fix test for UTC time strings Signed-off-by: Stephen Arnold --- test_schedule.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 7337f4eb..ffb6fa9d 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -143,11 +143,7 @@ def test_utc_is_normal(self): self.assertIsInstance(fo, datetime.tzinfo) dt = datetime.datetime.now(utc) self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) - if sys.version_info > (3, 0, 0): - utc_arg = "UTC+00:00" - else: - utc_arg = "UTC" - self.assertEqual(fo.tzname(dt), utc_arg) + assert "UTC" in fo.tzname(dt) def test_utc_dst_is_dt(self): fo = utc From 88f5932330dadc0cd061c0f0008ee00124b90932 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 10:31:08 -0800 Subject: [PATCH 17/33] hacky test updates, make logfile path an argument for setup_logging() * test coverage is good, test classes need cleanup Signed-off-by: Steve Arnold --- schedule/parent_logger.py | 8 +- test_schedule.py | 156 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 4 deletions(-) diff --git a/schedule/parent_logger.py b/schedule/parent_logger.py index 063ab267..fce5d415 100644 --- a/schedule/parent_logger.py +++ b/schedule/parent_logger.py @@ -5,12 +5,12 @@ import logging -def setup_logging(debug): +def setup_logging(debug, filename): """ Can be imported by ```` to create a log file for current scheduler and job class logging. In this example we use a ``debug`` - flag set in ```` to change the Log Level. We also use - UTC time and force the name in ``datefmt``. + flag set in ```` to change the Log Level and ``filename`` + to set log path. We also use UTC time and force the name in ``datefmt``. """ if debug: log_level = logging.getLevelName('DEBUG') @@ -20,7 +20,7 @@ def setup_logging(debug): logging.basicConfig(level=log_level, format="%(asctime)s %(name)s[%(process)d] %(levelname)s - %(message)s", datefmt='%Y-%m-%d %H:%M:%S UTC', - filename='/var/log/scheduler.log') + filename=filename) # BUG: This does not print the TZ name because logging module uses # time instead of tz-aware datetime objects (so we force the diff --git a/test_schedule.py b/test_schedule.py index ffb6fa9d..10c132a0 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1,9 +1,13 @@ """Unit tests for schedule.py""" +import os import sys import datetime +import logging import functools +import time import mock import unittest +from test import support # Silence "missing docstring", "method could be a function", # "class already defined", and "too many public methods" messages: @@ -11,6 +15,7 @@ import schedule from schedule import every, ScheduleError, ScheduleValueError, IntervalError +from schedule.parent_logger import setup_logging try: from datetime import timezone @@ -138,6 +143,7 @@ def test_singular_time_units_match_plural_units(self): assert every().day.unit == every().days.unit assert every().week.unit == every().weeks.unit +<<<<<<< HEAD def test_utc_is_normal(self): fo = utc self.assertIsInstance(fo, datetime.tzinfo) @@ -154,6 +160,8 @@ def test_utc_dst_is_dt(self): dst_arg = datetime.timedelta(0) self.assertEqual(fo.dst(dt), dst_arg) +======= +>>>>>>> 61adaaa... hacky test updates, make logfile path an argument for setup_logging() def test_time_range(self): with mock_datetime(2014, 6, 28, 12, 0): mock_job = make_mock_job() @@ -504,3 +512,151 @@ def test_misconfigured_job_wont_break_scheduler(self): scheduler.every() scheduler.every(10).seconds scheduler.run_pending() + + +@unittest.skipUnless(sys.version_info < (3, 0, 0), + 'schedule class timezone tests only for Python 2.7') +class TimezoneTests(unittest.TestCase): + def test_utc_is_normal(self): + fo = utc + self.assertIsInstance(fo, datetime.tzinfo) + dt = datetime.datetime.now(utc) + self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) + self.assertEqual(fo.tzname(dt), "UTC") + + def test_utc_dst_is_dt(self): + fo = utc + dt = datetime.datetime.now() + if sys.version_info > (3, 0, 0): + dst_arg = None + else: + dst_arg = datetime.timedelta(0) + self.assertEqual(fo.dst(dt), dst_arg) + + +class LogFormatterTest(unittest.TestCase): + + """Tests for logging formats""" + + def setUp(self): + self.common = { + 'name': 'formatter.test', + 'level': logging.DEBUG, + 'pathname': os.path.join('path', 'to', 'dummy.ext'), + 'lineno': 42, + 'exc_info': None, + 'func': None, + 'msg': 'Message with %d %s', + 'args': (2, 'placeholders'), + } + self.variants = { + } + + def get_record(self, name=None): + result = dict(self.common) + if name is not None: + result.update(self.variants[name]) + return logging.makeLogRecord(result) + + def test_percent(self): + # Test %-formatting + r = self.get_record() + f = logging.Formatter('${%(message)s}') + self.assertEqual(f.format(r), '${Message with 2 placeholders}') + f = logging.Formatter('%(random)s') + self.assertRaises(KeyError, f.format, r) + self.assertFalse(f.usesTime()) + f = logging.Formatter('%(asctime)s') + self.assertTrue(f.usesTime()) + f = logging.Formatter('%(asctime)-15s') + self.assertTrue(f.usesTime()) + f = logging.Formatter('asctime') + self.assertFalse(f.usesTime()) + + def test_time(self): + original_datetime = datetime.datetime + tz = utc + with mock_datetime(1993, 2, 21, 12, 3): + r = self.get_record() + dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) + r.created = time.mktime(dt.astimezone(tz).timetuple()) + r.msecs = 123 + f = logging.Formatter('%(asctime)s %(message)s') + f.converter = time.gmtime + self.assertEqual(f.formatTime(r), '1993-02-21 12:03:00,123') + self.assertEqual(f.formatTime(r, '%Y:%d'), '1993:21') + f.format(r) + self.assertEqual(r.asctime, '1993-02-21 12:03:00,123') + + +class BasicConfigTest(unittest.TestCase): + + """Tests for logging.basicConfig.""" + + def setUp(self): + super(BasicConfigTest, self).setUp() + self.handlers = logging.root.handlers + self.saved_handlers = logging._handlers.copy() + self.saved_handler_list = logging._handlerList[:] + self.original_logging_level = logging.root.level + self.addCleanup(self.cleanup) + logging.root.handlers = [] + + def tearDown(self): + for h in logging.root.handlers[:]: + logging.root.removeHandler(h) + h.close() + super(BasicConfigTest, self).tearDown() + + def cleanup(self): + setattr(logging.root, 'handlers', self.handlers) + logging._handlers.clear() + logging._handlers.update(self.saved_handlers) + logging._handlerList[:] = self.saved_handler_list + logging.root.level = self.original_logging_level + + def test_debug_level(self): + old_level = logging.root.level + self.addCleanup(logging.root.setLevel, old_level) + + debug = True + setup_logging(debug, '/dev/null') + self.assertEqual(logging.root.level, logging.DEBUG) + # Test that second call has no effect + logging.basicConfig(level=58) + self.assertEqual(logging.root.level, logging.DEBUG) + + def test_info_level(self): + old_level = logging.root.level + self.addCleanup(logging.root.setLevel, old_level) + + debug = False + setup_logging(debug, '/dev/null') + self.assertEqual(logging.root.level, logging.INFO) + # Test that second call has no effect + logging.basicConfig(level=58) + self.assertEqual(logging.root.level, logging.INFO) + + def _test_log(self, method, level=None): + # logging.root has no handlers so basicConfig should be called + called = [] + + old_basic_config = logging.basicConfig + + def my_basic_config(*a, **kw): + old_basic_config() + old_level = logging.root.level + logging.root.setLevel(100) # avoid having messages in stderr + self.addCleanup(logging.root.setLevel, old_level) + called.append((a, kw)) + + support.patch(self, logging, 'basicConfig', my_basic_config) + + log_method = getattr(logging, method) + if level is not None: + log_method(level, "test me") + else: + log_method("test me") + + # basicConfig was called with no arguments + self.assertEqual(called, [((), {})]) From 40fcc3f4e752a53cc7a478cdb64100a13d60bff9 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 18:17:45 -0800 Subject: [PATCH 18/33] test_schedule.py: more test coverage, small wrinkle in utc tests Signed-off-by: Steve Arnold --- test_schedule.py | 68 ++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 10c132a0..d48e1492 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -7,7 +7,7 @@ import time import mock import unittest -from test import support +# from test import support # Silence "missing docstring", "method could be a function", # "class already defined", and "too many public methods" messages: @@ -445,6 +445,40 @@ def test_next_run_property(self): tzinfo=utc) assert schedule.idle_seconds() == 60 * 60 + def test_last_run_property(self): + original_datetime = datetime.datetime + with mock_datetime(2010, 1, 6, 13, 16): + hourly_job = make_mock_job('hourly') + daily_job = make_mock_job('daily') + every().day.do(daily_job) + every().hour.do(hourly_job) + schedule.run_all() + # Make sure jobs have last_run and idle_seconds_since + assert schedule.last_run() == original_datetime(2010, 1, 6, 13, 16, + tzinfo=utc) + assert schedule.idle_seconds_since() == 0 + schedule.clear() + assert schedule.last_run() is None + + def test_job_info(self): + original_datetime = datetime.datetime + with mock_datetime(2010, 1, 6, 14, 16): + mock_job = make_mock_job(name='info_job') + info_job = every().second.do(mock_job, 1, 7, 'three') + schedule.run_all() + assert len(schedule.jobs) == 1 + assert schedule.jobs[0] == info_job + info_job.job_name = repr(info_job) + dt = original_datetime(2010, 1, 6, 14, 16, tzinfo=utc) + ts = dt.strftime("%Y-%m-%d %H:%M:%S %Z") + info_job.job_info = "last_run: " + ts + for job in schedule.jobs: + s = info_job.info + assert 'info_job' in s + assert 'three' in s + assert 'UTC' in s + assert '7' in s + def test_cancel_job(self): def stop_job(): return schedule.CancelJob @@ -514,13 +548,15 @@ def test_misconfigured_job_wont_break_scheduler(self): scheduler.run_pending() -@unittest.skipUnless(sys.version_info < (3, 0, 0), - 'schedule class timezone tests only for Python 2.7') class TimezoneTests(unittest.TestCase): + if sys.version_info > (3, 0, 0): + from schedule.timezone import UTC + utc = UTC() + def test_utc_is_normal(self): fo = utc self.assertIsInstance(fo, datetime.tzinfo) - dt = datetime.datetime.now(utc) + dt = datetime.datetime.now() self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) self.assertEqual(fo.tzname(dt), "UTC") @@ -636,27 +672,3 @@ def test_info_level(self): # Test that second call has no effect logging.basicConfig(level=58) self.assertEqual(logging.root.level, logging.INFO) - - def _test_log(self, method, level=None): - # logging.root has no handlers so basicConfig should be called - called = [] - - old_basic_config = logging.basicConfig - - def my_basic_config(*a, **kw): - old_basic_config() - old_level = logging.root.level - logging.root.setLevel(100) # avoid having messages in stderr - self.addCleanup(logging.root.setLevel, old_level) - called.append((a, kw)) - - support.patch(self, logging, 'basicConfig', my_basic_config) - - log_method = getattr(logging, method) - if level is not None: - log_method(level, "test me") - else: - log_method("test me") - - # basicConfig was called with no arguments - self.assertEqual(called, [((), {})]) From 8e26274785adcd16d9a36d0d396785ab38e9515c Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 19:10:44 -0800 Subject: [PATCH 19/33] test_schedule.py: add last new test for run_all(tag) Signed-off-by: Steve Arnold --- test_schedule.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test_schedule.py b/test_schedule.py index d48e1492..a04191b7 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -294,6 +294,21 @@ def test_run_all(self): every().day.at('11:00').do(mock_job) schedule.run_all() assert mock_job.call_count == 3 + schedule.clear() + + # setup new tagged jobs + job1 = every().second.do(make_mock_job(name='job1')).tag('tag1') + job2 = every().second.do(make_mock_job(name='job2')).tag('tag1', 'tag2') + job3 = every().second.do(make_mock_job(name='job3')).tag('tag3', 'tag3', + 'tag3', 'tag2') + assert len(schedule.jobs) == 3 + schedule.run_all(0, 'tag3') + assert 'tag3' in str(job3.tags) + assert 'tag3' not in str(job2.tags) + assert 'tag1' in str(job2.tags) + self.assertRaises(TypeError, job1.last_run) + self.assertRaises(TypeError, job2.last_run) + assert schedule.last_run() is not None def test_job_func_args_are_passed_on(self): mock_job = make_mock_job() From 5f73a9b0c105d2b21606b110edfb1e3eb09b4131 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 19:22:43 -0800 Subject: [PATCH 20/33] .travis.yml: set TZ env variable (fix for test_time() failure) Signed-off-by: Steve Arnold --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5850c6b2..8f77f2e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ dist: xenial language: python + python: - "2.7" - "3.5" @@ -7,7 +8,12 @@ python: - "3.7" - "3.8-dev" - "nightly" + install: pip install tox-travis coveralls + +before_install: + - export TZ=PST8PDT + script: - tox after_success: From 4fc2813efd8558a38d58162f7e7cbe21331921c3 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 19:27:21 -0800 Subject: [PATCH 21/33] .travis.yml: okay, let's see what travis says the date is... Signed-off-by: Steve Arnold --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8f77f2e9..25e02a0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ install: pip install tox-travis coveralls before_install: - export TZ=PST8PDT + - date script: - tox From 5de0f5fc81951c3863a61eb6e5fdb68f03ada54a Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 19:39:28 -0800 Subject: [PATCH 22/33] test_schedule.py: fix mock date for assert comparison Signed-off-by: Steve Arnold --- test_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index a04191b7..61ec710b 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -627,7 +627,7 @@ def test_percent(self): def test_time(self): original_datetime = datetime.datetime tz = utc - with mock_datetime(1993, 2, 21, 12, 3): + with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) r.created = time.mktime(dt.astimezone(tz).timetuple()) From af02ea9fce1c5427ef3027e6ff9167f9348b961c Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 19:49:30 -0800 Subject: [PATCH 23/33] test_schedule.py: get rid of timezone change in logging test_time() Signed-off-by: Steve Arnold --- test_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index 61ec710b..42dbec9f 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -630,7 +630,7 @@ def test_time(self): with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) - r.created = time.mktime(dt.astimezone(tz).timetuple()) + r.created = time.mktime(dt.timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') f.converter = time.gmtime From 5da832dcb0b608aafc4f6d3acc58cc1a357e360d Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 20:03:23 -0800 Subject: [PATCH 24/33] test_schedule.py: try something really simple for travis Signed-off-by: Steve Arnold --- test_schedule.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 42dbec9f..54f12490 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -626,10 +626,10 @@ def test_percent(self): def test_time(self): original_datetime = datetime.datetime - tz = utc - with mock_datetime(1993, 2, 21, 4, 3): + # tz = utc + with mock_datetime(1993, 2, 21, 12, 3): r = self.get_record() - dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) + dt = original_datetime(1993, 2, 21, 4, 3) r.created = time.mktime(dt.timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') From 6b5cd8dd1ab25a28a5265407ac7eca9f56cb5a9a Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 20:34:08 -0800 Subject: [PATCH 25/33] test_schedule.py: one more silly travis test of time_test() Signed-off-by: Steve Arnold --- test_schedule.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 54f12490..70f2385f 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -625,12 +625,12 @@ def test_percent(self): self.assertFalse(f.usesTime()) def test_time(self): - original_datetime = datetime.datetime + # original_datetime = datetime.datetime(1993, 2, 21, 4, 3, 0, 0, tzinfo=None) # tz = utc - with mock_datetime(1993, 2, 21, 12, 3): + with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() - dt = original_datetime(1993, 2, 21, 4, 3) - r.created = time.mktime(dt.timetuple()) + dt = datetime.datetime.now(utc) + r.created = time.mktime(dt.astimezone(utc).timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') f.converter = time.gmtime From 471a169ff2b2037e2074eb0b3f34e4aae5831eb7 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 20:50:44 -0800 Subject: [PATCH 26/33] test_schedule.py: try an even simpler test in travis... Signed-off-by: Steve Arnold --- test_schedule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 70f2385f..e939f03f 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -629,8 +629,8 @@ def test_time(self): # tz = utc with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() - dt = datetime.datetime.now(utc) - r.created = time.mktime(dt.astimezone(utc).timetuple()) + dt = datetime.datetime.now() + r.created = time.mktime(dt.timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') f.converter = time.gmtime From 85b6cef3500a7d725697d6300461e7c46c503eea Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 21:00:42 -0800 Subject: [PATCH 27/33] test_schedule.py: yet another silly travis test Signed-off-by: Steve Arnold --- test_schedule.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index e939f03f..e5a11f4e 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -586,6 +586,9 @@ def test_utc_dst_is_dt(self): class LogFormatterTest(unittest.TestCase): + if sys.version_info > (3, 0, 0): + from schedule.timezone import UTC + utc = UTC() """Tests for logging formats""" @@ -629,7 +632,7 @@ def test_time(self): # tz = utc with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() - dt = datetime.datetime.now() + dt = datetime.datetime.now(utc) r.created = time.mktime(dt.timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') From 1759225ea89e3ef3b1452aa3578022bcbc358eec Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Thu, 22 Nov 2018 22:37:47 -0800 Subject: [PATCH 28/33] test_schedule.py,travis.yml: revert, try another silly timezone setting Signed-off-by: Steve Arnold --- .travis.yml | 2 +- test_schedule.py | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25e02a0c..eacd7d7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ python: install: pip install tox-travis coveralls before_install: - - export TZ=PST8PDT + - export TZ=America/Los_Angeles - date script: diff --git a/test_schedule.py b/test_schedule.py index e5a11f4e..61ec710b 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -586,9 +586,6 @@ def test_utc_dst_is_dt(self): class LogFormatterTest(unittest.TestCase): - if sys.version_info > (3, 0, 0): - from schedule.timezone import UTC - utc = UTC() """Tests for logging formats""" @@ -628,12 +625,12 @@ def test_percent(self): self.assertFalse(f.usesTime()) def test_time(self): - # original_datetime = datetime.datetime(1993, 2, 21, 4, 3, 0, 0, tzinfo=None) - # tz = utc + original_datetime = datetime.datetime + tz = utc with mock_datetime(1993, 2, 21, 4, 3): r = self.get_record() - dt = datetime.datetime.now(utc) - r.created = time.mktime(dt.timetuple()) + dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) + r.created = time.mktime(dt.astimezone(tz).timetuple()) r.msecs = 123 f = logging.Formatter('%(asctime)s %(message)s') f.converter = time.gmtime From ecf4766d6fe51e27361d521f282da168230db146 Mon Sep 17 00:00:00 2001 From: Steve Arnold Date: Fri, 23 Nov 2018 00:26:45 -0800 Subject: [PATCH 29/33] test_schedule.py: remove problematic tests we don't need anyway Signed-off-by: Steve Arnold --- test_schedule.py | 58 +----------------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 61ec710b..417c76a5 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1,10 +1,9 @@ """Unit tests for schedule.py""" -import os + import sys import datetime import logging import functools -import time import mock import unittest # from test import support @@ -585,61 +584,6 @@ def test_utc_dst_is_dt(self): self.assertEqual(fo.dst(dt), dst_arg) -class LogFormatterTest(unittest.TestCase): - - """Tests for logging formats""" - - def setUp(self): - self.common = { - 'name': 'formatter.test', - 'level': logging.DEBUG, - 'pathname': os.path.join('path', 'to', 'dummy.ext'), - 'lineno': 42, - 'exc_info': None, - 'func': None, - 'msg': 'Message with %d %s', - 'args': (2, 'placeholders'), - } - self.variants = { - } - - def get_record(self, name=None): - result = dict(self.common) - if name is not None: - result.update(self.variants[name]) - return logging.makeLogRecord(result) - - def test_percent(self): - # Test %-formatting - r = self.get_record() - f = logging.Formatter('${%(message)s}') - self.assertEqual(f.format(r), '${Message with 2 placeholders}') - f = logging.Formatter('%(random)s') - self.assertRaises(KeyError, f.format, r) - self.assertFalse(f.usesTime()) - f = logging.Formatter('%(asctime)s') - self.assertTrue(f.usesTime()) - f = logging.Formatter('%(asctime)-15s') - self.assertTrue(f.usesTime()) - f = logging.Formatter('asctime') - self.assertFalse(f.usesTime()) - - def test_time(self): - original_datetime = datetime.datetime - tz = utc - with mock_datetime(1993, 2, 21, 4, 3): - r = self.get_record() - dt = original_datetime(1993, 2, 21, 4, 3, tzinfo=tz) - r.created = time.mktime(dt.astimezone(tz).timetuple()) - r.msecs = 123 - f = logging.Formatter('%(asctime)s %(message)s') - f.converter = time.gmtime - self.assertEqual(f.formatTime(r), '1993-02-21 12:03:00,123') - self.assertEqual(f.formatTime(r, '%Y:%d'), '1993:21') - f.format(r) - self.assertEqual(r.asctime, '1993-02-21 12:03:00,123') - - class BasicConfigTest(unittest.TestCase): """Tests for logging.basicConfig.""" From d84b3bb687173edb28720d99a3252ea68ef38b0e Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sat, 24 Nov 2018 10:24:49 -0800 Subject: [PATCH 30/33] schedule/__init__.py: minor fixes and test updates Signed-off-by: Stephen Arnold --- schedule/__init__.py | 5 +++-- test_schedule.py | 53 ++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 436e365e..ede6c26f 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -206,8 +206,9 @@ def idle_seconds_since(self): :return: Number of seconds since (check for NoneType before using). :meth:`next_run `. """ - if self.last_run is not None: - return (datetime.datetime.now(utc) - self.last_run).total_seconds() + if self.last_run is None: + return None + return (datetime.datetime.now(utc) - self.last_run).total_seconds() class Job(object): diff --git a/test_schedule.py b/test_schedule.py index 417c76a5..19606de2 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -293,21 +293,24 @@ def test_run_all(self): every().day.at('11:00').do(mock_job) schedule.run_all() assert mock_job.call_count == 3 - schedule.clear() - # setup new tagged jobs - job1 = every().second.do(make_mock_job(name='job1')).tag('tag1') - job2 = every().second.do(make_mock_job(name='job2')).tag('tag1', 'tag2') - job3 = every().second.do(make_mock_job(name='job3')).tag('tag3', 'tag3', - 'tag3', 'tag2') - assert len(schedule.jobs) == 3 - schedule.run_all(0, 'tag3') - assert 'tag3' in str(job3.tags) - assert 'tag3' not in str(job2.tags) - assert 'tag1' in str(job2.tags) - self.assertRaises(TypeError, job1.last_run) - self.assertRaises(TypeError, job2.last_run) - assert schedule.last_run() is not None + def test_run_tag(self): + with mock_datetime(2010, 1, 6, 12, 15): + # setup new tagged jobs + mock_job = make_mock_job() + assert schedule.last_run() is None + job1 = every().hour.do(mock_job(name='job1')).tag('tag1') + job2 = every().hour.do(mock_job(name='job2')).tag('tag1', 'tag2') + job3 = every().hour.do(mock_job(name='job3')).tag('tag3', 'tag3', + 'tag3', 'tag2') + assert len(schedule.jobs) == 3 + schedule.run_all(0, 'tag1') + assert 'tag1' in str(job1.tags) + assert 'tag1' not in str(job3.tags) + assert 'tag1' in str(job2.tags) + assert job1.last_run.minute == 15 + assert job2.last_run.hour == 12 + assert job3.last_run is None def test_job_func_args_are_passed_on(self): mock_job = make_mock_job() @@ -466,8 +469,8 @@ def test_last_run_property(self): daily_job = make_mock_job('daily') every().day.do(daily_job) every().hour.do(hourly_job) + assert schedule.idle_seconds_since() is None schedule.run_all() - # Make sure jobs have last_run and idle_seconds_since assert schedule.last_run() == original_datetime(2010, 1, 6, 13, 16, tzinfo=utc) assert schedule.idle_seconds_since() == 0 @@ -475,23 +478,19 @@ def test_last_run_property(self): assert schedule.last_run() is None def test_job_info(self): - original_datetime = datetime.datetime with mock_datetime(2010, 1, 6, 14, 16): mock_job = make_mock_job(name='info_job') - info_job = every().second.do(mock_job, 1, 7, 'three') + info_job = every().minute.do(mock_job, 1, 7, 'three') schedule.run_all() assert len(schedule.jobs) == 1 assert schedule.jobs[0] == info_job - info_job.job_name = repr(info_job) - dt = original_datetime(2010, 1, 6, 14, 16, tzinfo=utc) - ts = dt.strftime("%Y-%m-%d %H:%M:%S %Z") - info_job.job_info = "last_run: " + ts - for job in schedule.jobs: - s = info_job.info - assert 'info_job' in s - assert 'three' in s - assert 'UTC' in s - assert '7' in s + assert repr(info_job) + assert info_job.job_name is not None + s = info_job.info + assert 'info_job' in s + assert 'three' in s + assert '2010' in s + assert '14:16' in s def test_cancel_job(self): def stop_job(): From be40e3fb69fc760512012514b4b34156184e6b4c Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sun, 25 Nov 2018 13:27:56 -0800 Subject: [PATCH 31/33] test_schedule.py: remove spurious comment Signed-off-by: Stephen Arnold --- test_schedule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index 19606de2..37808e70 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -296,7 +296,6 @@ def test_run_all(self): def test_run_tag(self): with mock_datetime(2010, 1, 6, 12, 15): - # setup new tagged jobs mock_job = make_mock_job() assert schedule.last_run() is None job1 = every().hour.do(mock_job(name='job1')).tag('tag1') From 50c15c4f61eac1db0931abf7c6999b817a02246a Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Thu, 14 Nov 2019 13:31:27 -0800 Subject: [PATCH 32/33] test_schedule.py: pluck fix for UTC string comparison from utc branch Signed-off-by: Stephen Arnold --- test_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_schedule.py b/test_schedule.py index 37808e70..8e0e6224 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -570,7 +570,7 @@ def test_utc_is_normal(self): self.assertIsInstance(fo, datetime.tzinfo) dt = datetime.datetime.now() self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) - self.assertEqual(fo.tzname(dt), "UTC") + assert "UTC" in fo.tzname(dt) def test_utc_dst_is_dt(self): fo = utc From 651e0ed1d79ade843e81cfcefc9df40cc7824ac0 Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Thu, 14 Nov 2019 13:49:40 -0800 Subject: [PATCH 33/33] test_schedule.py: fix silly left-over merge cruft Signed-off-by: Stephen Arnold --- test_schedule.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test_schedule.py b/test_schedule.py index 8e0e6224..9e93ffa1 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -142,25 +142,6 @@ def test_singular_time_units_match_plural_units(self): assert every().day.unit == every().days.unit assert every().week.unit == every().weeks.unit -<<<<<<< HEAD - def test_utc_is_normal(self): - fo = utc - self.assertIsInstance(fo, datetime.tzinfo) - dt = datetime.datetime.now(utc) - self.assertEqual(fo.utcoffset(dt), datetime.timedelta(0)) - assert "UTC" in fo.tzname(dt) - - def test_utc_dst_is_dt(self): - fo = utc - dt = datetime.datetime.now() - if sys.version_info > (3, 0, 0): - dst_arg = None - else: - dst_arg = datetime.timedelta(0) - self.assertEqual(fo.dst(dt), dst_arg) - -======= ->>>>>>> 61adaaa... hacky test updates, make logfile path an argument for setup_logging() def test_time_range(self): with mock_datetime(2014, 6, 28, 12, 0): mock_job = make_mock_job()